diff --git a/CHANGELOG.md b/CHANGELOG.md index 64a35df2..f801b08d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,59 @@ Version 1.4.0 ============= This version adds support for integer _ranges_ via the `..` and `..=` operators. +Many standard API's are extended with range parameters where appropriate. + +Script-breaking changes +----------------------- + +* `is` is (pun intended) now a reserved keyword to prepare for possible future type checking expressions (e.g. `x is "string"`). + +Breaking changes +---------------- + +* `LogicPackage` is removed from `CorePackage`. +* Bit-field functions are moved into a new `BitFieldPackage` (used to be in `LogicPackage`) which makes more sense. + +Bug fixes +--------- + +* Constructing a literal array or object map now checks for size limits for each item instead of at the very end when it is already too late. +* Non-`INT` integer types are now treated exactly as custom types under `only_i64` and `only_i32`. +* Calling `pad` on an array now checks for total size over limit after each item added. New features ------------ * Added support for integer _ranges_ via the `..` and `..=` operators. +Enhancements +------------ + +* A new syntax is introduced for `def_package!` that will replace the old syntax in future versions. +* Added `NativeCallContext::call_fn` to easily call a function. +* Doc-comments on plugin module functions are extracted into the functions' metadata. + +Deprecated API's +---------------- + +* `Expression::get_variable_name` is deprecated in favor of the new `Expression::get_string_value`. +* The old syntax of `def_package!` is deprecated in favor of the new syntax. + + +Version 1.3.1 +============= + +Bug fixes +--------- + +* Custom syntax now works properly inside binary expressions and with method calls. +* Hex numbers with the high-bit set now parse correctly into negative integer numbers. + +Enhancements +------------ + +* `BLOB`'s are refined to display in a more compact hex format. + Version 1.3.0 ============= @@ -71,8 +118,8 @@ Bug fixes Version 1.2.0 ============= -Bug fixes with breaking script changes -------------------------------------- +Bug fixes (potentially script-breaking) +-------------------------------------- * As originally intended, function calls with a bang (`!`) now operates directly on the caller's scope, allowing variables inside the scope to be mutated. * As originally intended, `Engine::XXX_with_scope` API's now properly propagate constants within the provided scope also to _functions_ in the script. @@ -104,7 +151,7 @@ Deprecated API's ---------------- * `NativeCallContext::call_fn_dynamic_raw` is deprecated and `NativeCallContext::call_fn_raw` is added. -* `From` for `Result>` is deprecated so it will no longer be possible to do `EvalAltResult::ErrorXXXXX.into()` to convert to a `Result`; instead, `Err(EvalAltResult:ErrorXXXXX.into())` must be used. Code is clearer if errors are explicitly wrapped in `Err`. +* `From` for `Result >` is deprecated so it will no longer be possible to do `EvalAltResult::ErrorXXXXX.into()` to convert to a `Result`; instead, `Err(EvalAltResult:ErrorXXXXX.into())` must be used. Code is clearer if errors are explicitly wrapped in `Err`. Version 1.1.2 @@ -177,7 +224,7 @@ Enhancements ### `Scope` API -* `Scope::set_value` now takes anything that implements `Into>`. +* `Scope::set_value` now takes anything that implements `Into >`. * Added `Scope::is_constant` to check if a variable is constant. * Added `Scope::set_or_push` to add a new variable only if one doesn't already exist. @@ -462,7 +509,8 @@ Enhancements * Replaced all `HashMap` usage with `BTreeMap` for better performance because collections in Rhai are tiny. * `Engine::register_result_fn` no longer requires the successful return type to be `Dynamic`. It can now be any clonable type. -* `#[rhai_fn(return_raw)]` can now return `Result>` where `T` is any clonable type instead of `Result>`. +* `#[rhai_fn(return_raw)]` can now return `Result >` where `T` is any clonable + type instead of `Result >`. * `Dynamic::clone_cast` is added to simplify casting from a `&Dynamic`. @@ -932,7 +980,7 @@ Breaking changes ---------------- * `AST::iter_functions` now returns an iterator instead of taking a closure. -* `Module::get_script_function_by_signature` renamed to `Module::get_script_fn` and returns `&>`. +* `Module::get_script_function_by_signature` renamed to `Module::get_script_fn` and returns `& >`. * `Module::num_fn`, `Module::num_var` and `Module::num_iter` are removed and merged into `Module::count`. * The `merge_namespaces` parameter to `Module::eval_ast_as_new` is removed and now defaults to `true`. * `GlobalFileModuleResolver` is removed because its performance gain over the `FileModuleResolver` is no longer very significant. @@ -980,7 +1028,7 @@ Bug fixes Breaking changes ---------------- -* `Engine::register_set_result` and `Engine::register_indexer_set_result` now take a function that returns `Result<(), Box>`. +* `Engine::register_set_result` and `Engine::register_indexer_set_result` now take a function that returns `Result<(), Box >`. * `Engine::register_indexer_XXX` and `Module::set_indexer_XXX` panic when the type is `Array`, `Map` or `String`. * `EvalAltResult` has a new variant `ErrorInModule` which holds errors when loading an external module. * `Module::eval_ast_as_new` now takes an extra boolean parameter, indicating whether to encapsulate the entire module into a separate namespace. @@ -1101,7 +1149,7 @@ Breaking changes * `EvalAltResult::ErrorMismatchOutputType` has an extra argument containing the name of the requested type. * `Engine::call_fn_dynamic` take an extra argument, allowing a `Dynamic` value to be bound to the `this` pointer. -* Precedence of the `%` (modulo) operator is lowered to below `<<` ad `>>`. This is to handle the case of `x << 3 % 10`. +* Precedence of the `%` (modulo) operator is lowered to below bit shifts. This is to handle the case of `x < < 3 % 10`. New features ------------ diff --git a/README.md b/README.md index 92248da0..85dac181 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ The [`scripts`](https://github.com/rhaiscript/rhai/tree/master/scripts) subdirec Below is the standard _Fibonacci_ example for scripting languages: -```js +```ts // This Rhai script calculates the n-th Fibonacci number using a // really dumb algorithm to test the speed of the scripting engine. diff --git a/benches/eval_array.rs b/benches/eval_array.rs index b89cc9d6..7287f222 100644 --- a/benches/eval_array.rs +++ b/benches/eval_array.rs @@ -64,7 +64,7 @@ fn bench_eval_array_large_set(bench: &mut Bencher) { #[bench] fn bench_eval_array_loop(bench: &mut Bencher) { - let script = r#" + let script = " let list = []; for i in 0..10_000 { @@ -76,7 +76,7 @@ fn bench_eval_array_loop(bench: &mut Bencher) { for i in list { sum += i; } - "#; + "; let mut engine = Engine::new(); engine.set_optimization_level(OptimizationLevel::None); diff --git a/benches/eval_expression.rs b/benches/eval_expression.rs index 73a2afb9..73bae9c0 100644 --- a/benches/eval_expression.rs +++ b/benches/eval_expression.rs @@ -108,12 +108,12 @@ fn bench_eval_call(bench: &mut Bencher) { #[bench] fn bench_eval_loop_number(bench: &mut Bencher) { - let script = r#" + let script = " let s = 0; for x in 0..10000 { s += 1; } - "#; + "; let mut engine = Engine::new(); engine.set_optimization_level(OptimizationLevel::None); @@ -159,7 +159,7 @@ fn bench_eval_loop_strings_no_build(bench: &mut Bencher) { #[bench] fn bench_eval_switch(bench: &mut Bencher) { - let script = r#" + let script = " let sum = 0; let rem = 0; @@ -179,7 +179,7 @@ fn bench_eval_switch(bench: &mut Bencher) { 9 => 1, } } - "#; + "; let mut engine = Engine::new(); engine.set_optimization_level(OptimizationLevel::None); @@ -191,7 +191,7 @@ fn bench_eval_switch(bench: &mut Bencher) { #[bench] fn bench_eval_nested_if(bench: &mut Bencher) { - let script = r#" + let script = " let sum = 0; let rem = 0; @@ -209,7 +209,7 @@ fn bench_eval_nested_if(bench: &mut Bencher) { else if rem == 8 { 42 } else if rem == 9 { 1 }; } - "#; + "; let mut engine = Engine::new(); engine.set_optimization_level(OptimizationLevel::None); diff --git a/benches/eval_module.rs b/benches/eval_module.rs index 3bb4e04e..de7843c0 100644 --- a/benches/eval_module.rs +++ b/benches/eval_module.rs @@ -8,10 +8,10 @@ use test::Bencher; #[bench] fn bench_eval_module(bench: &mut Bencher) { - let script = r#" + let script = " fn foo(x) { x + 1 } fn bar(x) { foo(x) } - "#; + "; let mut engine = Engine::new(); engine.set_optimization_level(OptimizationLevel::None); @@ -24,10 +24,10 @@ fn bench_eval_module(bench: &mut Bencher) { let ast = engine .compile( - r#" + " fn foo(x) { x - 1 } testing::bar(41) - "#, + ", ) .unwrap(); @@ -41,11 +41,11 @@ fn bench_eval_function_call(bench: &mut Bencher) { let ast = engine .compile( - r#" + " fn foo(x) { x - 1 } fn bar(x) { foo(x) } bar(41) - "#, + ", ) .unwrap(); diff --git a/benches/iterations.rs b/benches/iterations.rs index b322a95d..33ac414f 100644 --- a/benches/iterations.rs +++ b/benches/iterations.rs @@ -8,13 +8,13 @@ use test::Bencher; #[bench] fn bench_iterations_1000(bench: &mut Bencher) { - let script = r#" + let script = " let x = 1_000; while x > 0 { x -= 1; } - "#; + "; let mut engine = Engine::new(); engine.set_optimization_level(OptimizationLevel::None); @@ -26,7 +26,7 @@ fn bench_iterations_1000(bench: &mut Bencher) { #[bench] fn bench_iterations_fibonacci(bench: &mut Bencher) { - let script = r#" + let script = " fn fibonacci(n) { if n < 2 { n @@ -36,7 +36,7 @@ fn bench_iterations_fibonacci(bench: &mut Bencher) { } fibonacci(20) - "#; + "; let mut engine = Engine::new(); engine.set_optimization_level(OptimizationLevel::None); @@ -48,11 +48,11 @@ fn bench_iterations_fibonacci(bench: &mut Bencher) { #[bench] fn bench_iterations_array(bench: &mut Bencher) { - let script = r#" + let script = " let x = []; x.pad(1000, 0); for i in 0..1000 { x[i] = i % 256; } - "#; + "; let mut engine = Engine::new(); engine.set_optimization_level(OptimizationLevel::None); @@ -64,10 +64,10 @@ fn bench_iterations_array(bench: &mut Bencher) { #[bench] fn bench_iterations_blob(bench: &mut Bencher) { - let script = r#" + let script = " let x = blob(1000, 0); for i in 0..1000 { x[i] = i % 256; } - "#; + "; let mut engine = Engine::new(); engine.set_optimization_level(OptimizationLevel::None); diff --git a/benches/parsing.rs b/benches/parsing.rs index 907a7c71..d45b2252 100644 --- a/benches/parsing.rs +++ b/benches/parsing.rs @@ -64,7 +64,7 @@ fn bench_parse_map(bench: &mut Bencher) { #[bench] fn bench_parse_primes(bench: &mut Bencher) { - let script = r#" + let script = " // This script uses the Sieve of Eratosthenes to calculate prime numbers. let now = timestamp(); @@ -79,7 +79,7 @@ fn bench_parse_primes(bench: &mut Bencher) { let total_primes_found = 0; - for p in 2..=MAX_NUMBER_TO_CHECK { + for p in 2..MAX_NUMBER_TO_CHECK { if prime_mask[p] { print(p); @@ -95,7 +95,7 @@ fn bench_parse_primes(bench: &mut Bencher) { print(`Total ${total_primes_found} primes.`); print(`Run time = ${now.elapsed} seconds.`); - "#; + "; let mut engine = Engine::new(); engine.set_optimization_level(OptimizationLevel::None); diff --git a/benches/primes.rs b/benches/primes.rs index 8110c4f7..40c047a0 100644 --- a/benches/primes.rs +++ b/benches/primes.rs @@ -8,29 +8,29 @@ use test::Bencher; // This script uses the Sieve of Eratosthenes to calculate prime numbers. -const SCRIPT: &str = r#" -const MAX_NUMBER_TO_CHECK = 1_000; // 168 primes <= 1000 +const SCRIPT: &str = " + const MAX_NUMBER_TO_CHECK = 1_000; // 168 primes <= 1000 -let prime_mask = []; -prime_mask.pad(MAX_NUMBER_TO_CHECK, true); + let prime_mask = []; + prime_mask.pad(MAX_NUMBER_TO_CHECK, true); -prime_mask[0] = false; -prime_mask[1] = false; + prime_mask[0] = false; + prime_mask[1] = false; -let total_primes_found = 0; + let total_primes_found = 0; -for p in 2..=MAX_NUMBER_TO_CHECK { - if prime_mask[p] { - total_primes_found += 1; - let i = 2 * p; + for p in 2..MAX_NUMBER_TO_CHECK { + if prime_mask[p] { + total_primes_found += 1; + let i = 2 * p; - while i < MAX_NUMBER_TO_CHECK { - prime_mask[i] = false; - i += p; + while i < MAX_NUMBER_TO_CHECK { + prime_mask[i] = false; + i += p; + } } } -} -"#; +"; #[bench] fn bench_eval_primes(bench: &mut Bencher) { diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 472c5ca1..8c471813 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai_codegen" -version = "1.2.0" +version = "1.3.0" edition = "2018" authors = ["jhwgh1968", "Stephen Chung"] description = "Procedural macros support package for Rhai, a scripting language and engine for Rust" @@ -16,7 +16,7 @@ default = [] metadata = [] [dev-dependencies] -rhai = { path = "..", version = "1.1" } +rhai = { path = "..", version = "1.4" } trybuild = "1" [dependencies] diff --git a/codegen/src/attrs.rs b/codegen/src/attrs.rs index 977e73de..0997430b 100644 --- a/codegen/src/attrs.rs +++ b/codegen/src/attrs.rs @@ -117,18 +117,64 @@ pub fn inner_item_attributes( attrs: &mut Vec, attr_name: &str, ) -> syn::Result { - // Find the #[rhai_fn] attribute which will turn be read for the function parameters. - if let Some(rhai_fn_idx) = attrs + // Find the #[rhai_fn] attribute which will turn be read for function parameters. + if let Some(index) = attrs .iter() .position(|a| a.path.get_ident().map(|i| *i == attr_name).unwrap_or(false)) { - let rhai_fn_attr = attrs.remove(rhai_fn_idx); + let rhai_fn_attr = attrs.remove(index); + + // Cannot have more than one #[rhai_fn] + if let Some(duplicate) = attrs + .iter() + .find(|a| a.path.get_ident().map(|i| *i == attr_name).unwrap_or(false)) + { + return Err(syn::Error::new( + duplicate.span(), + format!("duplicated attribute '{}'", attr_name), + )); + } + rhai_fn_attr.parse_args_with(T::parse_stream) } else { Ok(T::no_attrs()) } } +#[cfg(feature = "metadata")] +pub fn doc_attributes(attrs: &mut Vec) -> syn::Result> { + // Find the #[doc] attribute which will turn be read for function documentation. + let mut comments = Vec::new(); + + while let Some(index) = attrs + .iter() + .position(|attr| attr.path.get_ident().map(|i| *i == "doc").unwrap_or(false)) + { + match attrs.remove(index).parse_meta()? { + syn::Meta::NameValue(syn::MetaNameValue { + lit: syn::Lit::Str(s), + .. + }) => { + let mut line = s.value(); + + if line.contains('\n') { + // Must be a block comment `/** ... */` + line.insert_str(0, "/**"); + line.push_str("*/"); + } else { + // Single line - assume it is `///` + line.insert_str(0, "///"); + } + + comments.push(line); + } + _ => continue, + } + } + + Ok(comments) +} + pub fn collect_cfg_attr(attrs: &[syn::Attribute]) -> Vec { attrs .iter() diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 5e70c8c8..29e2a2cf 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -282,6 +282,8 @@ pub struct ExportedFn { mut_receiver: bool, params: ExportedFnParams, cfg_attrs: Vec, + #[cfg(feature = "metadata")] + comments: Vec, } impl Parse for ExportedFn { @@ -404,6 +406,8 @@ impl Parse for ExportedFn { mut_receiver, params: Default::default(), cfg_attrs, + #[cfg(feature = "metadata")] + comments: Default::default(), }) } } @@ -503,6 +507,16 @@ impl ExportedFn { } } + #[cfg(feature = "metadata")] + pub fn comments(&self) -> &[String] { + &self.comments + } + + #[cfg(feature = "metadata")] + pub fn set_comments(&mut self, comments: Vec) { + self.comments = comments + } + pub fn set_cfg_attrs(&mut self, cfg_attrs: Vec) { self.cfg_attrs = cfg_attrs } diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 75099218..14290c59 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -117,18 +117,22 @@ impl Parse for Module { syn::Item::Fn(f) => Some(f), _ => None, }) - .try_fold(Vec::new(), |mut vec, item_fn| { + .try_fold(Vec::new(), |mut vec, item_fn| -> syn::Result<_> { let params = crate::attrs::inner_item_attributes(&mut item_fn.attrs, "rhai_fn")?; - syn::parse2::(item_fn.to_token_stream()) - .and_then(|mut f| { + let f = + syn::parse2(item_fn.to_token_stream()).and_then(|mut f: ExportedFn| { f.set_params(params)?; f.set_cfg_attrs(crate::attrs::collect_cfg_attr(&item_fn.attrs)); + + #[cfg(feature = "metadata")] + f.set_comments(crate::attrs::doc_attributes(&mut item_fn.attrs)?); Ok(f) - }) - .map(|f| vec.push(f)) - .map(|_| vec) + })?; + + vec.push(f); + Ok(vec) })?; // Gather and parse constants definitions. for item in content.iter() { diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index a16eab56..01bacf62 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -166,20 +166,32 @@ pub fn generate_body( ); #[cfg(feature = "metadata")] - let param_names = quote! { - Some(#fn_token_name::PARAM_NAMES) - }; + let (param_names, comments) = ( + quote! { Some(#fn_token_name::PARAM_NAMES) }, + function + .comments() + .iter() + .map(|s| syn::LitStr::new(s, Span::call_site())) + .collect::>(), + ); #[cfg(not(feature = "metadata"))] - let param_names = quote! { None }; + let (param_names, comments) = (quote! { None }, Vec::::new()); - set_fn_statements.push( + set_fn_statements.push(if comments.is_empty() { syn::parse2::(quote! { #(#cfg_attrs)* m.set_fn(#fn_literal, FnNamespace::#ns_str, FnAccess::Public, #param_names, &[#(#fn_input_types),*], #fn_token_name().into()); }) - .unwrap(), - ); + .unwrap() + } else { + syn::parse2::(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()); + }) + .unwrap() + }); } gen_fn_tokens.push(quote! { diff --git a/codegen/src/test/module.rs b/codegen/src/test/module.rs index 3ada9401..4ba006f7 100644 --- a/codegen/src/test/module.rs +++ b/codegen/src/test/module.rs @@ -37,6 +37,49 @@ mod module_tests { ); } + #[test] + fn one_factory_fn_with_comments_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + /// This is a doc-comment. + /// Another line. + /** block doc-comment */ + // Regular comment + /// Final line. + /** doc-comment + in multiple lines + */ + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.consts().is_empty()); + assert_eq!(item_mod.fns().len(), 1); + assert_eq!(item_mod.fns()[0].name().to_string(), "get_mystic_number"); + assert_eq!( + item_mod.fns()[0] + .comments() + .iter() + .cloned() + .collect::>(), + vec![ + "/// This is a doc-comment.", + "/// Another line.", + "/// block doc-comment ", + "/// Final line.", + "/** doc-comment\n in multiple lines\n */" + ] + ); + assert_eq!(item_mod.fns()[0].arg_count(), 0); + assert_eq!( + item_mod.fns()[0].return_type().unwrap(), + &syn::parse2::(quote! { INT }).unwrap() + ); + } + #[test] fn one_single_arg_fn_module() { let input_tokens: TokenStream = quote! { @@ -323,6 +366,66 @@ mod generate_tests { assert_streams_eq(item_mod.generate(), expected_tokens); } + #[test] + fn one_factory_fn_with_comments_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + /// This is a doc-comment. + /// Another line. + /** block doc-comment */ + // Regular comment + /// Final line. + /** doc-comment + in multiple lines + */ + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + #[allow(unused_imports)] + use super::*; + + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m.build_index(); + m + } + #[allow(unused_mut)] + 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.","/** doc-comment\n in multiple lines\n */"], + get_mystic_number_token().into()); + if flatten {} else {} + } + #[allow(non_camel_case_types)] + pub struct get_mystic_number_token(); + impl get_mystic_number_token { + pub const PARAM_NAMES: &'static [&'static str] = &["INT"]; + #[inline(always)] pub fn param_types() -> [TypeId; 0usize] { [] } + } + impl PluginFunction for get_mystic_number_token { + #[inline(always)] + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { + Ok(Dynamic::from(get_mystic_number())) + } + + #[inline(always)] fn is_method_call(&self) -> bool { false } + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + #[test] fn one_single_arg_global_fn_module() { let input_tokens: TokenStream = quote! { diff --git a/codegen/ui_tests/rhai_fn_duplicate_attr.rs b/codegen/ui_tests/rhai_fn_duplicate_attr.rs new file mode 100644 index 00000000..b775fe4d --- /dev/null +++ b/codegen/ui_tests/rhai_fn_duplicate_attr.rs @@ -0,0 +1,18 @@ +use rhai::plugin::*; + +#[export_module] +pub mod test_module { + #[rhai_fn(name = "test")] + #[rhai_fn(pure)] + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } +} + +fn main() { + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_duplicate_attr.stderr b/codegen/ui_tests/rhai_fn_duplicate_attr.stderr new file mode 100644 index 00000000..983e5d1d --- /dev/null +++ b/codegen/ui_tests/rhai_fn_duplicate_attr.stderr @@ -0,0 +1,17 @@ +error: duplicated attribute 'rhai_fn' + --> ui_tests/rhai_fn_duplicate_attr.rs:6:5 + | +6 | #[rhai_fn(pure)] + | ^^^^^^^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared crate or module `test_module` + --> ui_tests/rhai_fn_duplicate_attr.rs:13:8 + | +13 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared crate or module `test_module` + +error[E0425]: cannot find value `n` in this scope + --> ui_tests/rhai_fn_duplicate_attr.rs:13:29 + | +13 | if test_module::test_fn(n) { + | ^ not found in this scope diff --git a/codegen/ui_tests/rhai_fn_non_clonable_return.stderr b/codegen/ui_tests/rhai_fn_non_clonable_return.stderr index ba165394..9d946726 100644 --- a/codegen/ui_tests/rhai_fn_non_clonable_return.stderr +++ b/codegen/ui_tests/rhai_fn_non_clonable_return.stderr @@ -10,5 +10,5 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied note: required by a bound in `rhai::Dynamic::from` --> $WORKSPACE/src/types/dynamic.rs | - | pub fn from(mut value: T) -> Self { + | pub fn from(value: T) -> Self { | ^^^^^ required by this bound in `rhai::Dynamic::from` diff --git a/codegen/ui_tests/rhai_mod_non_clonable_return.stderr b/codegen/ui_tests/rhai_mod_non_clonable_return.stderr index 8cd59893..1d664c65 100644 --- a/codegen/ui_tests/rhai_mod_non_clonable_return.stderr +++ b/codegen/ui_tests/rhai_mod_non_clonable_return.stderr @@ -10,5 +10,5 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied note: required by a bound in `rhai::Dynamic::from` --> $WORKSPACE/src/types/dynamic.rs | - | pub fn from(mut value: T) -> Self { + | pub fn from(value: T) -> Self { | ^^^^^ required by this bound in `rhai::Dynamic::from` diff --git a/scripts/doc-comments.rhai b/scripts/doc-comments.rhai new file mode 100644 index 00000000..9245065d --- /dev/null +++ b/scripts/doc-comments.rhai @@ -0,0 +1,20 @@ +/// The function `foo`, which prints `hello, world!` and a magic number, +/// accepts three parameters. +/// +/// # Parameters +/// +/// `x` - `i64` +/// `y` - `string` +/// `z` - `bool` +/// +/// # Notes +/// +/// This is a doc-comment. It can be obtained with the `metadata` feature. +/// +/// An example is the `rhai-doc` app. +/// +fn foo(x, y, z) { + print(`hello, world! ${if z { x + y.len() } else { x } }`); +} + +foo(39, "bar", true); diff --git a/scripts/for1.rhai b/scripts/for1.rhai index 1e215da5..4818b202 100644 --- a/scripts/for1.rhai +++ b/scripts/for1.rhai @@ -11,7 +11,7 @@ for (a, i) in arr { if a == 3 { break; } } -//print(a); // <- if you uncomment this line, the script will fail to run +//print(a); // <- if you uncomment this line, the script will fail to compile // because 'a' is not defined here for i in range(5, 0, -1) { // runs from 5 down to 1 diff --git a/scripts/for3.rhai b/scripts/for3.rhai new file mode 100644 index 00000000..de799684 --- /dev/null +++ b/scripts/for3.rhai @@ -0,0 +1,26 @@ +const MAX = 100; +const CHECK = ((MAX - 1) ** 2) * MAX; + +print("Ready... Go!"); + +let now = timestamp(); + +print(`Creating ${MAX} closures...`); + +let list = []; + +for i in 0..MAX { + list.push(|| i ** 2); +} + +print(`Time = ${now.elapsed} seconds...`); +print(`Summing ${MAX} closures...`); + +let sum = 0; + +for f in list { + sum += f.call(); +} + +print(`Sum = ${sum} (should be ${CHECK})`); +print(`Finished. Total run time = ${now.elapsed} seconds.`); diff --git a/scripts/switch.rhai b/scripts/switch.rhai index a252fa66..971ff989 100644 --- a/scripts/switch.rhai +++ b/scripts/switch.rhai @@ -1,11 +1,13 @@ -let arr = [42, 123.456, "hello", true, 'x', 999, 1]; +let arr = [42, 123.456, "hello", true, "hey", 'x', 999, 1, 2, 3, 4]; for item in arr { switch item { 42 => print("The Answer!"), 123.456 => print(`Floating point... ${item}`), "hello" => print(`${item} world!`), - 999 => print(`A number: ${item}`), - _ => print(`Something else: <${item}>`) + 999 => print(`Got 999: ${item}`), + 0..100 if item % 2 == 0 => print(`A small even number: ${item}`), + 0..100 => print(`A small odd number: ${item}`), + _ => print(`Something else: <${item}> is ${type_of(item)}`) } } diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index 6f72e1b7..28226949 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -1,10 +1,10 @@ //! Module that defines the `call_fn` API of [`Engine`]. #![cfg(not(feature = "no_function"))] -use crate::engine::{EvalState, Imports}; +use crate::engine::{EvalState, GlobalRuntimeState}; use crate::types::dynamic::Variant; use crate::{ - Dynamic, Engine, EvalAltResult, FuncArgs, Position, RhaiResult, Scope, StaticVec, AST, + Dynamic, Engine, FuncArgs, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, AST, ERR, }; use std::any::type_name; #[cfg(feature = "no_std")] @@ -60,7 +60,7 @@ impl Engine { ast: &AST, name: impl AsRef, args: impl FuncArgs, - ) -> Result> { + ) -> RhaiResultOf { let mut arg_values = StaticVec::new_const(); args.parse(&mut arg_values); @@ -69,7 +69,7 @@ impl Engine { let typ = self.map_type_name(result.type_name()); result.try_cast().ok_or_else(|| { - EvalAltResult::ErrorMismatchOutputType( + ERR::ErrorMismatchOutputType( self.map_type_name(type_name::()).into(), typ.into(), Position::NONE, @@ -90,10 +90,11 @@ impl Engine { /// /// This function is very low level. /// - /// ## Arguments + /// # Arguments /// /// All the arguments are _consumed_, meaning that they're replaced by `()`. /// This is to avoid unnecessarily cloning the arguments. + /// /// Do not use the arguments after this call. If they are needed afterwards, /// clone them _before_ calling this function. /// @@ -129,10 +130,10 @@ impl Engine { /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "bar", None, [])?; /// assert_eq!(result.cast::(), 21); /// - /// let mut value: Dynamic = 1_i64.into(); + /// let mut value = 1_i64.into(); /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "action", Some(&mut value), [ 41_i64.into() ])?; /// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer - /// assert_eq!(value.as_int().expect("value should be INT"), 42); + /// assert_eq!(value.as_int().unwrap(), 42); /// /// engine.call_fn_raw(&mut scope, &ast, true, false, "decl", None, [ 42_i64.into() ])?; /// // ^^^^^ do not rewind scope @@ -153,14 +154,13 @@ impl Engine { arg_values: impl AsMut<[Dynamic]>, ) -> RhaiResult { let state = &mut EvalState::new(); - let mods = &mut Imports::new(); + let global = &mut GlobalRuntimeState::new(); let statements = ast.statements(); let orig_scope_len = scope.len(); if eval_ast && !statements.is_empty() { - // Make sure new variables introduced at global level do not _spill_ into the function call - self.eval_global_statements(scope, mods, state, statements, &[ast.as_ref()], 0)?; + self.eval_global_statements(scope, global, state, statements, &[ast.as_ref()], 0)?; if rewind_scope { scope.rewind(orig_scope_len); @@ -175,7 +175,7 @@ impl Engine { let fn_def = ast .shared_lib() .get_script_fn(name, args.len()) - .ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::NONE))?; + .ok_or_else(|| ERR::ErrorFunctionNotFound(name.into(), Position::NONE))?; // Check for data race. #[cfg(not(feature = "no_closure"))] @@ -183,7 +183,7 @@ impl Engine { let result = self.call_script_fn( scope, - mods, + global, state, &[ast.as_ref()], &mut this_ptr, diff --git a/src/api/compile.rs b/src/api/compile.rs index 9ca748cd..d10ede04 100644 --- a/src/api/compile.rs +++ b/src/api/compile.rs @@ -1,7 +1,7 @@ //! Module that defines the public compilation API of [`Engine`]. -use crate::parser::ParseState; -use crate::{Engine, ParseError, Scope, AST}; +use crate::parser::{ParseResult, ParseState}; +use crate::{Engine, Scope, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -26,7 +26,7 @@ impl Engine { /// # } /// ``` #[inline(always)] - pub fn compile(&self, script: impl AsRef) -> Result { + pub fn compile(&self, script: impl AsRef) -> ParseResult { self.compile_with_scope(&Scope::new(), script) } /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation. @@ -67,11 +67,7 @@ impl Engine { /// # } /// ``` #[inline(always)] - pub fn compile_with_scope( - &self, - scope: &Scope, - script: impl AsRef, - ) -> Result { + pub fn compile_with_scope(&self, scope: &Scope, script: impl AsRef) -> ParseResult { self.compile_scripts_with_scope(scope, &[script]) } /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation, @@ -88,7 +84,7 @@ impl Engine { &self, scope: &Scope, script: impl AsRef, - ) -> Result> { + ) -> crate::RhaiResultOf { use crate::{ ast::{ASTNode, Expr, Stmt}, func::native::shared_take_or_clone, @@ -101,18 +97,16 @@ impl Engine { resolver: &StaticModuleResolver, imports: &mut BTreeSet, ) { - ast.walk( - &mut |path| match path.last().expect("contains current node") { - // Collect all `import` statements with a string constant path - ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, _), _, _)) - if !resolver.contains_path(s) && !imports.contains(s.as_str()) => - { - imports.insert(s.clone().into()); - true - } - _ => true, - }, - ); + ast.walk(&mut |path| match path.last().unwrap() { + // Collect all `import` statements with a string constant path + ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, _), _, _)) + if !resolver.contains_path(s) && !imports.contains(s.as_str()) => + { + imports.insert(s.clone().into()); + true + } + _ => true, + }); } let mut ast = self.compile_scripts_with_scope(scope, &[script])?; @@ -197,11 +191,11 @@ impl Engine { /// # } /// ``` #[inline(always)] - pub fn compile_scripts_with_scope( + pub fn compile_scripts_with_scope>( &self, scope: &Scope, - scripts: &[impl AsRef], - ) -> Result { + scripts: impl AsRef<[S]>, + ) -> ParseResult { self.compile_with_scope_and_optimization_level( scope, scripts, @@ -217,14 +211,16 @@ impl Engine { /// throughout the script _including_ functions. This allows functions to be optimized based on /// dynamic global constants. #[inline] - pub(crate) fn compile_with_scope_and_optimization_level( + pub(crate) fn compile_with_scope_and_optimization_level>( &self, scope: &Scope, - scripts: &[impl AsRef], + scripts: impl AsRef<[S]>, #[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel, - ) -> Result { - let (stream, tokenizer_control) = - self.lex_raw(scripts, self.token_mapper.as_ref().map(Box::as_ref)); + ) -> ParseResult { + let (stream, tokenizer_control) = self.lex_raw( + scripts.as_ref(), + self.token_mapper.as_ref().map(Box::as_ref), + ); let mut state = ParseState::new(self, tokenizer_control); self.parse( &mut stream.peekable(), @@ -255,7 +251,7 @@ impl Engine { /// # } /// ``` #[inline(always)] - pub fn compile_expression(&self, script: impl AsRef) -> Result { + pub fn compile_expression(&self, script: impl AsRef) -> ParseResult { self.compile_expression_with_scope(&Scope::new(), script) } /// Compile a string containing an expression into an [`AST`] using own scope, @@ -295,7 +291,7 @@ impl Engine { &self, scope: &Scope, script: impl AsRef, - ) -> Result { + ) -> ParseResult { let scripts = [script]; let (stream, tokenizer_control) = self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); @@ -359,14 +355,14 @@ impl Engine { &self, json: impl AsRef, has_null: bool, - ) -> Result> { + ) -> crate::RhaiResultOf { use crate::tokenizer::Token; fn parse_json_inner( engine: &Engine, json: &str, has_null: bool, - ) -> Result> { + ) -> crate::RhaiResultOf { let mut scope = Scope::new(); let json_text = json.trim_start(); let scripts = if json_text.starts_with(Token::MapStart.literal_syntax()) { @@ -374,7 +370,7 @@ impl Engine { } else if json_text.starts_with(Token::LeftBrace.literal_syntax()) { ["#", json_text] } else { - return Err(crate::ParseErrorType::MissingToken( + return Err(crate::PERR::MissingToken( Token::LeftBrace.syntax().into(), "to start a JSON object hash".into(), ) diff --git a/src/custom_syntax.rs b/src/api/custom_syntax.rs similarity index 90% rename from src/custom_syntax.rs rename to src/api/custom_syntax.rs index 56e96c5f..0558673d 100644 --- a/src/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -3,12 +3,12 @@ use crate::ast::Expr; use crate::engine::EvalContext; use crate::func::native::SendSync; +use crate::parser::ParseResult; use crate::r#unsafe::unsafe_try_cast; use crate::tokenizer::{is_valid_identifier, Token}; use crate::types::dynamic::Variant; use crate::{ - Engine, Identifier, ImmutableString, LexError, ParseError, Position, RhaiResult, Shared, - StaticVec, INT, + Engine, Identifier, ImmutableString, LexError, Position, RhaiResult, Shared, StaticVec, INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -47,11 +47,11 @@ pub type FnCustomSyntaxEval = dyn Fn(&mut EvalContext, &[Expression]) -> RhaiRes /// A general expression parsing trait object. #[cfg(not(feature = "sync"))] pub type FnCustomSyntaxParse = - dyn Fn(&[ImmutableString], &str) -> Result, ParseError>; + dyn Fn(&[ImmutableString], &str) -> ParseResult>; /// A general expression parsing trait object. #[cfg(feature = "sync")] pub type FnCustomSyntaxParse = - dyn Fn(&[ImmutableString], &str) -> Result, ParseError> + Send + Sync; + dyn Fn(&[ImmutableString], &str) -> ParseResult> + Send + Sync; /// An expression sub-tree in an [`AST`][crate::AST]. #[derive(Debug, Clone)] @@ -65,11 +65,17 @@ impl<'a> From<&'a Expr> for Expression<'a> { } impl Expression<'_> { - /// If this expression is a variable name, return it. Otherwise [`None`]. + /// Get the value of this expression if it is a variable name or a string constant. + /// + /// Returns [`None`] also if the constant is not of the specified type. #[inline(always)] #[must_use] - pub fn get_variable_name(&self) -> Option<&str> { - self.0.get_variable_name(true) + pub fn get_string_value(&self) -> Option<&str> { + match self.0 { + Expr::Variable(_, _, x) if x.1.is_none() => Some(x.2.as_str()), + Expr::StringConstant(x, _) => Some(x.as_str()), + _ => None, + } } /// Get the position of this expression. #[inline(always)] @@ -89,38 +95,39 @@ impl Expression<'_> { if TypeId::of::() == TypeId::of::() { return match self.0 { - Expr::IntegerConstant(x, _) => unsafe_try_cast(*x).ok(), + Expr::IntegerConstant(x, _) => unsafe_try_cast(*x), _ => None, }; } #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Expr::FloatConstant(x, _) => unsafe_try_cast(*x).ok(), + Expr::FloatConstant(x, _) => unsafe_try_cast(*x), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Expr::CharConstant(x, _) => unsafe_try_cast(*x).ok(), + Expr::CharConstant(x, _) => unsafe_try_cast(*x), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Expr::StringConstant(x, _) => unsafe_try_cast(x.clone()).ok(), + Expr::StringConstant(x, _) => unsafe_try_cast(x.clone()), + Expr::Variable(_, _, x) => unsafe_try_cast(Into::::into(&x.2)), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Expr::BoolConstant(x, _) => unsafe_try_cast(*x).ok(), + Expr::BoolConstant(x, _) => unsafe_try_cast(*x), _ => None, }; } if TypeId::of::() == TypeId::of::<()>() { return match self.0 { - Expr::Unit(_) => unsafe_try_cast(()).ok(), + Expr::Unit(_) => unsafe_try_cast(()), _ => None, }; } @@ -154,7 +161,7 @@ impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_> { pub fn eval_expression_tree(&mut self, expr: &Expression) -> RhaiResult { self.engine.eval_expr( self.scope, - self.mods, + self.global, self.state, self.lib, self.this_ptr, @@ -204,15 +211,15 @@ impl Engine { /// does NOT count, so `false` should be passed. pub fn register_custom_syntax + Into>( &mut self, - symbols: &[S], + symbols: impl AsRef<[S]>, scope_may_be_changed: bool, func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static, - ) -> Result<&mut Self, ParseError> { + ) -> ParseResult<&mut Self> { use markers::*; let mut segments = StaticVec::::new(); - for s in symbols { + for s in symbols.as_ref() { let s = s.as_ref().trim(); // Skip empty symbols @@ -344,11 +351,11 @@ impl Engine { /// /// * `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`]. + /// * `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]. pub fn register_custom_syntax_raw( &mut self, key: impl Into, - parse: impl Fn(&[ImmutableString], &str) -> Result, ParseError> + parse: impl Fn(&[ImmutableString], &str) -> ParseResult> + SendSync + 'static, scope_may_be_changed: bool, diff --git a/src/api/deprecated.rs b/src/api/deprecated.rs index b315e636..166daf4f 100644 --- a/src/api/deprecated.rs +++ b/src/api/deprecated.rs @@ -1,8 +1,8 @@ //! Module containing all deprecated API that will be removed in the next major version. use crate::{ - Dynamic, Engine, EvalAltResult, FnPtr, ImmutableString, NativeCallContext, RhaiResult, Scope, - AST, + Dynamic, Engine, EvalAltResult, Expression, FnPtr, ImmutableString, NativeCallContext, + RhaiResult, RhaiResultOf, Scope, AST, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -20,9 +20,10 @@ impl Engine { /// 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(any(target_arch = "wasm32", target_arch = "wasm64")))] + #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_arch = "wasm64"))] #[inline(always)] - pub fn consume_file(&self, path: std::path::PathBuf) -> Result<(), Box> { + pub fn consume_file(&self, path: std::path::PathBuf) -> RhaiResultOf<()> { self.run_file(path) } @@ -38,13 +39,14 @@ impl Engine { /// This method will be removed in the next major version. #[deprecated(since = "1.1.0", note = "use `run_file_with_scope` instead")] #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_arch = "wasm64"))] #[inline(always)] pub fn consume_file_with_scope( &self, scope: &mut Scope, path: std::path::PathBuf, - ) -> Result<(), Box> { + ) -> RhaiResultOf<()> { self.run_file_with_scope(scope, path) } @@ -58,7 +60,7 @@ impl Engine { /// This method will be removed in the next major version. #[deprecated(since = "1.1.0", note = "use `run` instead")] #[inline(always)] - pub fn consume(&self, script: &str) -> Result<(), Box> { + pub fn consume(&self, script: &str) -> RhaiResultOf<()> { self.run(script) } @@ -72,11 +74,7 @@ impl Engine { /// This method will be removed in the next major version. #[deprecated(since = "1.1.0", note = "use `run_with_scope` instead")] #[inline(always)] - pub fn consume_with_scope( - &self, - scope: &mut Scope, - script: &str, - ) -> Result<(), Box> { + pub fn consume_with_scope(&self, scope: &mut Scope, script: &str) -> RhaiResultOf<()> { self.run_with_scope(scope, script) } @@ -90,7 +88,7 @@ impl Engine { /// This method will be removed in the next major version. #[deprecated(since = "1.1.0", note = "use `run_ast` instead")] #[inline(always)] - pub fn consume_ast(&self, ast: &AST) -> Result<(), Box> { + pub fn consume_ast(&self, ast: &AST) -> RhaiResultOf<()> { self.run_ast(ast) } @@ -104,11 +102,7 @@ impl Engine { /// This method will be removed in the next major version. #[deprecated(since = "1.1.0", note = "use `run_ast_with_scope` instead")] #[inline(always)] - pub fn consume_ast_with_scope( - &self, - scope: &mut Scope, - ast: &AST, - ) -> Result<(), Box> { + pub fn consume_ast_with_scope(&self, scope: &mut Scope, ast: &AST) -> RhaiResultOf<()> { self.run_ast_with_scope(scope, ast) } /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments @@ -128,10 +122,11 @@ impl Engine { /// /// This function is very low level. /// - /// ## Arguments + /// # Arguments /// /// All the arguments are _consumed_, meaning that they're replaced by `()`. /// This is to avoid unnecessarily cloning the arguments. + /// /// Do not use the arguments after this call. If they are needed afterwards, /// clone them _before_ calling this function. /// @@ -228,7 +223,7 @@ impl NativeCallContext<'_> { /// /// This function is very low level. /// - /// ## Arguments + /// # Arguments /// /// All arguments may be _consumed_, meaning that they may be replaced by `()`. This is to avoid /// unnecessarily cloning the arguments. @@ -258,7 +253,7 @@ impl NativeCallContext<'_> { #[allow(useless_deprecated)] #[deprecated(since = "1.2.0", note = "explicitly wrap `EvalAltResult` in `Err`")] -impl From for Result> { +impl From for RhaiResultOf { #[inline(always)] fn from(err: EvalAltResult) -> Self { Err(err.into()) @@ -284,10 +279,11 @@ impl FnPtr { /// /// This function is very low level. /// - /// ## Arguments + /// # Arguments /// /// All the arguments are _consumed_, meaning that they're replaced by `()`. /// This is to avoid unnecessarily cloning the arguments. + /// /// Do not use the arguments after this call. If they are needed afterwards, /// clone them _before_ calling this function. #[deprecated( @@ -304,3 +300,19 @@ impl FnPtr { self.call_raw(context, this_ptr, arg_values) } } + +impl Expression<'_> { + /// If this expression is a variable name, return it. Otherwise [`None`]. + /// + /// # Deprecated + /// + /// This method is deprecated. Use [`get_string_value`][Expression::get_string_value] instead. + /// + /// This method will be removed in the next major version. + #[deprecated(since = "1.4.0", note = "use `get_string_value` instead")] + #[inline(always)] + #[must_use] + pub fn get_variable_name(&self) -> Option<&str> { + self.get_string_value() + } +} diff --git a/src/api/eval.rs b/src/api/eval.rs index 6356c1e8..402b696b 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -1,9 +1,9 @@ //! Module that defines the public evaluation API of [`Engine`]. -use crate::engine::{EvalState, Imports}; +use crate::engine::{EvalState, GlobalRuntimeState}; use crate::parser::ParseState; use crate::types::dynamic::Variant; -use crate::{Dynamic, Engine, EvalAltResult, Module, Position, RhaiResult, Scope, AST}; +use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR}; use std::any::type_name; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -24,7 +24,7 @@ impl Engine { /// # } /// ``` #[inline(always)] - pub fn eval(&self, script: &str) -> Result> { + pub fn eval(&self, script: &str) -> RhaiResultOf { self.eval_with_scope(&mut Scope::new(), script) } /// Evaluate a string with own scope. @@ -60,7 +60,7 @@ impl Engine { &self, scope: &mut Scope, script: &str, - ) -> Result> { + ) -> RhaiResultOf { let ast = self.compile_with_scope_and_optimization_level( scope, &[script], @@ -84,10 +84,7 @@ impl Engine { /// # } /// ``` #[inline(always)] - pub fn eval_expression( - &self, - script: &str, - ) -> Result> { + pub fn eval_expression(&self, script: &str) -> RhaiResultOf { self.eval_expression_with_scope(&mut Scope::new(), script) } /// Evaluate a string containing an expression with own scope. @@ -113,7 +110,7 @@ impl Engine { &self, scope: &mut Scope, script: &str, - ) -> Result> { + ) -> RhaiResultOf { let scripts = [script]; let (stream, tokenizer_control) = self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); @@ -149,7 +146,7 @@ impl Engine { /// # } /// ``` #[inline(always)] - pub fn eval_ast(&self, ast: &AST) -> Result> { + pub fn eval_ast(&self, ast: &AST) -> RhaiResultOf { self.eval_ast_with_scope(&mut Scope::new(), ast) } /// Evaluate an [`AST`] with own scope. @@ -186,15 +183,15 @@ impl Engine { &self, scope: &mut Scope, ast: &AST, - ) -> Result> { - let mods = &mut Imports::new(); + ) -> RhaiResultOf { + let global = &mut GlobalRuntimeState::new(); - let result = self.eval_ast_with_scope_raw(scope, mods, ast, 0)?; + let result = self.eval_ast_with_scope_raw(scope, global, ast, 0)?; let typ = self.map_type_name(result.type_name()); result.try_cast::().ok_or_else(|| { - EvalAltResult::ErrorMismatchOutputType( + ERR::ErrorMismatchOutputType( self.map_type_name(type_name::()).into(), typ.into(), Position::NONE, @@ -207,17 +204,16 @@ impl Engine { pub(crate) fn eval_ast_with_scope_raw<'a>( &self, scope: &mut Scope, - mods: &mut Imports, + global: &mut GlobalRuntimeState, ast: &'a AST, level: usize, ) -> RhaiResult { let mut state = EvalState::new(); - if ast.source_raw().is_some() { - mods.source = ast.source_raw().cloned(); - } + global.source = ast.source_raw().clone(); + #[cfg(not(feature = "no_module"))] { - mods.embedded_module_resolver = ast.resolver().cloned(); + global.embedded_module_resolver = ast.resolver().cloned(); } let statements = ast.statements(); @@ -235,6 +231,6 @@ impl Engine { } else { &lib }; - self.eval_global_statements(scope, mods, &mut state, statements, lib, level) + self.eval_global_statements(scope, global, &mut state, statements, lib, level) } } diff --git a/src/api/events.rs b/src/api/events.rs index 9f981836..e43d4e4a 100644 --- a/src/api/events.rs +++ b/src/api/events.rs @@ -2,7 +2,7 @@ use crate::engine::EvalContext; use crate::func::SendSync; -use crate::{Dynamic, Engine, EvalAltResult, Position}; +use crate::{Dynamic, Engine, Position, RhaiResultOf}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -58,7 +58,7 @@ impl Engine { #[inline(always)] pub fn on_var( &mut self, - callback: impl Fn(&str, usize, &EvalContext) -> Result, Box> + callback: impl Fn(&str, usize, &EvalContext) -> RhaiResultOf> + SendSync + 'static, ) -> &mut Self { diff --git a/src/api/files.rs b/src/api/files.rs index 785bffbc..82267f01 100644 --- a/src/api/files.rs +++ b/src/api/files.rs @@ -1,19 +1,20 @@ //! Module that defines the public file-based API of [`Engine`]. #![cfg(not(feature = "no_std"))] -#![cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +#![cfg(not(target_arch = "wasm32"))] +#![cfg(not(target_arch = "wasm64"))] use crate::types::dynamic::Variant; -use crate::{Engine, EvalAltResult, Scope, AST}; +use crate::{Engine, RhaiResultOf, Scope, AST, ERR}; #[cfg(feature = "no_std")] use std::prelude::v1::*; impl Engine { /// Read the contents of a file into a string. - fn read_file(path: std::path::PathBuf) -> Result> { + fn read_file(path: std::path::PathBuf) -> RhaiResultOf { use std::io::Read; let mut f = std::fs::File::open(path.clone()).map_err(|err| { - EvalAltResult::ErrorSystem( + ERR::ErrorSystem( format!("Cannot open script file '{}'", path.to_string_lossy()), err.into(), ) @@ -22,7 +23,7 @@ impl Engine { let mut contents = String::new(); f.read_to_string(&mut contents).map_err(|err| { - EvalAltResult::ErrorSystem( + ERR::ErrorSystem( format!("Cannot read script file '{}'", path.to_string_lossy()), err.into(), ) @@ -62,7 +63,7 @@ impl Engine { /// # } /// ``` #[inline(always)] - pub fn compile_file(&self, path: std::path::PathBuf) -> Result> { + pub fn compile_file(&self, path: std::path::PathBuf) -> RhaiResultOf { self.compile_file_with_scope(&Scope::new(), path) } /// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation. @@ -103,7 +104,7 @@ impl Engine { &self, scope: &Scope, path: std::path::PathBuf, - ) -> Result> { + ) -> RhaiResultOf { Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?)) } /// Evaluate a script file. @@ -124,10 +125,7 @@ impl Engine { /// # } /// ``` #[inline] - pub fn eval_file( - &self, - path: std::path::PathBuf, - ) -> Result> { + pub fn eval_file(&self, path: std::path::PathBuf) -> RhaiResultOf { Self::read_file(path).and_then(|contents| self.eval::(&contents)) } /// Evaluate a script file with own scope. @@ -162,16 +160,14 @@ impl Engine { &self, scope: &mut Scope, path: std::path::PathBuf, - ) -> Result> { + ) -> RhaiResultOf { Self::read_file(path).and_then(|contents| self.eval_with_scope(scope, &contents)) } /// Evaluate a file, returning any error (if any). /// /// Not available under `no_std` or `WASM`. - #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[inline] - pub fn run_file(&self, path: std::path::PathBuf) -> Result<(), Box> { + pub fn run_file(&self, path: std::path::PathBuf) -> RhaiResultOf<()> { Self::read_file(path).and_then(|contents| self.run(&contents)) } /// Evaluate a file with own scope, returning any error (if any). @@ -183,14 +179,12 @@ impl Engine { /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within /// the scope are propagated throughout the script _including_ functions. This allows functions /// to be optimized based on dynamic global constants. - #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[inline] pub fn run_file_with_scope( &self, scope: &mut Scope, path: std::path::PathBuf, - ) -> Result<(), Box> { + ) -> RhaiResultOf<()> { Self::read_file(path).and_then(|contents| self.run_with_scope(scope, &contents)) } } diff --git a/src/api/limits.rs b/src/api/limits.rs index 7d9fd36c..876a5f1b 100644 --- a/src/api/limits.rs +++ b/src/api/limits.rs @@ -1,6 +1,7 @@ //! Settings for [`Engine`]'s limitations. #![cfg(not(feature = "unchecked"))] +use super::default_limits; use crate::Engine; use std::num::{NonZeroU64, NonZeroUsize}; #[cfg(feature = "no_std")] @@ -56,10 +57,10 @@ impl Limits { pub const fn new() -> Self { Self { #[cfg(not(feature = "no_function"))] - max_call_stack_depth: crate::engine::MAX_CALL_STACK_DEPTH, - max_expr_depth: NonZeroUsize::new(crate::engine::MAX_EXPR_DEPTH), + max_call_stack_depth: default_limits::MAX_CALL_STACK_DEPTH, + max_expr_depth: NonZeroUsize::new(default_limits::MAX_EXPR_DEPTH), #[cfg(not(feature = "no_function"))] - max_function_expr_depth: NonZeroUsize::new(crate::engine::MAX_FUNCTION_EXPR_DEPTH), + max_function_expr_depth: NonZeroUsize::new(default_limits::MAX_FUNCTION_EXPR_DEPTH), max_operations: None, #[cfg(not(feature = "no_module"))] max_modules: usize::MAX, diff --git a/src/api/mod.rs b/src/api/mod.rs index a8a2a3c3..39045da7 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -18,6 +18,8 @@ pub mod limits; pub mod events; +pub mod custom_syntax; + pub mod deprecated; use crate::engine::Precedence; @@ -27,6 +29,34 @@ use crate::{Engine, Identifier}; #[cfg(feature = "no_std")] use std::prelude::v1::*; +pub mod default_limits { + #[cfg(not(feature = "unchecked"))] + #[cfg(debug_assertions)] + #[cfg(not(feature = "no_function"))] + pub const MAX_CALL_STACK_DEPTH: usize = 8; + #[cfg(not(feature = "unchecked"))] + #[cfg(debug_assertions)] + pub const MAX_EXPR_DEPTH: usize = 32; + #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "no_function"))] + #[cfg(debug_assertions)] + pub const MAX_FUNCTION_EXPR_DEPTH: usize = 16; + + #[cfg(not(feature = "unchecked"))] + #[cfg(not(debug_assertions))] + #[cfg(not(feature = "no_function"))] + pub const MAX_CALL_STACK_DEPTH: usize = 64; + #[cfg(not(feature = "unchecked"))] + #[cfg(not(debug_assertions))] + pub const MAX_EXPR_DEPTH: usize = 64; + #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "no_function"))] + #[cfg(not(debug_assertions))] + pub const MAX_FUNCTION_EXPR_DEPTH: usize = 32; + + pub const MAX_DYNAMIC_PARAMETERS: usize = 16; +} + /// Script optimization API. #[cfg(not(feature = "no_optimize"))] impl Engine { @@ -83,17 +113,15 @@ impl Engine { .map(|f| { f.func .get_script_fn_def() - .expect("scripted function") + .expect("script-defined function") .clone() }) .collect(); - let statements = std::mem::take(ast.statements_mut()); - crate::optimizer::optimize_into_ast( self, scope, - statements, + ast.take_statements(), #[cfg(not(feature = "no_function"))] lib, optimization_level, @@ -185,7 +213,7 @@ impl Engine { /// ``` pub fn register_custom_operator( &mut self, - keyword: impl AsRef + Into, + keyword: impl AsRef, precedence: u8, ) -> Result<&mut Self, String> { let precedence = Precedence::new(precedence); @@ -219,7 +247,8 @@ impl Engine { } // Add to custom keywords - self.custom_keywords.insert(keyword.into(), precedence); + self.custom_keywords + .insert(keyword.as_ref().into(), precedence); Ok(self) } diff --git a/src/api/register.rs b/src/api/register.rs index 32c929d7..71ef20e9 100644 --- a/src/api/register.rs +++ b/src/api/register.rs @@ -3,8 +3,7 @@ use crate::func::{FnCallArgs, RegisterNativeFunction, SendSync}; use crate::types::dynamic::Variant; use crate::{ - Engine, EvalAltResult, FnAccess, FnNamespace, Identifier, Module, NativeCallContext, Shared, - SmartString, + Engine, FnAccess, FnNamespace, Identifier, Module, NativeCallContext, RhaiResultOf, Shared, }; use std::any::{type_name, TypeId}; #[cfg(feature = "no_std")] @@ -15,13 +14,14 @@ impl Engine { #[inline(always)] #[allow(dead_code)] pub(crate) fn global_namespace(&self) -> &Module { - self.global_modules.first().expect("not empty") + self.global_modules.first().unwrap() } /// Get a mutable reference to the global namespace module /// (which is the first module in `global_modules`). #[inline(always)] pub(crate) fn global_namespace_mut(&mut self) -> &mut Module { - Shared::get_mut(self.global_modules.first_mut().expect("not empty")).expect("not shared") + let module = self.global_modules.first_mut().unwrap(); + Shared::get_mut(module).expect("not shared") } /// Register a custom function with the [`Engine`]. /// @@ -69,18 +69,20 @@ impl Engine { } #[cfg(feature = "metadata")] - let param_type_names: Option> = - Some(param_type_names.iter().map(|ty| ty.as_str()).collect()); + let param_type_names: crate::StaticVec<_> = + param_type_names.iter().map(|ty| ty.as_str()).collect(); + #[cfg(feature = "metadata")] + let param_type_names = Some(param_type_names.as_ref()); #[cfg(not(feature = "metadata"))] - let param_type_names: Option<[&str; 0]> = None; + let param_type_names: Option<&[&str]> = None; self.global_namespace_mut().set_fn( name, FnNamespace::Global, FnAccess::Public, - param_type_names.as_ref().map(|v| v.as_ref()), - ¶m_types, + param_type_names, + param_types, func.into_callable_function(), ); self @@ -113,7 +115,7 @@ impl Engine { pub fn register_result_fn(&mut self, name: N, func: F) -> &mut Self where N: AsRef + Into, - F: RegisterNativeFunction>>, + F: RegisterNativeFunction>, { let param_types = F::param_types(); @@ -127,18 +129,20 @@ impl Engine { .collect(); #[cfg(feature = "metadata")] - let param_type_names: Option> = - Some(param_type_names.iter().map(|ty| ty.as_str()).collect()); + let param_type_names: crate::StaticVec<_> = + param_type_names.iter().map(|ty| ty.as_str()).collect(); + #[cfg(feature = "metadata")] + let param_type_names = Some(param_type_names.as_ref()); #[cfg(not(feature = "metadata"))] - let param_type_names: Option<[&str; 0]> = None; + let param_type_names: Option<&[&str]> = None; self.global_namespace_mut().set_fn( name, FnNamespace::Global, FnAccess::Public, - param_type_names.as_ref().map(|v| v.as_ref()), - ¶m_types, + param_type_names, + param_types, func.into_callable_function(), ); self @@ -150,7 +154,7 @@ impl Engine { /// This function is very low level. It takes a list of [`TypeId`][std::any::TypeId]'s /// indicating the actual types of the parameters. /// - /// ## Arguments + /// # Arguments /// /// Arguments are simply passed in as a mutable array of [`&mut Dynamic`][crate::Dynamic]. /// The arguments are guaranteed to be of the correct types matching the [`TypeId`][std::any::TypeId]'s. @@ -165,10 +169,8 @@ impl Engine { pub fn register_raw_fn( &mut self, name: N, - arg_types: &[TypeId], - func: impl Fn(NativeCallContext, &mut FnCallArgs) -> Result> - + SendSync - + 'static, + arg_types: impl AsRef<[TypeId]>, + func: impl Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResultOf + SendSync + 'static, ) -> &mut Self where N: AsRef + Into, @@ -195,8 +197,12 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } - /// fn update(&mut self, offset: i64) { self.field += offset; } + /// fn new() -> Self { + /// Self { field: 1 } + /// } + /// fn update(&mut self, offset: i64) { + /// self.field += offset; + /// } /// } /// /// # fn main() -> Result<(), Box> { @@ -235,7 +241,9 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } + /// fn new() -> Self { + /// Self { field: 1 } + /// } /// } /// /// # fn main() -> Result<(), Box> { @@ -276,8 +284,8 @@ impl Engine { #[inline(always)] pub fn register_type_with_name_raw( &mut self, - fully_qualified_type_path: impl Into, - name: impl Into, + fully_qualified_type_path: impl Into, + name: impl Into, ) -> &mut Self { // Add the pretty-print type name into the map self.type_names @@ -310,9 +318,13 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } + /// fn new() -> Self { + /// Self { field: 1 } + /// } /// // Even a getter must start with `&mut self` and not `&self`. - /// fn get_field(&mut self) -> i64 { self.field } + /// fn get_field(&mut self) -> i64 { + /// self.field + /// } /// } /// /// # fn main() -> Result<(), Box> { @@ -338,7 +350,7 @@ impl Engine { name: impl AsRef, get_fn: impl Fn(&mut T) -> V + SendSync + 'static, ) -> &mut Self { - self.register_fn(&crate::engine::make_getter(name), get_fn) + self.register_fn(crate::engine::make_getter(name.as_ref()).as_str(), get_fn) } /// Register a getter function for a member of a registered type with the [`Engine`]. /// @@ -357,7 +369,9 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } + /// fn new() -> Self { + /// Self { field: 1 } + /// } /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self) -> Result> { /// Ok(self.field) @@ -383,9 +397,9 @@ impl Engine { pub fn register_get_result( &mut self, name: impl AsRef, - get_fn: impl Fn(&mut T) -> Result> + SendSync + 'static, + get_fn: impl Fn(&mut T) -> RhaiResultOf + SendSync + 'static, ) -> &mut Self { - self.register_result_fn(&crate::engine::make_getter(name), get_fn) + self.register_result_fn(crate::engine::make_getter(name.as_ref()).as_str(), get_fn) } /// Register a setter function for a member of a registered type with the [`Engine`]. /// @@ -400,8 +414,12 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } - /// fn set_field(&mut self, new_val: i64) { self.field = new_val; } + /// fn new() -> Self { + /// Self { field: 1 } + /// } + /// fn set_field(&mut self, new_val: i64) { + /// self.field = new_val; + /// } /// } /// /// # fn main() -> Result<(), Box> { @@ -431,7 +449,7 @@ impl Engine { name: impl AsRef, set_fn: impl Fn(&mut T, V) + SendSync + 'static, ) -> &mut Self { - self.register_fn(&crate::engine::make_setter(name), set_fn) + self.register_fn(crate::engine::make_setter(name.as_ref()).as_str(), set_fn) } /// Register a setter function for a member of a registered type with the [`Engine`]. /// @@ -448,8 +466,10 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } - /// fn set_field(&mut self, new_val: i64) -> Result<(), Box> { + /// fn new() -> Self { + /// Self { field: 1 } + /// } + /// fn set_field(&mut self, new_val: i64) -> Result<(), Box> { /// self.field = new_val; /// Ok(()) /// } @@ -478,9 +498,9 @@ impl Engine { pub fn register_set_result( &mut self, name: impl AsRef, - set_fn: impl Fn(&mut T, V) -> Result<(), Box> + SendSync + 'static, + set_fn: impl Fn(&mut T, V) -> RhaiResultOf<()> + SendSync + 'static, ) -> &mut Self { - self.register_result_fn(&crate::engine::make_setter(name), set_fn) + self.register_result_fn(crate::engine::make_setter(name.as_ref()).as_str(), set_fn) } /// Short-hand for registering both getter and setter functions /// of a registered type with the [`Engine`]. @@ -498,10 +518,16 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } + /// fn new() -> Self { + /// Self { field: 1 } + /// } /// // Even a getter must start with `&mut self` and not `&self`. - /// fn get_field(&mut self) -> i64 { self.field } - /// fn set_field(&mut self, new_val: i64) { self.field = new_val; } + /// fn get_field(&mut self) -> i64 { + /// self.field + /// } + /// fn set_field(&mut self, new_val: i64) { + /// self.field = new_val; + /// } /// } /// /// # fn main() -> Result<(), Box> { @@ -552,9 +578,13 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// fn new() -> Self { + /// Self { fields: vec![1, 2, 3, 4, 5] } + /// } /// // Even a getter must start with `&mut self` and not `&self`. - /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } + /// fn get_field(&mut self, index: i64) -> i64 { + /// self.fields[index as usize] + /// } /// } /// /// # fn main() -> Result<(), Box> { @@ -625,7 +655,9 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// fn new() -> Self { + /// Self { fields: vec![1, 2, 3, 4, 5] } + /// } /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self, index: i64) -> Result> { /// Ok(self.fields[index as usize]) @@ -657,7 +689,7 @@ impl Engine { V: Variant + Clone, >( &mut self, - get_fn: impl Fn(&mut T, X) -> Result> + SendSync + 'static, + get_fn: impl Fn(&mut T, X) -> RhaiResultOf + SendSync + 'static, ) -> &mut Self { #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { @@ -698,8 +730,12 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } - /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } + /// fn new() -> Self { + /// Self { fields: vec![1, 2, 3, 4, 5] } + /// } + /// fn set_field(&mut self, index: i64, value: i64) { + /// self.fields[index as usize] = value; + /// } /// } /// /// # fn main() -> Result<(), Box> { @@ -717,10 +753,10 @@ impl Engine { /// .register_indexer_set(TestStruct::set_field); /// /// # #[cfg(not(feature = "no_index"))] - /// assert_eq!( - /// engine.eval::("let a = new_ts(); a[2] = 42; a")?.fields[2], - /// 42 - /// ); + /// let result = engine.eval::("let a = new_ts(); a[2] = 42; a")?; + /// + /// # #[cfg(not(feature = "no_index"))] + /// assert_eq!(result.fields[2], 42); /// # Ok(()) /// # } /// ``` @@ -771,8 +807,10 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } - /// fn set_field(&mut self, index: i64, value: i64) -> Result<(), Box> { + /// fn new() -> Self { + /// Self { fields: vec![1, 2, 3, 4, 5] } + /// } + /// fn set_field(&mut self, index: i64, value: i64) -> Result<(), Box> { /// self.fields[index as usize] = value; /// Ok(()) /// } @@ -791,10 +829,10 @@ impl Engine { /// .register_indexer_set_result(TestStruct::set_field); /// /// # #[cfg(not(feature = "no_index"))] - /// assert_eq!( - /// engine.eval::("let a = new_ts(); a[2] = 42; a")?.fields[2], - /// 42 - /// ); + /// let result = engine.eval::("let a = new_ts(); a[2] = 42; a")?; + /// + /// # #[cfg(not(feature = "no_index"))] + /// assert_eq!(result.fields[2], 42); /// # Ok(()) /// # } /// ``` @@ -806,7 +844,7 @@ impl Engine { V: Variant + Clone, >( &mut self, - set_fn: impl Fn(&mut T, X, V) -> Result<(), Box> + SendSync + 'static, + set_fn: impl Fn(&mut T, X, V) -> RhaiResultOf<()> + SendSync + 'static, ) -> &mut Self { #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { @@ -847,10 +885,16 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// fn new() -> Self { + /// Self { fields: vec![1, 2, 3, 4, 5] } + /// } /// // Even a getter must start with `&mut self` and not `&self`. - /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } - /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } + /// fn get_field(&mut self, index: i64) -> i64 { + /// self.fields[index as usize] + /// } + /// fn set_field(&mut self, index: i64, value: i64) { + /// self.fields[index as usize] = value; + /// } /// } /// /// # fn main() -> Result<(), Box> { @@ -935,17 +979,17 @@ impl Engine { #[cfg(not(feature = "no_module"))] pub fn register_static_module( &mut self, - name: impl AsRef + Into, + name: impl AsRef, module: Shared, ) -> &mut Self { fn register_static_module_raw( root: &mut std::collections::BTreeMap>, - name: impl AsRef + Into, + name: &str, module: Shared, ) { let separator = crate::tokenizer::Token::DoubleColon.syntax(); - if !name.as_ref().contains(separator.as_ref()) { + if !name.contains(separator.as_ref()) { if !module.is_indexed() { // Index the module (making a clone copy if necessary) if it is not indexed let mut module = crate::func::native::shared_take_or_clone(module); @@ -955,7 +999,7 @@ impl Engine { root.insert(name.into(), module); } } else { - let mut iter = name.as_ref().splitn(2, separator.as_ref()); + let mut iter = name.splitn(2, separator.as_ref()); let sub_module = iter.next().expect("contains separator").trim(); let remainder = iter.next().expect("contains separator").trim(); @@ -974,7 +1018,7 @@ impl Engine { } } - register_static_module_raw(&mut self.global_sub_modules, name, module); + register_static_module_raw(&mut self.global_sub_modules, name.as_ref(), module); self } /// _(metadata)_ Generate a list of all registered functions. diff --git a/src/api/run.rs b/src/api/run.rs index 84e2222d..ce33c049 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -1,15 +1,15 @@ //! Module that defines the public evaluation API of [`Engine`]. -use crate::engine::{EvalState, Imports}; +use crate::engine::{EvalState, GlobalRuntimeState}; use crate::parser::ParseState; -use crate::{Engine, EvalAltResult, Module, Scope, AST}; +use crate::{Engine, Module, RhaiResultOf, Scope, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; impl Engine { /// Evaluate a script, returning any error (if any). #[inline(always)] - pub fn run(&self, script: &str) -> Result<(), Box> { + pub fn run(&self, script: &str) -> RhaiResultOf<()> { self.run_with_scope(&mut Scope::new(), script) } /// Evaluate a script with own scope, returning any error (if any). @@ -20,11 +20,7 @@ impl Engine { /// the scope are propagated throughout the script _including_ functions. This allows functions /// to be optimized based on dynamic global constants. #[inline] - pub fn run_with_scope( - &self, - scope: &mut Scope, - script: &str, - ) -> Result<(), Box> { + pub fn run_with_scope(&self, scope: &mut Scope, script: &str) -> RhaiResultOf<()> { let scripts = [script]; let (stream, tokenizer_control) = self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); @@ -42,24 +38,19 @@ impl Engine { } /// Evaluate an [`AST`], returning any error (if any). #[inline(always)] - pub fn run_ast(&self, ast: &AST) -> Result<(), Box> { + pub fn run_ast(&self, ast: &AST) -> RhaiResultOf<()> { self.run_ast_with_scope(&mut Scope::new(), ast) } /// Evaluate an [`AST`] with own scope, returning any error (if any). #[inline] - pub fn run_ast_with_scope( - &self, - scope: &mut Scope, - ast: &AST, - ) -> Result<(), Box> { - let mods = &mut Imports::new(); + pub fn run_ast_with_scope(&self, scope: &mut Scope, ast: &AST) -> RhaiResultOf<()> { + let global = &mut GlobalRuntimeState::new(); let mut state = EvalState::new(); - if ast.source_raw().is_some() { - mods.source = ast.source_raw().cloned(); - } + global.source = ast.source_raw().clone(); + #[cfg(not(feature = "no_module"))] { - mods.embedded_module_resolver = ast.resolver().cloned(); + global.embedded_module_resolver = ast.resolver().cloned(); } let statements = ast.statements(); @@ -73,7 +64,7 @@ impl Engine { } else { &lib }; - self.eval_global_statements(scope, mods, &mut state, statements, lib, 0)?; + self.eval_global_statements(scope, global, &mut state, statements, lib, 0)?; } Ok(()) } diff --git a/src/ast.rs b/src/ast.rs deleted file mode 100644 index dc87ec79..00000000 --- a/src/ast.rs +++ /dev/null @@ -1,2585 +0,0 @@ -//! Module defining the AST (abstract syntax tree). - -use crate::calc_fn_hash; -use crate::engine::{OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE}; -use crate::func::hashing::ALT_ZERO_HASH; -use crate::module::NamespaceRef; -use crate::tokenizer::Token; -use crate::types::dynamic::Union; -use crate::{ - Dynamic, FnNamespace, Identifier, ImmutableString, Module, Position, Shared, StaticVec, INT, -}; -#[cfg(feature = "no_std")] -use std::prelude::v1::*; -use std::{ - collections::BTreeMap, - fmt, - hash::Hash, - mem, - num::{NonZeroU8, NonZeroUsize}, - ops::{ - Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, Deref, DerefMut, Not, Sub, - SubAssign, - }, -}; - -#[cfg(not(feature = "no_float"))] -use std::str::FromStr; - -#[cfg(not(feature = "no_float"))] -use num_traits::Float; - -/// A type representing the access mode of a function. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub enum FnAccess { - /// Public function. - Public, - /// Private function. - Private, -} - -/// _(internals)_ A type containing information on a scripted function. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone)] -pub struct ScriptFnDef { - /// Function body. - pub body: StmtBlock, - /// Encapsulated running environment, if any. - pub lib: Option>, - /// Encapsulated imported modules. - /// - /// Not available under `no_module`. - #[cfg(not(feature = "no_module"))] - pub mods: crate::engine::Imports, - /// Function name. - pub name: Identifier, - /// Function access mode. - pub access: FnAccess, - /// Names of function parameters. - pub params: StaticVec, - /// _(metadata)_ Function doc-comments (if any). - /// Exported under the `metadata` feature only. - #[cfg(feature = "metadata")] - pub comments: Option]>>, -} - -impl fmt::Display for ScriptFnDef { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}{}({})", - match self.access { - FnAccess::Public => "", - FnAccess::Private => "private ", - }, - self.name, - self.params - .iter() - .map(|s| s.as_str()) - .collect::>() - .join(", ") - ) - } -} - -/// A type containing the metadata of a script-defined function. -/// -/// Not available under `no_function`. -/// -/// Created by [`AST::iter_functions`]. -#[cfg(not(feature = "no_function"))] -#[derive(Debug, Eq, PartialEq, Clone, Hash)] -pub struct ScriptFnMetadata<'a> { - /// _(metadata)_ Function doc-comments (if any). - /// Exported under the `metadata` feature only. - /// - /// 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. - /// - /// Leading white-spaces are stripped, and each string slice always starts with the corresponding - /// doc-comment leader: `///` or `/**`. - #[cfg(feature = "metadata")] - pub comments: Vec<&'a str>, - /// Function access mode. - pub access: FnAccess, - /// Function name. - pub name: &'a str, - /// Function parameters (if any). - pub params: Vec<&'a str>, -} - -#[cfg(not(feature = "no_function"))] -impl fmt::Display for ScriptFnMetadata<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}{}({})", - match self.access { - FnAccess::Public => "", - FnAccess::Private => "private ", - }, - self.name, - self.params - .iter() - .cloned() - .collect::>() - .join(", ") - ) - } -} - -#[cfg(not(feature = "no_function"))] -impl<'a> From<&'a ScriptFnDef> for ScriptFnMetadata<'a> { - #[inline] - fn from(value: &'a ScriptFnDef) -> Self { - Self { - #[cfg(not(feature = "no_function"))] - #[cfg(feature = "metadata")] - comments: value - .comments - .as_ref() - .map_or_else(|| Vec::new(), |v| v.iter().map(Box::as_ref).collect()), - access: value.access, - name: &value.name, - params: value.params.iter().map(|s| s.as_str()).collect(), - } - } -} - -#[cfg(not(feature = "no_function"))] -impl std::cmp::PartialOrd for ScriptFnMetadata<'_> { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -#[cfg(not(feature = "no_function"))] -impl std::cmp::Ord for ScriptFnMetadata<'_> { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - match self.name.cmp(other.name) { - std::cmp::Ordering::Equal => self.params.len().cmp(&other.params.len()), - cmp => cmp, - } - } -} - -/// Compiled AST (abstract syntax tree) of a Rhai script. -/// -/// # Thread Safety -/// -/// Currently, [`AST`] is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. -#[derive(Debug, Clone)] -pub struct AST { - /// Source of the [`AST`]. - source: Option, - /// Global statements. - body: StmtBlock, - /// Script-defined functions. - #[cfg(not(feature = "no_function"))] - functions: Shared, - /// Embedded module resolver, if any. - #[cfg(not(feature = "no_module"))] - resolver: Option>, -} - -impl Default for AST { - #[inline(always)] - fn default() -> Self { - Self::empty() - } -} - -impl AST { - /// Create a new [`AST`]. - #[cfg(not(feature = "internals"))] - #[inline(always)] - #[must_use] - pub(crate) fn new( - statements: impl IntoIterator, - #[cfg(not(feature = "no_function"))] functions: impl Into>, - ) -> Self { - Self { - source: None, - body: StmtBlock::new(statements, Position::NONE), - #[cfg(not(feature = "no_function"))] - functions: functions.into(), - #[cfg(not(feature = "no_module"))] - resolver: None, - } - } - /// _(internals)_ Create a new [`AST`]. - /// Exported under the `internals` feature only. - #[cfg(feature = "internals")] - #[inline(always)] - #[must_use] - pub fn new( - statements: impl IntoIterator, - #[cfg(not(feature = "no_function"))] functions: impl Into>, - ) -> Self { - Self { - source: None, - body: StmtBlock::new(statements, Position::NONE), - #[cfg(not(feature = "no_function"))] - functions: functions.into(), - #[cfg(not(feature = "no_module"))] - resolver: None, - } - } - /// Create a new [`AST`] with a source name. - #[cfg(not(feature = "internals"))] - #[inline(always)] - #[must_use] - pub(crate) fn new_with_source( - statements: impl IntoIterator, - #[cfg(not(feature = "no_function"))] functions: impl Into>, - source: impl Into, - ) -> Self { - let mut ast = Self::new( - statements, - #[cfg(not(feature = "no_function"))] - functions, - ); - ast.set_source(source); - ast - } - /// _(internals)_ Create a new [`AST`] with a source name. - /// Exported under the `internals` feature only. - #[cfg(feature = "internals")] - #[inline(always)] - #[must_use] - pub fn new_with_source( - statements: impl IntoIterator, - #[cfg(not(feature = "no_function"))] functions: impl Into>, - source: impl Into, - ) -> Self { - let mut ast = Self::new( - statements, - #[cfg(not(feature = "no_function"))] - functions, - ); - ast.set_source(source); - ast - } - /// Create an empty [`AST`]. - #[inline] - #[must_use] - pub fn empty() -> Self { - Self { - source: None, - body: StmtBlock::NONE, - #[cfg(not(feature = "no_function"))] - functions: Module::new().into(), - #[cfg(not(feature = "no_module"))] - resolver: None, - } - } - /// Get the source, if any. - #[inline(always)] - #[must_use] - pub fn source(&self) -> Option<&str> { - self.source.as_ref().map(|s| s.as_str()) - } - /// Get a reference to the source. - #[inline(always)] - #[must_use] - pub(crate) fn source_raw(&self) -> Option<&Identifier> { - self.source.as_ref() - } - /// Set the source. - #[inline] - pub fn set_source(&mut self, source: impl Into) -> &mut Self { - let source = source.into(); - #[cfg(not(feature = "no_function"))] - Shared::get_mut(&mut self.functions) - .as_mut() - .map(|m| m.set_id(source.clone())); - self.source = Some(source); - self - } - /// Clear the source. - #[inline(always)] - pub fn clear_source(&mut self) -> &mut Self { - self.source = None; - self - } - /// Get the statements. - #[cfg(not(feature = "internals"))] - #[inline(always)] - #[must_use] - pub(crate) fn statements(&self) -> &[Stmt] { - &self.body.0 - } - /// _(internals)_ Get the statements. - /// Exported under the `internals` feature only. - #[cfg(feature = "internals")] - #[inline(always)] - #[must_use] - pub fn statements(&self) -> &[Stmt] { - &self.body.0 - } - /// Get a mutable reference to the statements. - #[allow(dead_code)] - #[inline(always)] - #[must_use] - pub(crate) fn statements_mut(&mut self) -> &mut StaticVec { - &mut self.body.0 - } - /// Does this [`AST`] contain script-defined functions? - /// - /// Not available under `no_function`. - #[cfg(not(feature = "no_function"))] - #[inline(always)] - #[must_use] - pub fn has_functions(&self) -> bool { - !self.functions.is_empty() - } - /// Get the internal shared [`Module`] containing all script-defined functions. - #[cfg(not(feature = "internals"))] - #[cfg(not(feature = "no_function"))] - #[inline(always)] - #[must_use] - pub(crate) fn shared_lib(&self) -> &Shared { - &self.functions - } - /// _(internals)_ Get the internal shared [`Module`] containing all script-defined functions. - /// Exported under the `internals` feature only. - /// - /// Not available under `no_function`. - #[cfg(feature = "internals")] - #[cfg(not(feature = "no_function"))] - #[inline(always)] - #[must_use] - pub fn shared_lib(&self) -> &Shared { - &self.functions - } - /// Get the embedded [module resolver][`ModuleResolver`]. - #[cfg(not(feature = "internals"))] - #[cfg(not(feature = "no_module"))] - #[inline(always)] - #[must_use] - pub(crate) fn resolver( - &self, - ) -> Option<&Shared> { - self.resolver.as_ref() - } - /// _(internals)_ Get the embedded [module resolver][crate::ModuleResolver]. - /// Exported under the `internals` feature only. - /// - /// Not available under `no_module`. - #[cfg(feature = "internals")] - #[cfg(not(feature = "no_module"))] - #[inline(always)] - #[must_use] - pub fn resolver(&self) -> Option<&Shared> { - self.resolver.as_ref() - } - /// Set the embedded [module resolver][`ModuleResolver`]. - #[cfg(not(feature = "no_module"))] - #[inline(always)] - pub(crate) fn set_resolver( - &mut self, - resolver: impl Into>, - ) -> &mut Self { - self.resolver = Some(resolver.into()); - self - } - /// Clone the [`AST`]'s functions into a new [`AST`]. - /// No statements are cloned. - /// - /// Not available under `no_function`. - /// - /// This operation is cheap because functions are shared. - #[cfg(not(feature = "no_function"))] - #[inline(always)] - #[must_use] - pub fn clone_functions_only(&self) -> Self { - self.clone_functions_only_filtered(|_, _, _, _, _| true) - } - /// Clone the [`AST`]'s functions into a new [`AST`] based on a filter predicate. - /// No statements are cloned. - /// - /// Not available under `no_function`. - /// - /// This operation is cheap because functions are shared. - #[cfg(not(feature = "no_function"))] - #[inline] - #[must_use] - pub fn clone_functions_only_filtered( - &self, - filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, - ) -> Self { - let mut functions = Module::new(); - functions.merge_filtered(&self.functions, &filter); - Self { - source: self.source.clone(), - body: StmtBlock::NONE, - functions: functions.into(), - #[cfg(not(feature = "no_module"))] - resolver: self.resolver.clone(), - } - } - /// Clone the [`AST`]'s script statements into a new [`AST`]. - /// No functions are cloned. - #[inline(always)] - #[must_use] - pub fn clone_statements_only(&self) -> Self { - Self { - source: self.source.clone(), - body: self.body.clone(), - #[cfg(not(feature = "no_function"))] - functions: Module::new().into(), - #[cfg(not(feature = "no_module"))] - resolver: self.resolver.clone(), - } - } - /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, - /// version is returned. - /// - /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. - /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. - /// Of course, if the first [`AST`] uses a `return` statement at the end, then - /// the second [`AST`] will essentially be dead code. - /// - /// All script-defined functions in the second [`AST`] overwrite similarly-named functions - /// in the first [`AST`] with the same number of parameters. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// let ast1 = engine.compile(" - /// fn foo(x) { 42 + x } - /// foo(1) - /// ")?; - /// - /// let ast2 = engine.compile(r#" - /// fn foo(n) { `hello${n}` } - /// foo("!") - /// "#)?; - /// - /// let ast = ast1.merge(&ast2); // Merge 'ast2' into 'ast1' - /// - /// // Notice that using the '+' operator also works: - /// // let ast = &ast1 + &ast2; - /// - /// // 'ast' is essentially: - /// // - /// // fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten - /// // foo(1) // <- notice this will be "hello1" instead of 43, - /// // // but it is no longer the return value - /// // foo("!") // returns "hello!" - /// - /// // Evaluate it - /// assert_eq!(engine.eval_ast::(&ast)?, "hello!"); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - #[must_use] - pub fn merge(&self, other: &Self) -> Self { - self.merge_filtered_impl(other, |_, _, _, _, _| true) - } - /// Combine one [`AST`] with another. The second [`AST`] is consumed. - /// - /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. - /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. - /// Of course, if the first [`AST`] uses a `return` statement at the end, then - /// the second [`AST`] will essentially be dead code. - /// - /// All script-defined functions in the second [`AST`] overwrite similarly-named functions - /// in the first [`AST`] with the same number of parameters. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// let mut ast1 = engine.compile(" - /// fn foo(x) { 42 + x } - /// foo(1) - /// ")?; - /// - /// let ast2 = engine.compile(r#" - /// fn foo(n) { `hello${n}` } - /// foo("!") - /// "#)?; - /// - /// ast1.combine(ast2); // Combine 'ast2' into 'ast1' - /// - /// // Notice that using the '+=' operator also works: - /// // ast1 += ast2; - /// - /// // 'ast1' is essentially: - /// // - /// // fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten - /// // foo(1) // <- notice this will be "hello1" instead of 43, - /// // // but it is no longer the return value - /// // foo("!") // returns "hello!" - /// - /// // Evaluate it - /// assert_eq!(engine.eval_ast::(&ast1)?, "hello!"); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn combine(&mut self, other: Self) -> &mut Self { - self.combine_filtered_impl(other, |_, _, _, _, _| true) - } - /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version - /// is returned. - /// - /// Not available under `no_function`. - /// - /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. - /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. - /// Of course, if the first [`AST`] uses a `return` statement at the end, then - /// the second [`AST`] will essentially be dead code. - /// - /// All script-defined functions in the second [`AST`] are first selected based on a filter - /// predicate, then overwrite similarly-named functions in the first [`AST`] with the - /// same number of parameters. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// let ast1 = engine.compile(" - /// fn foo(x) { 42 + x } - /// foo(1) - /// ")?; - /// - /// let ast2 = engine.compile(r#" - /// fn foo(n) { `hello${n}` } - /// fn error() { 0 } - /// foo("!") - /// "#)?; - /// - /// // Merge 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1' - /// let ast = ast1.merge_filtered(&ast2, |_, _, script, name, params| - /// script && name == "error" && params == 0); - /// - /// // 'ast' is essentially: - /// // - /// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten - /// // // because 'ast2::foo' is filtered away - /// // foo(1) // <- notice this will be 43 instead of "hello1", - /// // // but it is no longer the return value - /// // fn error() { 0 } // <- this function passes the filter and is merged - /// // foo("!") // <- returns "42!" - /// - /// // Evaluate it - /// assert_eq!(engine.eval_ast::(&ast)?, "42!"); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_function"))] - #[inline(always)] - #[must_use] - pub fn merge_filtered( - &self, - other: &Self, - filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, - ) -> Self { - self.merge_filtered_impl(other, filter) - } - /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version - /// is returned. - #[inline] - #[must_use] - fn merge_filtered_impl( - &self, - other: &Self, - _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, - ) -> Self { - let merged = match (self.body.is_empty(), other.body.is_empty()) { - (false, false) => { - let mut body = self.body.clone(); - body.0.extend(other.body.0.iter().cloned()); - body - } - (false, true) => self.body.clone(), - (true, false) => other.body.clone(), - (true, true) => StmtBlock::NONE, - }; - - let source = other.source.clone().or_else(|| self.source.clone()); - - #[cfg(not(feature = "no_function"))] - let functions = { - let mut functions = self.functions.as_ref().clone(); - functions.merge_filtered(&other.functions, &_filter); - functions - }; - - if let Some(source) = source { - Self::new_with_source( - merged.0, - #[cfg(not(feature = "no_function"))] - functions, - source, - ) - } else { - Self::new( - merged.0, - #[cfg(not(feature = "no_function"))] - functions, - ) - } - } - /// Combine one [`AST`] with another. The second [`AST`] is consumed. - /// - /// Not available under `no_function`. - /// - /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. - /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. - /// Of course, if the first [`AST`] uses a `return` statement at the end, then - /// the second [`AST`] will essentially be dead code. - /// - /// All script-defined functions in the second [`AST`] are first selected based on a filter - /// predicate, then overwrite similarly-named functions in the first [`AST`] with the - /// same number of parameters. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// let mut ast1 = engine.compile(" - /// fn foo(x) { 42 + x } - /// foo(1) - /// ")?; - /// - /// let ast2 = engine.compile(r#" - /// fn foo(n) { `hello${n}` } - /// fn error() { 0 } - /// foo("!") - /// "#)?; - /// - /// // Combine 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1' - /// ast1.combine_filtered(ast2, |_, _, script, name, params| - /// script && name == "error" && params == 0); - /// - /// // 'ast1' is essentially: - /// // - /// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten - /// // // because 'ast2::foo' is filtered away - /// // foo(1) // <- notice this will be 43 instead of "hello1", - /// // // but it is no longer the return value - /// // fn error() { 0 } // <- this function passes the filter and is merged - /// // foo("!") // <- returns "42!" - /// - /// // Evaluate it - /// assert_eq!(engine.eval_ast::(&ast1)?, "42!"); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_function"))] - #[inline(always)] - pub fn combine_filtered( - &mut self, - other: Self, - filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, - ) -> &mut Self { - self.combine_filtered_impl(other, filter) - } - /// Combine one [`AST`] with another. The second [`AST`] is consumed. - #[inline] - fn combine_filtered_impl( - &mut self, - other: Self, - _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, - ) -> &mut Self { - self.body.0.extend(other.body.0.into_iter()); - - #[cfg(not(feature = "no_function"))] - if !other.functions.is_empty() { - crate::func::native::shared_make_mut(&mut self.functions) - .merge_filtered(&other.functions, &_filter); - } - self - } - /// Filter out the functions, retaining only some based on a filter predicate. - /// - /// Not available under `no_function`. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// let mut ast = engine.compile(r#" - /// fn foo(n) { n + 1 } - /// fn bar() { print("hello"); } - /// "#)?; - /// - /// // Remove all functions except 'foo(_)' - /// ast.retain_functions(|_, _, name, params| name == "foo" && params == 1); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_function"))] - #[inline] - pub fn retain_functions( - &mut self, - filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool, - ) -> &mut Self { - if !self.functions.is_empty() { - crate::func::native::shared_make_mut(&mut self.functions) - .retain_script_functions(filter); - } - self - } - /// Iterate through all function definitions. - /// - /// Not available under `no_function`. - #[cfg(not(feature = "no_function"))] - #[allow(dead_code)] - #[inline] - pub(crate) fn iter_fn_def(&self) -> impl Iterator { - self.functions - .iter_script_fn() - .map(|(_, _, _, _, fn_def)| fn_def.as_ref()) - } - /// Iterate through all function definitions. - /// - /// Not available under `no_function`. - #[cfg(not(feature = "no_function"))] - #[inline] - pub fn iter_functions<'a>(&'a self) -> impl Iterator + 'a { - self.functions - .iter_script_fn() - .map(|(_, _, _, _, fn_def)| fn_def.as_ref().into()) - } - /// Clear all function definitions in the [`AST`]. - /// - /// Not available under `no_function`. - #[cfg(not(feature = "no_function"))] - #[inline(always)] - pub fn clear_functions(&mut self) -> &mut Self { - self.functions = Module::new().into(); - self - } - /// Clear all statements in the [`AST`], leaving only function definitions. - #[inline(always)] - pub fn clear_statements(&mut self) -> &mut Self { - self.body = StmtBlock::NONE; - self - } - /// Extract all top-level literal constant and/or variable definitions. - /// This is useful for extracting all global constants from a script without actually running it. - /// - /// A literal constant/variable definition takes the form of: - /// `const VAR = `_value_`;` and `let VAR = `_value_`;` - /// where _value_ is a literal expression or will be optimized into a literal. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Scope}; - /// - /// let engine = Engine::new(); - /// - /// let ast = engine.compile( - /// " - /// const A = 40 + 2; // constant that optimizes into a literal - /// let b = 123; // literal variable - /// const B = b * A; // non-literal constant - /// const C = 999; // literal constant - /// b = A + C; // expression - /// - /// { // <- new block scope - /// const Z = 0; // <- literal constant not at top-level - /// } - /// ")?; - /// - /// let mut iter = ast.iter_literal_variables(true, false) - /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); - /// - /// # #[cfg(not(feature = "no_optimize"))] - /// assert_eq!(iter.next(), Some(("A", true, 42))); - /// assert_eq!(iter.next(), Some(("C", true, 999))); - /// assert_eq!(iter.next(), None); - /// - /// let mut iter = ast.iter_literal_variables(false, true) - /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); - /// - /// assert_eq!(iter.next(), Some(("b", false, 123))); - /// assert_eq!(iter.next(), None); - /// - /// let mut iter = ast.iter_literal_variables(true, true) - /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); - /// - /// # #[cfg(not(feature = "no_optimize"))] - /// assert_eq!(iter.next(), Some(("A", true, 42))); - /// assert_eq!(iter.next(), Some(("b", false, 123))); - /// assert_eq!(iter.next(), Some(("C", true, 999))); - /// assert_eq!(iter.next(), None); - /// - /// let scope: Scope = ast.iter_literal_variables(true, false).collect(); - /// - /// # #[cfg(not(feature = "no_optimize"))] - /// assert_eq!(scope.len(), 2); - /// - /// Ok(()) - /// # } - /// ``` - pub fn iter_literal_variables( - &self, - include_constants: bool, - include_variables: bool, - ) -> impl Iterator { - self.statements().iter().filter_map(move |stmt| match stmt { - Stmt::Var(expr, name, options, _) - if options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT) && include_constants - || !options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT) - && include_variables => - { - if let Some(value) = expr.get_literal_value() { - Some(( - name.as_str(), - options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT), - value, - )) - } else { - None - } - } - _ => None, - }) - } - /// Recursively walk the [`AST`], including function bodies (if any). - /// Return `false` from the callback to terminate the walk. - #[cfg(not(feature = "internals"))] - #[cfg(not(feature = "no_module"))] - #[inline] - pub(crate) fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool { - let path = &mut Vec::new(); - - for stmt in self.statements() { - if !stmt.walk(path, on_node) { - return false; - } - } - #[cfg(not(feature = "no_function"))] - for stmt in self.iter_fn_def().flat_map(|f| f.body.0.iter()) { - if !stmt.walk(path, on_node) { - return false; - } - } - - true - } - /// _(internals)_ Recursively walk the [`AST`], including function bodies (if any). - /// Return `false` from the callback to terminate the walk. - /// Exported under the `internals` feature only. - #[cfg(feature = "internals")] - #[inline] - pub fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool { - let path = &mut Vec::new(); - - for stmt in self.statements() { - if !stmt.walk(path, on_node) { - return false; - } - } - #[cfg(not(feature = "no_function"))] - for stmt in self.iter_fn_def().flat_map(|f| f.body.0.iter()) { - if !stmt.walk(path, on_node) { - return false; - } - } - - true - } -} - -impl> Add for &AST { - type Output = AST; - - #[inline(always)] - fn add(self, rhs: A) -> Self::Output { - self.merge(rhs.as_ref()) - } -} - -impl> AddAssign for AST { - #[inline(always)] - fn add_assign(&mut self, rhs: A) { - self.combine(rhs.into()); - } -} - -impl AsRef<[Stmt]> for AST { - #[inline(always)] - fn as_ref(&self) -> &[Stmt] { - self.statements() - } -} - -#[cfg(not(feature = "no_function"))] -impl AsRef for AST { - #[inline(always)] - fn as_ref(&self) -> &Module { - self.shared_lib().as_ref() - } -} - -#[cfg(not(feature = "no_function"))] -impl AsRef> for AST { - #[inline(always)] - fn as_ref(&self) -> &Shared { - self.shared_lib() - } -} - -/// _(internals)_ An identifier containing a name and a [position][Position]. -/// Exported under the `internals` feature only. -#[derive(Clone, Eq, PartialEq, Hash)] -pub struct Ident { - /// Identifier name. - pub name: Identifier, - /// Position. - pub pos: Position, -} - -impl fmt::Debug for Ident { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self.name)?; - self.pos.debug_print(f) - } -} - -impl AsRef for Ident { - #[inline(always)] - fn as_ref(&self) -> &str { - self.name.as_ref() - } -} - -impl Ident { - #[inline(always)] - pub fn as_str(&self) -> &str { - self.name.as_str() - } -} - -/// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`]. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone, Hash)] -pub enum ASTNode<'a> { - /// A statement ([`Stmt`]). - Stmt(&'a Stmt), - /// An expression ([`Expr`]). - Expr(&'a Expr), -} - -impl<'a> From<&'a Stmt> for ASTNode<'a> { - fn from(stmt: &'a Stmt) -> Self { - Self::Stmt(stmt) - } -} - -impl<'a> From<&'a Expr> for ASTNode<'a> { - fn from(expr: &'a Expr) -> Self { - Self::Expr(expr) - } -} - -impl ASTNode<'_> { - /// Get the [`Position`] of this [`ASTNode`]. - pub const fn position(&self) -> Position { - match self { - ASTNode::Stmt(stmt) => stmt.position(), - ASTNode::Expr(expr) => expr.position(), - } - } -} - -/// _(internals)_ A scoped block of statements. -/// Exported under the `internals` feature only. -#[derive(Clone, Hash, Default)] -pub struct StmtBlock(StaticVec, Position); - -impl StmtBlock { - /// A [`StmtBlock`] that does not exist. - pub const NONE: Self = Self::empty(Position::NONE); - - /// Create a new [`StmtBlock`]. - #[must_use] - pub fn new(statements: impl IntoIterator, pos: Position) -> Self { - let mut statements: StaticVec<_> = statements.into_iter().collect(); - statements.shrink_to_fit(); - Self(statements, pos) - } - /// Create an empty [`StmtBlock`]. - #[inline(always)] - #[must_use] - pub const fn empty(pos: Position) -> Self { - Self(StaticVec::new_const(), pos) - } - /// Is this statements block empty? - #[inline(always)] - #[must_use] - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - /// Number of statements in this statements block. - #[inline(always)] - #[must_use] - pub fn len(&self) -> usize { - self.0.len() - } - /// Get the position (location of the beginning `{`) of this statements block. - #[inline(always)] - #[must_use] - pub const fn position(&self) -> Position { - self.1 - } -} - -impl Deref for StmtBlock { - type Target = StaticVec; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for StmtBlock { - #[inline(always)] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl fmt::Debug for StmtBlock { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Block")?; - fmt::Debug::fmt(&self.0, f)?; - self.1.debug_print(f) - } -} - -impl From for Stmt { - #[inline] - fn from(block: StmtBlock) -> Self { - let block_pos = block.position(); - Self::Block(block.0.into_boxed_slice(), block_pos) - } -} - -/// A type that holds a configuration option with bit-flags. -/// Exported under the `internals` feature only. -#[derive(PartialEq, Eq, Copy, Clone, Hash, Default)] -pub struct OptionFlags(u8); - -impl OptionFlags { - /// Does this [`OptionFlags`] contain a particular option flag? - #[inline(always)] - #[must_use] - pub const fn contains(self, flag: Self) -> bool { - self.0 & flag.0 != 0 - } -} - -impl Not for OptionFlags { - type Output = Self; - - /// Return the negation of the [`OptionFlags`]. - #[inline(always)] - fn not(self) -> Self::Output { - Self(!self.0) & AST_OPTION_FLAGS::AST_OPTION_ALL - } -} - -impl Add for OptionFlags { - type Output = Self; - - /// Return the union of two [`OptionFlags`]. - #[inline(always)] - fn add(self, rhs: Self) -> Self::Output { - Self(self.0 | rhs.0) - } -} - -impl AddAssign for OptionFlags { - /// Add the option flags in one [`OptionFlags`] to another. - #[inline(always)] - fn add_assign(&mut self, rhs: Self) { - self.0 |= rhs.0 - } -} - -impl BitOr for OptionFlags { - type Output = Self; - - /// Return the union of two [`OptionFlags`]. - #[inline(always)] - fn bitor(self, rhs: Self) -> Self::Output { - Self(self.0 | rhs.0) - } -} - -impl BitOrAssign for OptionFlags { - /// Add the option flags in one [`OptionFlags`] to another. - #[inline(always)] - fn bitor_assign(&mut self, rhs: Self) { - self.0 |= rhs.0 - } -} - -impl Sub for OptionFlags { - type Output = Self; - - /// Return the difference of two [`OptionFlags`]. - #[inline(always)] - fn sub(self, rhs: Self) -> Self::Output { - Self(self.0 & !rhs.0) - } -} - -impl SubAssign for OptionFlags { - /// Remove the option flags in one [`OptionFlags`] from another. - #[inline(always)] - fn sub_assign(&mut self, rhs: Self) { - self.0 &= !rhs.0 - } -} - -impl BitAnd for OptionFlags { - type Output = Self; - - /// Return the intersection of two [`OptionFlags`]. - #[inline(always)] - fn bitand(self, rhs: Self) -> Self::Output { - Self(self.0 & !rhs.0) - } -} - -impl BitAndAssign for OptionFlags { - /// Keep only the intersection of one [`OptionFlags`] with another. - #[inline(always)] - fn bitand_assign(&mut self, rhs: Self) { - self.0 &= !rhs.0 - } -} - -/// Option bit-flags for [`AST`] nodes. -#[allow(non_snake_case)] -pub mod AST_OPTION_FLAGS { - use super::OptionFlags; - - /// _(internals)_ No options for the [`AST`][crate::AST] node. - /// Exported under the `internals` feature only. - pub const AST_OPTION_NONE: OptionFlags = OptionFlags(0b0000_0000); - /// _(internals)_ The [`AST`][crate::AST] node is constant. - /// Exported under the `internals` feature only. - pub const AST_OPTION_CONSTANT: OptionFlags = OptionFlags(0b0000_0001); - /// _(internals)_ The [`AST`][crate::AST] node is public. - /// Exported under the `internals` feature only. - pub const AST_OPTION_PUBLIC: OptionFlags = OptionFlags(0b0000_0010); - /// _(internals)_ The [`AST`][crate::AST] node is in negated mode. - /// Exported under the `internals` feature only. - pub const AST_OPTION_NEGATED: OptionFlags = OptionFlags(0b0000_0100); - /// _(internals)_ The [`AST`][crate::AST] node breaks out of normal control flow. - /// Exported under the `internals` feature only. - pub const AST_OPTION_BREAK_OUT: OptionFlags = OptionFlags(0b0000_1000); - /// _(internals)_ Mask of all options. - /// Exported under the `internals` feature only. - pub(crate) const AST_OPTION_ALL: OptionFlags = OptionFlags( - AST_OPTION_CONSTANT.0 | AST_OPTION_PUBLIC.0 | AST_OPTION_NEGATED.0 | AST_OPTION_BREAK_OUT.0, - ); - - impl std::fmt::Debug for OptionFlags { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - fn write_option( - options: &OptionFlags, - f: &mut std::fmt::Formatter<'_>, - num_flags: &mut usize, - flag: OptionFlags, - name: &str, - ) -> std::fmt::Result { - if options.contains(flag) { - if *num_flags > 0 { - f.write_str("+")?; - } - f.write_str(name)?; - *num_flags += 1; - } - Ok(()) - } - - let num_flags = &mut 0; - - f.write_str("(")?; - write_option(self, f, num_flags, AST_OPTION_CONSTANT, "Constant")?; - write_option(self, f, num_flags, AST_OPTION_PUBLIC, "Public")?; - write_option(self, f, num_flags, AST_OPTION_NEGATED, "Negated")?; - write_option(self, f, num_flags, AST_OPTION_BREAK_OUT, "Break")?; - f.write_str(")")?; - - Ok(()) - } - } -} - -/// _(internals)_ A statement. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone, Hash)] -pub enum Stmt { - /// No-op. - Noop(Position), - /// `if` expr `{` stmt `}` `else` `{` stmt `}` - If(Expr, Box<(StmtBlock, StmtBlock)>, Position), - /// `switch` expr `if` condition `{` literal or _ `=>` stmt `,` ... `}` - Switch( - Expr, - Box<( - BTreeMap, StmtBlock)>>, - StmtBlock, - StaticVec<(INT, INT, bool, Option, StmtBlock)>, - )>, - Position, - ), - /// `while` expr `{` stmt `}` | `loop` `{` stmt `}` - /// - /// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement. - While(Expr, Box, Position), - /// `do` `{` stmt `}` `while`|`until` expr - /// - /// ### Option Flags - /// - /// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `while` - /// * [`AST_OPTION_NEGATED`][AST_OPTION_FLAGS::AST_OPTION_NEGATED] = `until` - Do(Box, Expr, OptionFlags, Position), - /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` - For(Expr, Box<(Ident, Option, StmtBlock)>, Position), - /// \[`export`\] `let`|`const` id `=` expr - /// - /// ### Option Flags - /// - /// * [`AST_OPTION_PUBLIC`][AST_OPTION_FLAGS::AST_OPTION_PUBLIC] = `export` - /// * [`AST_OPTION_CONSTANT`][AST_OPTION_FLAGS::AST_OPTION_CONSTANT] = `const` - Var(Expr, Box, OptionFlags, Position), - /// expr op`=` expr - Assignment(Box<(Expr, Option>, Expr)>, Position), - /// func `(` expr `,` ... `)` - /// - /// Note - this is a duplicate of [`Expr::FnCall`] to cover the very common pattern of a single - /// function call forming one statement. - FnCall(Box, Position), - /// `{` stmt`;` ... `}` - Block(Box<[Stmt]>, Position), - /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` - TryCatch(Box<(StmtBlock, Option, StmtBlock)>, Position), - /// [expression][Expr] - Expr(Expr), - /// `continue`/`break` - /// - /// ### Option Flags - /// - /// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `continue` - /// * [`AST_OPTION_BREAK_OUT`][AST_OPTION_FLAGS::AST_OPTION_BREAK_OUT] = `break` - BreakLoop(OptionFlags, Position), - /// `return`/`throw` - /// - /// ### Option Flags - /// - /// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `return` - /// * [`AST_OPTION_BREAK_OUT`][AST_OPTION_FLAGS::AST_OPTION_BREAK_OUT] = `throw` - Return(OptionFlags, Option, Position), - /// `import` expr `as` var - /// - /// Not available under `no_module`. - #[cfg(not(feature = "no_module"))] - Import(Expr, Option>, Position), - /// `export` var `as` var `,` ... - /// - /// Not available under `no_module`. - #[cfg(not(feature = "no_module"))] - Export(Box<[(Ident, Ident)]>, Position), - /// Convert a variable to shared. - /// - /// Not available under `no_closure`. - /// - /// # Notes - /// - /// This variant does not map to any language structure. It is currently only used only to - /// convert a normal variable into a shared variable when the variable is _captured_ by a closure. - #[cfg(not(feature = "no_closure"))] - Share(Identifier), -} - -impl Default for Stmt { - #[inline(always)] - fn default() -> Self { - Self::Noop(Position::NONE) - } -} - -impl From for StmtBlock { - #[inline] - fn from(stmt: Stmt) -> Self { - match stmt { - Stmt::Block(mut block, pos) => Self(block.iter_mut().map(mem::take).collect(), pos), - Stmt::Noop(pos) => Self(StaticVec::new_const(), pos), - _ => { - let pos = stmt.position(); - Self(vec![stmt].into(), pos) - } - } - } -} - -impl Stmt { - /// Is this statement [`Noop`][Stmt::Noop]? - #[inline(always)] - #[must_use] - pub const fn is_noop(&self) -> bool { - matches!(self, Self::Noop(_)) - } - /// Get the [position][Position] of this statement. - #[must_use] - pub const fn position(&self) -> Position { - match self { - Self::Noop(pos) - | Self::BreakLoop(_, pos) - | Self::Block(_, pos) - | Self::Assignment(_, pos) - | Self::FnCall(_, pos) - | Self::If(_, _, pos) - | Self::Switch(_, _, pos) - | Self::While(_, _, pos) - | Self::Do(_, _, _, pos) - | Self::For(_, _, pos) - | Self::Return(_, _, pos) - | Self::Var(_, _, _, pos) - | Self::TryCatch(_, pos) => *pos, - - Self::Expr(x) => x.position(), - - #[cfg(not(feature = "no_module"))] - Self::Import(_, _, pos) => *pos, - #[cfg(not(feature = "no_module"))] - Self::Export(_, pos) => *pos, - - #[cfg(not(feature = "no_closure"))] - Self::Share(_) => Position::NONE, - } - } - /// Override the [position][Position] of this statement. - pub fn set_position(&mut self, new_pos: Position) -> &mut Self { - match self { - Self::Noop(pos) - | Self::BreakLoop(_, pos) - | Self::Block(_, pos) - | Self::Assignment(_, pos) - | Self::FnCall(_, pos) - | Self::If(_, _, pos) - | Self::Switch(_, _, pos) - | Self::While(_, _, pos) - | Self::Do(_, _, _, pos) - | Self::For(_, _, pos) - | Self::Return(_, _, pos) - | Self::Var(_, _, _, pos) - | Self::TryCatch(_, pos) => *pos = new_pos, - - Self::Expr(x) => { - x.set_position(new_pos); - } - - #[cfg(not(feature = "no_module"))] - Self::Import(_, _, pos) => *pos = new_pos, - #[cfg(not(feature = "no_module"))] - Self::Export(_, pos) => *pos = new_pos, - - #[cfg(not(feature = "no_closure"))] - Self::Share(_) => (), - } - - self - } - /// Does this statement return a value? - #[must_use] - pub const fn returns_value(&self) -> bool { - match self { - Self::If(_, _, _) - | Self::Switch(_, _, _) - | Self::Block(_, _) - | Self::Expr(_) - | Self::FnCall(_, _) => true, - - Self::Noop(_) - | Self::While(_, _, _) - | Self::Do(_, _, _, _) - | Self::For(_, _, _) - | Self::TryCatch(_, _) => false, - - Self::Var(_, _, _, _) - | Self::Assignment(_, _) - | Self::BreakLoop(_, _) - | Self::Return(_, _, _) => false, - - #[cfg(not(feature = "no_module"))] - Self::Import(_, _, _) | Self::Export(_, _) => false, - - #[cfg(not(feature = "no_closure"))] - Self::Share(_) => false, - } - } - /// Is this statement self-terminated (i.e. no need for a semicolon terminator)? - #[must_use] - pub const fn is_self_terminated(&self) -> bool { - match self { - Self::If(_, _, _) - | Self::Switch(_, _, _) - | Self::While(_, _, _) - | Self::For(_, _, _) - | Self::Block(_, _) - | Self::TryCatch(_, _) => true, - - // A No-op requires a semicolon in order to know it is an empty statement! - Self::Noop(_) => false, - - Self::Expr(Expr::Custom(x, _)) if x.is_self_terminated() => true, - - Self::Var(_, _, _, _) - | Self::Assignment(_, _) - | Self::Expr(_) - | Self::FnCall(_, _) - | Self::Do(_, _, _, _) - | Self::BreakLoop(_, _) - | Self::Return(_, _, _) => false, - - #[cfg(not(feature = "no_module"))] - Self::Import(_, _, _) | Self::Export(_, _) => false, - - #[cfg(not(feature = "no_closure"))] - Self::Share(_) => false, - } - } - /// Is this statement _pure_? - /// - /// A pure statement has no side effects. - #[must_use] - pub fn is_pure(&self) -> bool { - match self { - Self::Noop(_) => true, - Self::Expr(expr) => expr.is_pure(), - Self::If(condition, x, _) => { - condition.is_pure() - && (x.0).0.iter().all(Stmt::is_pure) - && (x.1).0.iter().all(Stmt::is_pure) - } - Self::Switch(expr, x, _) => { - expr.is_pure() - && x.0.values().all(|block| { - block.0.as_ref().map(Expr::is_pure).unwrap_or(true) - && (block.1).0.iter().all(Stmt::is_pure) - }) - && (x.2).iter().all(|(_, _, _, condition, stmt)| { - condition.as_ref().map(Expr::is_pure).unwrap_or(true) - && stmt.0.iter().all(Stmt::is_pure) - }) - && (x.1).0.iter().all(Stmt::is_pure) - } - - // Loops that exit can be pure because it can never be infinite. - Self::While(Expr::BoolConstant(false, _), _, _) => true, - Self::Do(body, Expr::BoolConstant(x, _), options, _) - if *x == options.contains(AST_OPTION_FLAGS::AST_OPTION_NEGATED) => - { - body.iter().all(Stmt::is_pure) - } - - // Loops are never pure since they can be infinite - and that's a side effect. - Self::While(_, _, _) | Self::Do(_, _, _, _) => false, - - // For loops can be pure because if the iterable is pure, it is finite, - // so infinite loops can never occur. - Self::For(iterable, x, _) => iterable.is_pure() && (x.2).0.iter().all(Stmt::is_pure), - - Self::Var(_, _, _, _) | Self::Assignment(_, _) | Self::FnCall(_, _) => false, - Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), - Self::BreakLoop(_, _) | Self::Return(_, _, _) => false, - Self::TryCatch(x, _) => { - (x.0).0.iter().all(Stmt::is_pure) && (x.2).0.iter().all(Stmt::is_pure) - } - - #[cfg(not(feature = "no_module"))] - Self::Import(_, _, _) => false, - #[cfg(not(feature = "no_module"))] - Self::Export(_, _) => false, - - #[cfg(not(feature = "no_closure"))] - Self::Share(_) => false, - } - } - /// Is this statement _pure_ within the containing block? - /// - /// An internally pure statement only has side effects that disappear outside the block. - /// - /// Currently only variable definitions (i.e. `let` and `const`) and `import`/`export` - /// statements are internally pure. - #[inline] - #[must_use] - pub fn is_internally_pure(&self) -> bool { - match self { - Self::Var(expr, _, _, _) => expr.is_pure(), - - #[cfg(not(feature = "no_module"))] - Self::Import(expr, _, _) => expr.is_pure(), - #[cfg(not(feature = "no_module"))] - Self::Export(_, _) => true, - - _ => self.is_pure(), - } - } - /// Does this statement break the current control flow through the containing block? - /// - /// Currently this is only true for `return`, `throw`, `break` and `continue`. - /// - /// All statements following this statement will essentially be dead code. - #[inline] - #[must_use] - pub const fn is_control_flow_break(&self) -> bool { - match self { - Self::Return(_, _, _) | Self::BreakLoop(_, _) => true, - _ => false, - } - } - /// Recursively walk this statement. - /// Return `false` from the callback to terminate the walk. - pub fn walk<'a>( - &'a self, - path: &mut Vec>, - on_node: &mut impl FnMut(&[ASTNode]) -> bool, - ) -> bool { - // Push the current node onto the path - path.push(self.into()); - - if !on_node(path) { - return false; - } - - match self { - Self::Var(e, _, _, _) => { - if !e.walk(path, on_node) { - return false; - } - } - Self::If(e, x, _) => { - if !e.walk(path, on_node) { - return false; - } - for s in &(x.0).0 { - if !s.walk(path, on_node) { - return false; - } - } - for s in &(x.1).0 { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::Switch(e, x, _) => { - if !e.walk(path, on_node) { - return false; - } - for b in x.0.values() { - if !b.0.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) { - return false; - } - for s in &(b.1).0 { - if !s.walk(path, on_node) { - return false; - } - } - } - for (_, _, _, c, stmt) in &x.2 { - if !c.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) { - return false; - } - for s in &stmt.0 { - if !s.walk(path, on_node) { - return false; - } - } - } - for s in &(x.1).0 { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::While(e, s, _) | Self::Do(s, e, _, _) => { - if !e.walk(path, on_node) { - return false; - } - for s in &s.0 { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::For(e, x, _) => { - if !e.walk(path, on_node) { - return false; - } - for s in &(x.2).0 { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::Assignment(x, _) => { - if !x.0.walk(path, on_node) { - return false; - } - if !x.2.walk(path, on_node) { - return false; - } - } - Self::FnCall(x, _) => { - for s in &x.args { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::Block(x, _) => { - for s in x.iter() { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::TryCatch(x, _) => { - for s in &(x.0).0 { - if !s.walk(path, on_node) { - return false; - } - } - for s in &(x.2).0 { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::Expr(e) | Self::Return(_, Some(e), _) => { - if !e.walk(path, on_node) { - return false; - } - } - #[cfg(not(feature = "no_module"))] - Self::Import(e, _, _) => { - if !e.walk(path, on_node) { - return false; - } - } - _ => (), - } - - path.pop().expect("contains current node"); - - true - } -} - -/// _(internals)_ A custom syntax expression. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone, Hash)] -pub struct CustomExpr { - /// List of keywords. - pub inputs: StaticVec, - /// List of tokens actually parsed. - pub tokens: StaticVec, - /// Is the current [`Scope`][crate::Scope] possibly modified by this custom statement - /// (e.g. introducing a new variable)? - pub scope_may_be_changed: bool, - /// Is this custom syntax self-terminated? - pub self_terminated: bool, -} - -impl CustomExpr { - /// Is this custom syntax self-terminated (i.e. no need for a semicolon terminator)? - /// - /// A self-terminated custom syntax always ends in `$block$`, `}` or `;` - #[must_use] - #[inline(always)] - pub const fn is_self_terminated(&self) -> bool { - self.self_terminated - } -} - -/// _(internals)_ A binary expression. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone, Hash)] -pub struct BinaryExpr { - /// LHS expression. - pub lhs: Expr, - /// RHS expression. - pub rhs: Expr, -} - -/// _(internals)_ An op-assignment operator. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub struct OpAssignment<'a> { - /// Hash of the op-assignment call. - pub hash_op_assign: u64, - /// Hash of the underlying operator call (for fallback). - pub hash_op: u64, - /// Op-assignment operator. - pub op: &'a str, -} - -impl OpAssignment<'_> { - /// Create a new [`OpAssignment`]. - /// - /// # Panics - /// - /// Panics if the operator name is not an op-assignment operator. - #[must_use] - pub fn new(op: Token) -> Self { - let op_raw = op - .map_op_assignment() - .expect("op-assignment") - .literal_syntax(); - let op_assignment = op.literal_syntax(); - - Self { - hash_op_assign: calc_fn_hash(op_assignment, 2), - hash_op: calc_fn_hash(op_raw, 2), - op: op_assignment, - } - } -} - -/// _(internals)_ A set of function call hashes. Exported under the `internals` feature only. -/// -/// Two separate hashes are pre-calculated because of the following patterns: -/// -/// ```ignore -/// func(a, b, c); // Native: func(a, b, c) - 3 parameters -/// // Script: func(a, b, c) - 3 parameters -/// -/// a.func(b, c); // Native: func(&mut a, b, c) - 3 parameters -/// // Script: func(b, c) - 2 parameters -/// ``` -/// -/// For normal function calls, the native hash equals the script hash. -/// -/// For method-style calls, the script hash contains one fewer parameter. -/// -/// Function call hashes are used in the following manner: -/// -/// * First, the script hash is tried, which contains only the called function's name plus the -/// number of parameters. -/// -/// * Next, the actual types of arguments are hashed and _combined_ with the native hash, which is -/// then used to search for a native function. In other words, a complete native function call -/// hash always contains the called function's name plus the types of the arguments. This is due -/// to possible function overloading for different parameter types. -#[derive(Clone, Copy, Eq, PartialEq, Hash, Default)] -pub struct FnCallHashes { - /// Pre-calculated hash for a script-defined function (zero if native functions only). - #[cfg(not(feature = "no_function"))] - pub script: u64, - /// Pre-calculated hash for a native Rust function with no parameter types. - pub native: u64, -} - -impl fmt::Debug for FnCallHashes { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - #[cfg(not(feature = "no_function"))] - if self.script != 0 { - return if self.script == self.native { - fmt::Debug::fmt(&self.native, f) - } else { - write!(f, "({}, {})", self.script, self.native) - }; - } - - write!(f, "{} (native only)", self.native) - } -} - -impl From for FnCallHashes { - #[inline(always)] - fn from(hash: u64) -> Self { - let hash = if hash == 0 { ALT_ZERO_HASH } else { hash }; - - Self { - #[cfg(not(feature = "no_function"))] - script: hash, - native: hash, - } - } -} - -impl FnCallHashes { - /// Create a [`FnCallHashes`] with only the native Rust hash. - #[inline(always)] - #[must_use] - pub const fn from_native(hash: u64) -> Self { - Self { - #[cfg(not(feature = "no_function"))] - script: 0, - native: if hash == 0 { ALT_ZERO_HASH } else { hash }, - } - } - /// Create a [`FnCallHashes`] with both native Rust and script function hashes. - #[inline(always)] - #[must_use] - pub const fn from_all(#[cfg(not(feature = "no_function"))] script: u64, native: u64) -> Self { - Self { - #[cfg(not(feature = "no_function"))] - script: if script == 0 { ALT_ZERO_HASH } else { script }, - native: if native == 0 { ALT_ZERO_HASH } else { native }, - } - } - /// Is this [`FnCallHashes`] native Rust only? - #[inline(always)] - #[must_use] - pub const fn is_native_only(&self) -> bool { - #[cfg(not(feature = "no_function"))] - return self.script == 0; - - #[cfg(feature = "no_function")] - return true; - } -} - -/// _(internals)_ A function call. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone, Default, Hash)] -pub struct FnCallExpr { - /// Namespace of the function, if any. - pub namespace: Option, - /// Function name. - pub name: Identifier, - /// Pre-calculated hashes. - pub hashes: FnCallHashes, - /// List of function call argument expressions. - pub args: StaticVec, - /// List of function call arguments that are constants. - /// - /// Any arguments in `args` that is [`Expr::Stack`] indexes into this - /// array to find the constant for use as its argument value. - /// - /// # Notes - /// - /// Constant arguments are very common in function calls, and keeping each constant in - /// an [`Expr::DynamicConstant`] involves an additional allocation. Keeping the constant - /// values in an inlined array avoids these extra allocations. - pub constants: StaticVec, - /// Does this function call capture the parent scope? - pub capture_parent_scope: bool, -} - -impl FnCallExpr { - /// Does this function call contain a qualified namespace? - #[inline(always)] - #[must_use] - pub const fn is_qualified(&self) -> bool { - self.namespace.is_some() - } - /// Convert this into an [`Expr::FnCall`]. - #[inline(always)] - #[must_use] - pub fn into_fn_call_expr(self, pos: Position) -> Expr { - Expr::FnCall(self.into(), pos) - } -} - -/// A type that wraps a floating-point number and implements [`Hash`]. -/// -/// Not available under `no_float`. -#[cfg(not(feature = "no_float"))] -#[derive(Clone, Copy, PartialEq, PartialOrd)] -pub struct FloatWrapper(F); - -#[cfg(not(feature = "no_float"))] -impl Hash for FloatWrapper { - #[inline(always)] - fn hash(&self, state: &mut H) { - self.0.to_ne_bytes().hash(state); - } -} - -#[cfg(not(feature = "no_float"))] -impl AsRef for FloatWrapper { - #[inline(always)] - fn as_ref(&self) -> &F { - &self.0 - } -} - -#[cfg(not(feature = "no_float"))] -impl AsMut for FloatWrapper { - #[inline(always)] - fn as_mut(&mut self) -> &mut F { - &mut self.0 - } -} - -#[cfg(not(feature = "no_float"))] -impl std::ops::Deref for FloatWrapper { - type Target = F; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[cfg(not(feature = "no_float"))] -impl std::ops::DerefMut for FloatWrapper { - #[inline(always)] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -#[cfg(not(feature = "no_float"))] -impl fmt::Debug for FloatWrapper { - #[inline(always)] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self.0, f) - } -} - -#[cfg(not(feature = "no_float"))] -impl> fmt::Display for FloatWrapper { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let abs = self.0.abs(); - if abs.is_zero() { - f.write_str("0.0") - } else if abs > Self::MAX_NATURAL_FLOAT_FOR_DISPLAY.into() - || abs < Self::MIN_NATURAL_FLOAT_FOR_DISPLAY.into() - { - write!(f, "{:e}", self.0) - } else { - fmt::Display::fmt(&self.0, f)?; - if abs.fract().is_zero() { - f.write_str(".0")?; - } - Ok(()) - } - } -} - -#[cfg(not(feature = "no_float"))] -impl From for FloatWrapper { - #[inline(always)] - fn from(value: F) -> Self { - Self::new(value) - } -} - -#[cfg(not(feature = "no_float"))] -impl FromStr for FloatWrapper { - type Err = ::Err; - - #[inline] - fn from_str(s: &str) -> Result { - F::from_str(s).map(Into::::into) - } -} - -#[cfg(not(feature = "no_float"))] -impl FloatWrapper { - /// Maximum floating-point number for natural display before switching to scientific notation. - pub const MAX_NATURAL_FLOAT_FOR_DISPLAY: f32 = 10000000000000.0; - - /// Minimum floating-point number for natural display before switching to scientific notation. - pub const MIN_NATURAL_FLOAT_FOR_DISPLAY: f32 = 0.0000000000001; - - /// Create a new [`FloatWrapper`]. - #[inline(always)] - #[must_use] - pub fn new(value: F) -> Self { - Self(value) - } -} - -#[cfg(not(feature = "no_float"))] -impl FloatWrapper { - /// Create a new [`FloatWrapper`]. - #[inline(always)] - #[must_use] - pub const fn new_const(value: crate::FLOAT) -> Self { - Self(value) - } -} - -/// _(internals)_ An expression sub-tree. -/// Exported under the `internals` feature only. -#[derive(Clone, Hash)] -pub enum Expr { - /// Dynamic constant. - /// - /// Used to hold complex constants such as [`Array`][crate::Array] or [`Map`][crate::Map] for quick cloning. - /// Primitive data types should use the appropriate variants to avoid an allocation. - DynamicConstant(Box, Position), - /// Boolean constant. - BoolConstant(bool, Position), - /// Integer constant. - IntegerConstant(INT, Position), - /// Floating-point constant. - /// - /// Not available under `no_float`. - #[cfg(not(feature = "no_float"))] - FloatConstant(FloatWrapper, Position), - /// Character constant. - CharConstant(char, Position), - /// [String][ImmutableString] constant. - StringConstant(ImmutableString, Position), - /// An interpolated [string][ImmutableString]. - InterpolatedString(Box>, Position), - /// [ expr, ... ] - Array(Box>, Position), - /// #{ name:expr, ... } - Map( - Box<(StaticVec<(Ident, Expr)>, BTreeMap)>, - Position, - ), - /// () - Unit(Position), - /// Variable access - optional short index, position, (optional index, optional (hash, modules), variable name) - /// - /// The short index is [`u8`] which is used when the index is <= 255, which should be the vast - /// majority of cases (unless there are more than 255 variables defined!). - /// This is to avoid reading a pointer redirection during each variable access. - Variable( - Option, - Position, - Box<( - Option, - Option<(NamespaceRef, u64)>, - Identifier, - )>, - ), - /// Property access - ((getter, hash), (setter, hash), prop) - Property( - Box<( - (Identifier, u64), - (Identifier, u64), - (ImmutableString, Position), - )>, - ), - /// Stack slot for function calls. See [`FnCallExpr`] for more details. - /// - /// This variant does not map to any language structure. It is used in function calls with - /// constant arguments where the `usize` number indexes into an array containing a list of - /// constant arguments for the function call. - Stack(usize, Position), - /// { [statement][Stmt] ... } - Stmt(Box), - /// func `(` expr `,` ... `)` - FnCall(Box, Position), - /// lhs `.` rhs - bool variable is a dummy - Dot(Box, bool, Position), - /// expr `[` expr `]` - boolean indicates whether the dotting/indexing chain stops - Index(Box, bool, Position), - /// lhs `&&` rhs - And(Box, Position), - /// lhs `||` rhs - Or(Box, Position), - /// Custom syntax - Custom(Box, Position), -} - -impl Default for Expr { - #[inline(always)] - fn default() -> Self { - Self::Unit(Position::NONE) - } -} - -impl fmt::Debug for Expr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut display_pos = self.position(); - - match self { - Self::DynamicConstant(value, _) => write!(f, "{:?}", value), - Self::BoolConstant(value, _) => write!(f, "{:?}", value), - Self::IntegerConstant(value, _) => write!(f, "{:?}", value), - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(value, _) => write!(f, "{:?}", value), - Self::CharConstant(value, _) => write!(f, "{:?}", value), - Self::StringConstant(value, _) => write!(f, "{:?}", value), - Self::Unit(_) => f.write_str("()"), - - Self::InterpolatedString(x, _) => { - f.write_str("InterpolatedString")?; - return f.debug_list().entries(x.iter()).finish(); - } - Self::Array(x, _) => { - f.write_str("Array")?; - f.debug_list().entries(x.iter()).finish() - } - Self::Map(x, _) => { - f.write_str("Map")?; - f.debug_map() - .entries(x.0.iter().map(|(k, v)| (k, v))) - .finish() - } - Self::Variable(i, _, x) => { - f.write_str("Variable(")?; - if let Some((_, ref namespace)) = x.1 { - write!(f, "{}{}", namespace, Token::DoubleColon.literal_syntax())? - } - f.write_str(&x.2)?; - if let Some(n) = i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) { - write!(f, " #{}", n)? - } - f.write_str(")") - } - Self::Property(x) => write!(f, "Property({})", (x.2).0), - Self::Stack(x, _) => write!(f, "StackSlot({})", x), - Self::Stmt(x) => { - f.write_str("ExprStmtBlock")?; - f.debug_list().entries(x.0.iter()).finish() - } - Self::FnCall(x, _) => { - let mut ff = f.debug_struct("FnCall"); - x.namespace.as_ref().map(|ns| ff.field("namespace", ns)); - ff.field("name", &x.name) - .field("hash", &x.hashes) - .field("args", &x.args); - if !x.constants.is_empty() { - ff.field("constants", &x.constants); - } - if x.capture_parent_scope { - ff.field("capture_parent_scope", &x.capture_parent_scope); - } - ff.finish() - } - Self::Index(x, term, pos) => { - display_pos = *pos; - - f.debug_struct("Index") - .field("lhs", &x.lhs) - .field("rhs", &x.rhs) - .field("terminate", term) - .finish() - } - Self::Dot(x, _, pos) | Self::And(x, pos) | Self::Or(x, pos) => { - let op_name = match self { - Self::Dot(_, _, _) => "Dot", - Self::And(_, _) => "And", - Self::Or(_, _) => "Or", - _ => unreachable!(), - }; - - display_pos = *pos; - - f.debug_struct(op_name) - .field("lhs", &x.lhs) - .field("rhs", &x.rhs) - .finish() - } - Self::Custom(x, _) => f.debug_tuple("Custom").field(x).finish(), - }?; - - display_pos.debug_print(f) - } -} - -impl Expr { - /// Get the [`Dynamic`] value of a literal constant expression. - /// - /// Returns [`None`] if the expression is not a literal constant. - #[inline] - #[must_use] - pub fn get_literal_value(&self) -> Option { - Some(match self { - Self::DynamicConstant(x, _) => x.as_ref().clone(), - Self::IntegerConstant(x, _) => (*x).into(), - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(x, _) => (*x).into(), - Self::CharConstant(x, _) => (*x).into(), - Self::StringConstant(x, _) => x.clone().into(), - Self::BoolConstant(x, _) => (*x).into(), - Self::Unit(_) => Dynamic::UNIT, - - #[cfg(not(feature = "no_index"))] - Self::Array(x, _) if self.is_constant() => { - let mut arr = crate::Array::with_capacity(x.len()); - arr.extend( - x.iter() - .map(|v| v.get_literal_value().expect("constant value")), - ); - Dynamic::from_array(arr) - } - - #[cfg(not(feature = "no_object"))] - Self::Map(x, _) if self.is_constant() => { - Dynamic::from_map(x.0.iter().fold(x.1.clone(), |mut map, (k, v)| { - let value_ref = map.get_mut(k.name.as_str()).expect("contains all keys"); - *value_ref = v.get_literal_value().expect("constant value"); - map - })) - } - - // Binary operators - Self::FnCall(x, _) if x.args.len() == 2 => match x.name.as_str() { - // x..y - OP_EXCLUSIVE_RANGE => { - if let Expr::IntegerConstant(ref start, _) = x.args[0] { - if let Expr::IntegerConstant(ref end, _) = x.args[1] { - (*start..*end).into() - } else { - return None; - } - } else { - return None; - } - } - // x..=y - OP_INCLUSIVE_RANGE => { - if let Expr::IntegerConstant(ref start, _) = x.args[0] { - if let Expr::IntegerConstant(ref end, _) = x.args[1] { - (*start..=*end).into() - } else { - return None; - } - } else { - return None; - } - } - _ => return None, - }, - - _ => return None, - }) - } - /// Create an [`Expr`] from a [`Dynamic`] value. - #[inline] - #[must_use] - pub fn from_dynamic(value: Dynamic, pos: Position) -> Self { - match value.0 { - Union::Unit(_, _, _) => Self::Unit(pos), - Union::Bool(b, _, _) => Self::BoolConstant(b, pos), - Union::Str(s, _, _) => Self::StringConstant(s, pos), - Union::Char(c, _, _) => Self::CharConstant(c, pos), - Union::Int(i, _, _) => Self::IntegerConstant(i, pos), - - #[cfg(feature = "decimal")] - Union::Decimal(value, _, _) => Self::DynamicConstant(Box::new((*value).into()), pos), - - #[cfg(not(feature = "no_float"))] - Union::Float(f, _, _) => Self::FloatConstant(f, pos), - - #[cfg(not(feature = "no_index"))] - Union::Array(a, _, _) => Self::DynamicConstant(Box::new((*a).into()), pos), - - #[cfg(not(feature = "no_object"))] - Union::Map(m, _, _) => Self::DynamicConstant(Box::new((*m).into()), pos), - - _ => Self::DynamicConstant(value.into(), pos), - } - } - /// Is the expression a simple variable access? - #[inline] - #[must_use] - pub(crate) const fn is_variable_access(&self, non_qualified: bool) -> bool { - match self { - Self::Variable(_, _, x) => !non_qualified || x.1.is_none(), - _ => false, - } - } - /// Return the variable name if the expression a simple variable access. - #[inline] - #[must_use] - pub(crate) fn get_variable_name(&self, non_qualified: bool) -> Option<&str> { - match self { - Self::Variable(_, _, x) if !non_qualified || x.1.is_none() => Some(x.2.as_str()), - _ => None, - } - } - /// Get the [position][Position] of the expression. - #[inline] - #[must_use] - pub const fn position(&self) -> Position { - match self { - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(_, pos) => *pos, - - Self::DynamicConstant(_, pos) - | Self::BoolConstant(_, pos) - | Self::IntegerConstant(_, pos) - | Self::CharConstant(_, pos) - | Self::Unit(pos) - | Self::StringConstant(_, pos) - | Self::Array(_, pos) - | Self::Map(_, pos) - | Self::Variable(_, pos, _) - | Self::Stack(_, pos) - | Self::FnCall(_, pos) - | Self::Custom(_, pos) - | Self::InterpolatedString(_, pos) => *pos, - - Self::Property(x) => (x.2).1, - Self::Stmt(x) => x.1, - - Self::And(x, _) | Self::Or(x, _) | Self::Dot(x, _, _) | Self::Index(x, _, _) => { - x.lhs.position() - } - } - } - /// Override the [position][Position] of the expression. - #[inline] - pub fn set_position(&mut self, new_pos: Position) -> &mut Self { - match self { - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(_, pos) => *pos = new_pos, - - Self::DynamicConstant(_, pos) - | Self::BoolConstant(_, pos) - | Self::IntegerConstant(_, pos) - | Self::CharConstant(_, pos) - | Self::Unit(pos) - | Self::StringConstant(_, pos) - | Self::Array(_, pos) - | Self::Map(_, pos) - | Self::And(_, pos) - | Self::Or(_, pos) - | Self::Dot(_, _, pos) - | Self::Index(_, _, pos) - | Self::Variable(_, pos, _) - | Self::Stack(_, pos) - | Self::FnCall(_, pos) - | Self::Custom(_, pos) - | Self::InterpolatedString(_, pos) => *pos = new_pos, - - Self::Property(x) => (x.2).1 = new_pos, - Self::Stmt(x) => x.1 = new_pos, - } - - self - } - /// Is the expression pure? - /// - /// A pure expression has no side effects. - #[inline] - #[must_use] - pub fn is_pure(&self) -> bool { - match self { - Self::InterpolatedString(x, _) | Self::Array(x, _) => x.iter().all(Self::is_pure), - - Self::Map(x, _) => x.0.iter().map(|(_, v)| v).all(Self::is_pure), - - Self::And(x, _) | Self::Or(x, _) => x.lhs.is_pure() && x.rhs.is_pure(), - - Self::Stmt(x) => x.0.iter().all(Stmt::is_pure), - - Self::Variable(_, _, _) | Self::Stack(_, _) => true, - - _ => self.is_constant(), - } - } - /// Is the expression the unit `()` literal? - #[inline(always)] - #[must_use] - pub const fn is_unit(&self) -> bool { - matches!(self, Self::Unit(_)) - } - /// Is the expression a constant? - #[inline] - #[must_use] - pub fn is_constant(&self) -> bool { - match self { - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(_, _) => true, - - Self::DynamicConstant(_, _) - | Self::BoolConstant(_, _) - | Self::IntegerConstant(_, _) - | Self::CharConstant(_, _) - | Self::StringConstant(_, _) - | Self::Unit(_) - | Self::Stack(_, _) => true, - - Self::InterpolatedString(x, _) | Self::Array(x, _) => x.iter().all(Self::is_constant), - - Self::Map(x, _) => x.0.iter().map(|(_, expr)| expr).all(Self::is_constant), - - _ => false, - } - } - /// Is a particular [token][Token] allowed as a postfix operator to this expression? - #[inline] - #[must_use] - pub const fn is_valid_postfix(&self, token: &Token) -> bool { - match token { - #[cfg(not(feature = "no_object"))] - Token::Period => return true, - _ => (), - } - - match self { - #[cfg(not(feature = "no_float"))] - Self::FloatConstant(_, _) => false, - - Self::DynamicConstant(_, _) - | Self::BoolConstant(_, _) - | Self::CharConstant(_, _) - | Self::And(_, _) - | Self::Or(_, _) - | Self::Unit(_) => false, - - Self::IntegerConstant(_, _) - | Self::StringConstant(_, _) - | Self::InterpolatedString(_, _) - | Self::FnCall(_, _) - | Self::Stmt(_) - | Self::Dot(_, _, _) - | Self::Index(_, _, _) - | Self::Array(_, _) - | Self::Map(_, _) => match token { - #[cfg(not(feature = "no_index"))] - Token::LeftBracket => true, - _ => false, - }, - - Self::Variable(_, _, _) => match token { - #[cfg(not(feature = "no_index"))] - Token::LeftBracket => true, - Token::LeftParen => true, - Token::Bang => true, - Token::DoubleColon => true, - _ => false, - }, - - Self::Property(_) => match token { - #[cfg(not(feature = "no_index"))] - Token::LeftBracket => true, - Token::LeftParen => true, - _ => false, - }, - - Self::Custom(_, _) => false, - - Self::Stack(_, _) => false, - } - } - /// Recursively walk this expression. - /// Return `false` from the callback to terminate the walk. - pub fn walk<'a>( - &'a self, - path: &mut Vec>, - on_node: &mut impl FnMut(&[ASTNode]) -> bool, - ) -> bool { - // Push the current node onto the path - path.push(self.into()); - - if !on_node(path) { - return false; - } - - match self { - Self::Stmt(x) => { - for s in &x.0 { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::InterpolatedString(x, _) | Self::Array(x, _) => { - for e in x.as_ref() { - if !e.walk(path, on_node) { - return false; - } - } - } - Self::Map(x, _) => { - for (_, e) in &x.0 { - if !e.walk(path, on_node) { - return false; - } - } - } - Self::Index(x, _, _) | Self::Dot(x, _, _) | Expr::And(x, _) | Expr::Or(x, _) => { - if !x.lhs.walk(path, on_node) { - return false; - } - if !x.rhs.walk(path, on_node) { - return false; - } - } - Self::FnCall(x, _) => { - for e in &x.args { - if !e.walk(path, on_node) { - return false; - } - } - } - Self::Custom(x, _) => { - for e in &x.inputs { - if !e.walk(path, on_node) { - return false; - } - } - } - _ => (), - } - - path.pop().expect("contains current node"); - - true - } -} - -impl AST { - /// _(internals)_ Get the internal [`Module`] containing all script-defined functions. - /// Exported under the `internals` feature only. - /// - /// Not available under `no_function`. - /// - /// # Deprecated - /// - /// This method is deprecated. Use [`shared_lib`][AST::shared_lib] instead. - /// - /// This method will be removed in the next major version. - #[deprecated(since = "1.3.0", note = "use `shared_lib` instead")] - #[cfg(feature = "internals")] - #[cfg(not(feature = "no_function"))] - #[inline(always)] - #[must_use] - pub fn lib(&self) -> &crate::Module { - &self.functions - } -} diff --git a/src/ast/ast.rs b/src/ast/ast.rs new file mode 100644 index 00000000..3a7b96b9 --- /dev/null +++ b/src/ast/ast.rs @@ -0,0 +1,863 @@ +//! Module defining the AST (abstract syntax tree). + +use super::{Expr, FnAccess, Stmt, StmtBlock, AST_OPTION_FLAGS}; +use crate::{Dynamic, FnNamespace, Identifier, Position, StaticVec}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; +use std::{ + hash::Hash, + ops::{Add, AddAssign}, +}; + +/// Compiled AST (abstract syntax tree) of a Rhai script. +/// +/// # Thread Safety +/// +/// Currently, [`AST`] is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. +#[derive(Debug, Clone)] +pub struct AST { + /// Source of the [`AST`]. + /// No source if string is empty. + source: Identifier, + /// Global statements. + body: StmtBlock, + /// Script-defined functions. + #[cfg(not(feature = "no_function"))] + functions: crate::Shared, + /// Embedded module resolver, if any. + #[cfg(not(feature = "no_module"))] + resolver: Option>, +} + +impl Default for AST { + #[inline(always)] + fn default() -> Self { + Self::empty() + } +} + +impl AST { + /// Create a new [`AST`]. + #[cfg(not(feature = "internals"))] + #[inline(always)] + #[must_use] + pub(crate) fn new( + statements: impl IntoIterator, + #[cfg(not(feature = "no_function"))] functions: impl Into>, + ) -> Self { + Self { + source: Identifier::new_const(), + body: StmtBlock::new(statements, Position::NONE), + #[cfg(not(feature = "no_function"))] + functions: functions.into(), + #[cfg(not(feature = "no_module"))] + resolver: None, + } + } + /// _(internals)_ Create a new [`AST`]. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] + #[inline(always)] + #[must_use] + pub fn new( + statements: impl IntoIterator, + #[cfg(not(feature = "no_function"))] functions: impl Into>, + ) -> Self { + Self { + source: Identifier::new_const(), + body: StmtBlock::new(statements, Position::NONE), + #[cfg(not(feature = "no_function"))] + functions: functions.into(), + #[cfg(not(feature = "no_module"))] + resolver: None, + } + } + /// Create a new [`AST`] with a source name. + #[cfg(not(feature = "internals"))] + #[inline(always)] + #[must_use] + pub(crate) fn new_with_source( + statements: impl IntoIterator, + #[cfg(not(feature = "no_function"))] functions: impl Into>, + source: impl Into, + ) -> Self { + let mut ast = Self::new( + statements, + #[cfg(not(feature = "no_function"))] + functions, + ); + ast.set_source(source); + ast + } + /// _(internals)_ Create a new [`AST`] with a source name. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] + #[inline(always)] + #[must_use] + pub fn new_with_source( + statements: impl IntoIterator, + #[cfg(not(feature = "no_function"))] functions: impl Into>, + source: impl Into, + ) -> Self { + let mut ast = Self::new( + statements, + #[cfg(not(feature = "no_function"))] + functions, + ); + ast.set_source(source); + ast + } + /// Create an empty [`AST`]. + #[inline] + #[must_use] + pub fn empty() -> Self { + Self { + source: Identifier::new_const(), + body: StmtBlock::NONE, + #[cfg(not(feature = "no_function"))] + functions: crate::Module::new().into(), + #[cfg(not(feature = "no_module"))] + resolver: None, + } + } + /// Get the source, if any. + #[inline(always)] + #[must_use] + pub fn source(&self) -> Option<&str> { + match self.source.as_str() { + "" => None, + s => Some(s), + } + } + /// Get a reference to the source. + #[inline(always)] + #[must_use] + pub(crate) fn source_raw(&self) -> &Identifier { + &self.source + } + /// Set the source. + #[inline] + pub fn set_source(&mut self, source: impl Into) -> &mut Self { + let source = source.into(); + #[cfg(not(feature = "no_function"))] + crate::Shared::get_mut(&mut self.functions) + .as_mut() + .map(|m| m.set_id(source.clone())); + self.source = source; + self + } + /// Clear the source. + #[inline(always)] + pub fn clear_source(&mut self) -> &mut Self { + self.source.clear(); + self + } + /// Get the statements. + #[cfg(not(feature = "internals"))] + #[inline(always)] + #[must_use] + pub(crate) fn statements(&self) -> &[Stmt] { + self.body.statements() + } + /// _(internals)_ Get the statements. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] + #[inline(always)] + #[must_use] + pub fn statements(&self) -> &[Stmt] { + self.body.statements() + } + /// Extract the statements. + #[allow(dead_code)] + #[inline(always)] + #[must_use] + pub(crate) fn take_statements(&mut self) -> StaticVec { + self.body.take_statements() + } + /// Does this [`AST`] contain script-defined functions? + /// + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub fn has_functions(&self) -> bool { + !self.functions.is_empty() + } + /// Get the internal shared [`Module`][crate::Module] containing all script-defined functions. + #[cfg(not(feature = "internals"))] + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub(crate) fn shared_lib(&self) -> &crate::Shared { + &self.functions + } + /// _(internals)_ Get the internal shared [`Module`][crate::Module] containing all script-defined functions. + /// Exported under the `internals` feature only. + /// + /// Not available under `no_function`. + #[cfg(feature = "internals")] + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub fn shared_lib(&self) -> &crate::Shared { + &self.functions + } + /// Get the embedded [module resolver][`ModuleResolver`]. + #[cfg(not(feature = "internals"))] + #[cfg(not(feature = "no_module"))] + #[inline(always)] + #[must_use] + pub(crate) fn resolver( + &self, + ) -> Option<&crate::Shared> { + self.resolver.as_ref() + } + /// _(internals)_ Get the embedded [module resolver][crate::ModuleResolver]. + /// Exported under the `internals` feature only. + /// + /// Not available under `no_module`. + #[cfg(feature = "internals")] + #[cfg(not(feature = "no_module"))] + #[inline(always)] + #[must_use] + pub fn resolver( + &self, + ) -> Option<&crate::Shared> { + self.resolver.as_ref() + } + /// Set the embedded [module resolver][`ModuleResolver`]. + #[cfg(not(feature = "no_module"))] + #[inline(always)] + pub(crate) fn set_resolver( + &mut self, + resolver: impl Into>, + ) -> &mut Self { + self.resolver = Some(resolver.into()); + self + } + /// Clone the [`AST`]'s functions into a new [`AST`]. + /// No statements are cloned. + /// + /// Not available under `no_function`. + /// + /// This operation is cheap because functions are shared. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub fn clone_functions_only(&self) -> Self { + self.clone_functions_only_filtered(|_, _, _, _, _| true) + } + /// Clone the [`AST`]'s functions into a new [`AST`] based on a filter predicate. + /// No statements are cloned. + /// + /// Not available under `no_function`. + /// + /// This operation is cheap because functions are shared. + #[cfg(not(feature = "no_function"))] + #[inline] + #[must_use] + pub fn clone_functions_only_filtered( + &self, + filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, + ) -> Self { + let mut functions = crate::Module::new(); + functions.merge_filtered(&self.functions, &filter); + Self { + source: self.source.clone(), + body: StmtBlock::NONE, + functions: functions.into(), + #[cfg(not(feature = "no_module"))] + resolver: self.resolver.clone(), + } + } + /// Clone the [`AST`]'s script statements into a new [`AST`]. + /// No functions are cloned. + #[inline(always)] + #[must_use] + pub fn clone_statements_only(&self) -> Self { + Self { + source: self.source.clone(), + body: self.body.clone(), + #[cfg(not(feature = "no_function"))] + functions: crate::Module::new().into(), + #[cfg(not(feature = "no_module"))] + resolver: self.resolver.clone(), + } + } + /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, + /// version is returned. + /// + /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. + /// Of course, if the first [`AST`] uses a `return` statement at the end, then + /// the second [`AST`] will essentially be dead code. + /// + /// All script-defined functions in the second [`AST`] overwrite similarly-named functions + /// in the first [`AST`] with the same number of parameters. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let ast1 = engine.compile(" + /// fn foo(x) { 42 + x } + /// foo(1) + /// ")?; + /// + /// let ast2 = engine.compile(r#" + /// fn foo(n) { `hello${n}` } + /// foo("!") + /// "#)?; + /// + /// let ast = ast1.merge(&ast2); // Merge 'ast2' into 'ast1' + /// + /// // Notice that using the '+' operator also works: + /// // let ast = &ast1 + &ast2; + /// + /// // 'ast' is essentially: + /// // + /// // fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten + /// // foo(1) // <- notice this will be "hello1" instead of 43, + /// // // but it is no longer the return value + /// // foo("!") // returns "hello!" + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast)?, "hello!"); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + #[must_use] + pub fn merge(&self, other: &Self) -> Self { + self.merge_filtered_impl(other, |_, _, _, _, _| true) + } + /// Combine one [`AST`] with another. The second [`AST`] is consumed. + /// + /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. + /// Of course, if the first [`AST`] uses a `return` statement at the end, then + /// the second [`AST`] will essentially be dead code. + /// + /// All script-defined functions in the second [`AST`] overwrite similarly-named functions + /// in the first [`AST`] with the same number of parameters. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let mut ast1 = engine.compile(" + /// fn foo(x) { 42 + x } + /// foo(1) + /// ")?; + /// + /// let ast2 = engine.compile(r#" + /// fn foo(n) { `hello${n}` } + /// foo("!") + /// "#)?; + /// + /// ast1.combine(ast2); // Combine 'ast2' into 'ast1' + /// + /// // Notice that using the '+=' operator also works: + /// // ast1 += ast2; + /// + /// // 'ast1' is essentially: + /// // + /// // fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten + /// // foo(1) // <- notice this will be "hello1" instead of 43, + /// // // but it is no longer the return value + /// // foo("!") // returns "hello!" + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast1)?, "hello!"); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn combine(&mut self, other: Self) -> &mut Self { + self.combine_filtered_impl(other, |_, _, _, _, _| true) + } + /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version + /// is returned. + /// + /// Not available under `no_function`. + /// + /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. + /// Of course, if the first [`AST`] uses a `return` statement at the end, then + /// the second [`AST`] will essentially be dead code. + /// + /// All script-defined functions in the second [`AST`] are first selected based on a filter + /// predicate, then overwrite similarly-named functions in the first [`AST`] with the + /// same number of parameters. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let ast1 = engine.compile(" + /// fn foo(x) { 42 + x } + /// foo(1) + /// ")?; + /// + /// let ast2 = engine.compile(r#" + /// fn foo(n) { `hello${n}` } + /// fn error() { 0 } + /// foo("!") + /// "#)?; + /// + /// // Merge 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1' + /// let ast = ast1.merge_filtered(&ast2, |_, _, script, name, params| + /// script && name == "error" && params == 0); + /// + /// // 'ast' is essentially: + /// // + /// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten + /// // // because 'ast2::foo' is filtered away + /// // foo(1) // <- notice this will be 43 instead of "hello1", + /// // // but it is no longer the return value + /// // fn error() { 0 } // <- this function passes the filter and is merged + /// // foo("!") // <- returns "42!" + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast)?, "42!"); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub fn merge_filtered( + &self, + other: &Self, + filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, + ) -> Self { + self.merge_filtered_impl(other, filter) + } + /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version + /// is returned. + #[inline] + #[must_use] + fn merge_filtered_impl( + &self, + other: &Self, + _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, + ) -> Self { + let merged = match (self.body.is_empty(), other.body.is_empty()) { + (false, false) => { + let mut body = self.body.clone(); + body.extend(other.body.iter().cloned()); + body + } + (false, true) => self.body.clone(), + (true, false) => other.body.clone(), + (true, true) => StmtBlock::NONE, + }; + + #[cfg(not(feature = "no_function"))] + let functions = { + let mut functions = self.functions.as_ref().clone(); + functions.merge_filtered(&other.functions, &_filter); + functions + }; + + if !other.source.is_empty() { + Self::new_with_source( + merged, + #[cfg(not(feature = "no_function"))] + functions, + other.source.clone(), + ) + } else { + Self::new( + merged, + #[cfg(not(feature = "no_function"))] + functions, + ) + } + } + /// Combine one [`AST`] with another. The second [`AST`] is consumed. + /// + /// Not available under `no_function`. + /// + /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. + /// Of course, if the first [`AST`] uses a `return` statement at the end, then + /// the second [`AST`] will essentially be dead code. + /// + /// All script-defined functions in the second [`AST`] are first selected based on a filter + /// predicate, then overwrite similarly-named functions in the first [`AST`] with the + /// same number of parameters. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let mut ast1 = engine.compile(" + /// fn foo(x) { 42 + x } + /// foo(1) + /// ")?; + /// + /// let ast2 = engine.compile(r#" + /// fn foo(n) { `hello${n}` } + /// fn error() { 0 } + /// foo("!") + /// "#)?; + /// + /// // Combine 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1' + /// ast1.combine_filtered(ast2, |_, _, script, name, params| + /// script && name == "error" && params == 0); + /// + /// // 'ast1' is essentially: + /// // + /// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten + /// // // because 'ast2::foo' is filtered away + /// // foo(1) // <- notice this will be 43 instead of "hello1", + /// // // but it is no longer the return value + /// // fn error() { 0 } // <- this function passes the filter and is merged + /// // foo("!") // <- returns "42!" + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast1)?, "42!"); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_function"))] + #[inline(always)] + pub fn combine_filtered( + &mut self, + other: Self, + filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, + ) -> &mut Self { + self.combine_filtered_impl(other, filter) + } + /// Combine one [`AST`] with another. The second [`AST`] is consumed. + #[inline] + fn combine_filtered_impl( + &mut self, + other: Self, + _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, + ) -> &mut Self { + self.body.extend(other.body.into_iter()); + + #[cfg(not(feature = "no_function"))] + if !other.functions.is_empty() { + crate::func::native::shared_make_mut(&mut self.functions) + .merge_filtered(&other.functions, &_filter); + } + self + } + /// Filter out the functions, retaining only some based on a filter predicate. + /// + /// Not available under `no_function`. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let mut ast = engine.compile(r#" + /// fn foo(n) { n + 1 } + /// fn bar() { print("hello"); } + /// "#)?; + /// + /// // Remove all functions except 'foo(_)' + /// ast.retain_functions(|_, _, name, params| name == "foo" && params == 1); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_function"))] + #[inline] + pub fn retain_functions( + &mut self, + filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool, + ) -> &mut Self { + if !self.functions.is_empty() { + crate::func::native::shared_make_mut(&mut self.functions) + .retain_script_functions(filter); + } + self + } + /// Iterate through all function definitions. + /// + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] + #[allow(dead_code)] + #[inline] + pub(crate) fn iter_fn_def(&self) -> impl Iterator { + self.functions + .iter_script_fn() + .map(|(_, _, _, _, fn_def)| fn_def.as_ref()) + } + /// Iterate through all function definitions. + /// + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] + #[inline] + pub fn iter_functions<'a>(&'a self) -> impl Iterator + 'a { + self.functions + .iter_script_fn() + .map(|(_, _, _, _, fn_def)| fn_def.as_ref().into()) + } + /// Clear all function definitions in the [`AST`]. + /// + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + pub fn clear_functions(&mut self) -> &mut Self { + self.functions = crate::Module::new().into(); + self + } + /// Clear all statements in the [`AST`], leaving only function definitions. + #[inline(always)] + pub fn clear_statements(&mut self) -> &mut Self { + self.body = StmtBlock::NONE; + self + } + /// Extract all top-level literal constant and/or variable definitions. + /// This is useful for extracting all global constants from a script without actually running it. + /// + /// A literal constant/variable definition takes the form of: + /// `const VAR = `_value_`;` and `let VAR = `_value_`;` + /// where _value_ is a literal expression or will be optimized into a literal. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Scope}; + /// + /// let engine = Engine::new(); + /// + /// let ast = engine.compile( + /// " + /// const A = 40 + 2; // constant that optimizes into a literal + /// let b = 123; // literal variable + /// const B = b * A; // non-literal constant + /// const C = 999; // literal constant + /// b = A + C; // expression + /// + /// { // <- new block scope + /// const Z = 0; // <- literal constant not at top-level + /// } + /// ")?; + /// + /// let mut iter = ast.iter_literal_variables(true, false) + /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); + /// + /// # #[cfg(not(feature = "no_optimize"))] + /// assert_eq!(iter.next(), Some(("A", true, 42))); + /// assert_eq!(iter.next(), Some(("C", true, 999))); + /// assert_eq!(iter.next(), None); + /// + /// let mut iter = ast.iter_literal_variables(false, true) + /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); + /// + /// assert_eq!(iter.next(), Some(("b", false, 123))); + /// assert_eq!(iter.next(), None); + /// + /// let mut iter = ast.iter_literal_variables(true, true) + /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); + /// + /// # #[cfg(not(feature = "no_optimize"))] + /// assert_eq!(iter.next(), Some(("A", true, 42))); + /// assert_eq!(iter.next(), Some(("b", false, 123))); + /// assert_eq!(iter.next(), Some(("C", true, 999))); + /// assert_eq!(iter.next(), None); + /// + /// let scope: Scope = ast.iter_literal_variables(true, false).collect(); + /// + /// # #[cfg(not(feature = "no_optimize"))] + /// assert_eq!(scope.len(), 2); + /// + /// Ok(()) + /// # } + /// ``` + pub fn iter_literal_variables( + &self, + include_constants: bool, + include_variables: bool, + ) -> impl Iterator { + self.statements().iter().filter_map(move |stmt| match stmt { + Stmt::Var(expr, name, options, _) + if options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT) && include_constants + || !options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT) + && include_variables => + { + if let Some(value) = expr.get_literal_value() { + Some(( + name.as_str(), + options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT), + value, + )) + } else { + None + } + } + _ => None, + }) + } + /// Recursively walk the [`AST`], including function bodies (if any). + /// Return `false` from the callback to terminate the walk. + #[cfg(not(feature = "internals"))] + #[cfg(not(feature = "no_module"))] + #[inline] + pub(crate) fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool { + let path = &mut Vec::new(); + + for stmt in self.statements() { + if !stmt.walk(path, on_node) { + return false; + } + } + #[cfg(not(feature = "no_function"))] + for stmt in self.iter_fn_def().flat_map(|f| f.body.iter()) { + if !stmt.walk(path, on_node) { + return false; + } + } + + true + } + /// _(internals)_ Recursively walk the [`AST`], including function bodies (if any). + /// Return `false` from the callback to terminate the walk. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] + #[inline] + pub fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool { + let path = &mut Vec::new(); + + for stmt in self.statements() { + if !stmt.walk(path, on_node) { + return false; + } + } + #[cfg(not(feature = "no_function"))] + for stmt in self.iter_fn_def().flat_map(|f| f.body.iter()) { + if !stmt.walk(path, on_node) { + return false; + } + } + + true + } +} + +impl> Add for &AST { + type Output = AST; + + #[inline(always)] + fn add(self, rhs: A) -> Self::Output { + self.merge(rhs.as_ref()) + } +} + +impl> AddAssign for AST { + #[inline(always)] + fn add_assign(&mut self, rhs: A) { + self.combine(rhs.into()); + } +} + +impl AsRef<[Stmt]> for AST { + #[inline(always)] + fn as_ref(&self) -> &[Stmt] { + self.statements() + } +} + +#[cfg(not(feature = "no_function"))] +impl AsRef for AST { + #[inline(always)] + fn as_ref(&self) -> &crate::Module { + self.shared_lib().as_ref() + } +} + +#[cfg(not(feature = "no_function"))] +impl AsRef> for AST { + #[inline(always)] + fn as_ref(&self) -> &crate::Shared { + self.shared_lib() + } +} + +/// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`]. +/// Exported under the `internals` feature only. +#[derive(Debug, Clone, Hash)] +pub enum ASTNode<'a> { + /// A statement ([`Stmt`]). + Stmt(&'a Stmt), + /// An expression ([`Expr`]). + Expr(&'a Expr), +} + +impl<'a> From<&'a Stmt> for ASTNode<'a> { + fn from(stmt: &'a Stmt) -> Self { + Self::Stmt(stmt) + } +} + +impl<'a> From<&'a Expr> for ASTNode<'a> { + fn from(expr: &'a Expr) -> Self { + Self::Expr(expr) + } +} + +impl ASTNode<'_> { + /// Get the [`Position`] of this [`ASTNode`]. + pub const fn position(&self) -> Position { + match self { + ASTNode::Stmt(stmt) => stmt.position(), + ASTNode::Expr(expr) => expr.position(), + } + } +} + +impl AST { + /// _(internals)_ Get the internal [`Module`][crate::Module] containing all script-defined functions. + /// Exported under the `internals` feature only. + /// + /// Not available under `no_function`. + /// + /// # Deprecated + /// + /// This method is deprecated. Use [`shared_lib`][AST::shared_lib] instead. + /// + /// This method will be removed in the next major version. + #[deprecated(since = "1.3.0", note = "use `shared_lib` instead")] + #[cfg(feature = "internals")] + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub fn lib(&self) -> &crate::Module { + &self.functions + } +} diff --git a/src/ast/expr.rs b/src/ast/expr.rs new file mode 100644 index 00000000..f59efa87 --- /dev/null +++ b/src/ast/expr.rs @@ -0,0 +1,831 @@ +//! Module defining script expressions. + +use super::{ASTNode, Ident, Stmt, StmtBlock}; +use crate::engine::{OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE}; +use crate::func::hashing::ALT_ZERO_HASH; +use crate::module::Namespace; +use crate::tokenizer::Token; +use crate::types::dynamic::Union; +use crate::{Dynamic, Identifier, ImmutableString, Position, StaticVec, INT}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; +use std::{ + collections::BTreeMap, + fmt, + hash::Hash, + num::{NonZeroU8, NonZeroUsize}, +}; + +#[cfg(not(feature = "no_float"))] +use std::str::FromStr; + +#[cfg(not(feature = "no_float"))] +use num_traits::Float; + +/// _(internals)_ A binary expression. +/// Exported under the `internals` feature only. +#[derive(Debug, Clone, Hash)] +pub struct BinaryExpr { + /// LHS expression. + pub lhs: Expr, + /// RHS expression. + pub rhs: Expr, +} + +/// _(internals)_ A custom syntax expression. +/// Exported under the `internals` feature only. +#[derive(Debug, Clone, Hash)] +pub struct CustomExpr { + /// List of keywords. + pub inputs: StaticVec, + /// List of tokens actually parsed. + pub tokens: StaticVec, + /// Is the current [`Scope`][crate::Scope] possibly modified by this custom statement + /// (e.g. introducing a new variable)? + pub scope_may_be_changed: bool, + /// Is this custom syntax self-terminated? + pub self_terminated: bool, +} + +impl CustomExpr { + /// Is this custom syntax self-terminated (i.e. no need for a semicolon terminator)? + /// + /// A self-terminated custom syntax always ends in `$block$`, `}` or `;` + #[must_use] + #[inline(always)] + pub const fn is_self_terminated(&self) -> bool { + self.self_terminated + } +} + +/// _(internals)_ A set of function call hashes. Exported under the `internals` feature only. +/// +/// Two separate hashes are pre-calculated because of the following patterns: +/// +/// ```js +/// func(a, b, c); // Native: func(a, b, c) - 3 parameters +/// // Script: func(a, b, c) - 3 parameters +/// +/// a.func(b, c); // Native: func(&mut a, b, c) - 3 parameters +/// // Script: func(b, c) - 2 parameters +/// ``` +/// +/// For normal function calls, the native hash equals the script hash. +/// +/// For method-style calls, the script hash contains one fewer parameter. +/// +/// Function call hashes are used in the following manner: +/// +/// * First, the script hash is tried, which contains only the called function's name plus the +/// number of parameters. +/// +/// * Next, the actual types of arguments are hashed and _combined_ with the native hash, which is +/// then used to search for a native function. In other words, a complete native function call +/// hash always contains the called function's name plus the types of the arguments. This is due +/// to possible function overloading for different parameter types. +#[derive(Clone, Copy, Eq, PartialEq, Hash, Default)] +pub struct FnCallHashes { + /// Pre-calculated hash for a script-defined function (zero if native functions only). + #[cfg(not(feature = "no_function"))] + pub script: u64, + /// Pre-calculated hash for a native Rust function with no parameter types. + pub native: u64, +} + +impl fmt::Debug for FnCallHashes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[cfg(not(feature = "no_function"))] + if self.script != 0 { + return if self.script == self.native { + fmt::Debug::fmt(&self.native, f) + } else { + write!(f, "({}, {})", self.script, self.native) + }; + } + + write!(f, "{} (native only)", self.native) + } +} + +impl From for FnCallHashes { + #[inline(always)] + fn from(hash: u64) -> Self { + let hash = if hash == 0 { ALT_ZERO_HASH } else { hash }; + + Self { + #[cfg(not(feature = "no_function"))] + script: hash, + native: hash, + } + } +} + +impl FnCallHashes { + /// Create a [`FnCallHashes`] with only the native Rust hash. + #[inline(always)] + #[must_use] + pub const fn from_native(hash: u64) -> Self { + Self { + #[cfg(not(feature = "no_function"))] + script: 0, + native: if hash == 0 { ALT_ZERO_HASH } else { hash }, + } + } + /// Create a [`FnCallHashes`] with both native Rust and script function hashes. + #[inline(always)] + #[must_use] + pub const fn from_all(#[cfg(not(feature = "no_function"))] script: u64, native: u64) -> Self { + Self { + #[cfg(not(feature = "no_function"))] + script: if script == 0 { ALT_ZERO_HASH } else { script }, + native: if native == 0 { ALT_ZERO_HASH } else { native }, + } + } + /// Is this [`FnCallHashes`] native Rust only? + #[inline(always)] + #[must_use] + pub const fn is_native_only(&self) -> bool { + #[cfg(not(feature = "no_function"))] + return self.script == 0; + + #[cfg(feature = "no_function")] + return true; + } +} + +/// _(internals)_ A function call. +/// Exported under the `internals` feature only. +#[derive(Debug, Clone, Default, Hash)] +pub struct FnCallExpr { + /// Namespace of the function, if any. + pub namespace: Option, + /// Function name. + pub name: Identifier, + /// Pre-calculated hashes. + pub hashes: FnCallHashes, + /// List of function call argument expressions. + pub args: StaticVec, + /// List of function call arguments that are constants. + /// + /// Any arguments in `args` that is [`Expr::Stack`] indexes into this + /// array to find the constant for use as its argument value. + /// + /// # Notes + /// + /// Constant arguments are very common in function calls, and keeping each constant in + /// an [`Expr::DynamicConstant`] involves an additional allocation. Keeping the constant + /// values in an inlined array avoids these extra allocations. + pub constants: StaticVec, + /// Does this function call capture the parent scope? + pub capture_parent_scope: bool, +} + +impl FnCallExpr { + /// Does this function call contain a qualified namespace? + #[inline(always)] + #[must_use] + pub const fn is_qualified(&self) -> bool { + self.namespace.is_some() + } + /// Convert this into an [`Expr::FnCall`]. + #[inline(always)] + #[must_use] + pub fn into_fn_call_expr(self, pos: Position) -> Expr { + Expr::FnCall(self.into(), pos) + } +} + +/// A type that wraps a floating-point number and implements [`Hash`]. +/// +/// Not available under `no_float`. +#[cfg(not(feature = "no_float"))] +#[derive(Clone, Copy, PartialEq, PartialOrd)] +pub struct FloatWrapper(F); + +#[cfg(not(feature = "no_float"))] +impl Hash for FloatWrapper { + #[inline(always)] + fn hash(&self, state: &mut H) { + self.0.to_ne_bytes().hash(state); + } +} + +#[cfg(not(feature = "no_float"))] +impl AsRef for FloatWrapper { + #[inline(always)] + fn as_ref(&self) -> &F { + &self.0 + } +} + +#[cfg(not(feature = "no_float"))] +impl AsMut for FloatWrapper { + #[inline(always)] + fn as_mut(&mut self) -> &mut F { + &mut self.0 + } +} + +#[cfg(not(feature = "no_float"))] +impl std::ops::Deref for FloatWrapper { + type Target = F; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[cfg(not(feature = "no_float"))] +impl std::ops::DerefMut for FloatWrapper { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[cfg(not(feature = "no_float"))] +impl fmt::Debug for FloatWrapper { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } +} + +#[cfg(not(feature = "no_float"))] +impl> fmt::Display for FloatWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let abs = self.0.abs(); + if abs.is_zero() { + f.write_str("0.0") + } else if abs > Self::MAX_NATURAL_FLOAT_FOR_DISPLAY.into() + || abs < Self::MIN_NATURAL_FLOAT_FOR_DISPLAY.into() + { + write!(f, "{:e}", self.0) + } else { + fmt::Display::fmt(&self.0, f)?; + if abs.fract().is_zero() { + f.write_str(".0")?; + } + Ok(()) + } + } +} + +#[cfg(not(feature = "no_float"))] +impl From for FloatWrapper { + #[inline(always)] + fn from(value: F) -> Self { + Self::new(value) + } +} + +#[cfg(not(feature = "no_float"))] +impl FromStr for FloatWrapper { + type Err = ::Err; + + #[inline] + fn from_str(s: &str) -> Result { + F::from_str(s).map(Into::into) + } +} + +#[cfg(not(feature = "no_float"))] +impl FloatWrapper { + /// Maximum floating-point number for natural display before switching to scientific notation. + pub const MAX_NATURAL_FLOAT_FOR_DISPLAY: f32 = 10000000000000.0; + + /// Minimum floating-point number for natural display before switching to scientific notation. + pub const MIN_NATURAL_FLOAT_FOR_DISPLAY: f32 = 0.0000000000001; + + /// Create a new [`FloatWrapper`]. + #[inline(always)] + #[must_use] + pub fn new(value: F) -> Self { + Self(value) + } +} + +#[cfg(not(feature = "no_float"))] +impl FloatWrapper { + /// Create a new [`FloatWrapper`]. + #[inline(always)] + #[must_use] + pub const fn new_const(value: crate::FLOAT) -> Self { + Self(value) + } +} + +/// _(internals)_ An expression sub-tree. +/// Exported under the `internals` feature only. +#[derive(Clone, Hash)] +pub enum Expr { + /// Dynamic constant. + /// + /// Used to hold complex constants such as [`Array`][crate::Array] or [`Map`][crate::Map] for quick cloning. + /// Primitive data types should use the appropriate variants to avoid an allocation. + DynamicConstant(Box, Position), + /// Boolean constant. + BoolConstant(bool, Position), + /// Integer constant. + IntegerConstant(INT, Position), + /// Floating-point constant. + /// + /// Not available under `no_float`. + #[cfg(not(feature = "no_float"))] + FloatConstant(FloatWrapper, Position), + /// Character constant. + CharConstant(char, Position), + /// [String][ImmutableString] constant. + StringConstant(ImmutableString, Position), + /// An interpolated [string][ImmutableString]. + InterpolatedString(Box>, Position), + /// [ expr, ... ] + Array(Box>, Position), + /// #{ name:expr, ... } + Map( + Box<(StaticVec<(Ident, Expr)>, BTreeMap)>, + Position, + ), + /// () + Unit(Position), + /// Variable access - optional short index, position, (optional index, optional (hash, modules), variable name) + /// + /// The short index is [`u8`] which is used when the index is <= 255, which should be the vast + /// majority of cases (unless there are more than 255 variables defined!). + /// This is to avoid reading a pointer redirection during each variable access. + Variable( + Option, + Position, + Box<(Option, Option<(Namespace, u64)>, Identifier)>, + ), + /// Property access - ((getter, hash), (setter, hash), prop) + Property( + Box<( + (Identifier, u64), + (Identifier, u64), + (ImmutableString, Position), + )>, + ), + /// Stack slot for function calls. See [`FnCallExpr`] for more details. + /// + /// This variant does not map to any language structure. It is used in function calls with + /// constant arguments where the `usize` number indexes into an array containing a list of + /// constant arguments for the function call. + Stack(usize, Position), + /// { [statement][Stmt] ... } + Stmt(Box), + /// func `(` expr `,` ... `)` + FnCall(Box, Position), + /// lhs `.` rhs - bool variable is a dummy + Dot(Box, bool, Position), + /// expr `[` expr `]` - boolean indicates whether the dotting/indexing chain stops + Index(Box, bool, Position), + /// lhs `&&` rhs + And(Box, Position), + /// lhs `||` rhs + Or(Box, Position), + /// Custom syntax + Custom(Box, Position), +} + +impl Default for Expr { + #[inline(always)] + fn default() -> Self { + Self::Unit(Position::NONE) + } +} + +impl fmt::Debug for Expr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut display_pos = self.position(); + + match self { + Self::DynamicConstant(value, _) => write!(f, "{:?}", value), + Self::BoolConstant(value, _) => write!(f, "{:?}", value), + Self::IntegerConstant(value, _) => write!(f, "{:?}", value), + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(value, _) => write!(f, "{:?}", value), + Self::CharConstant(value, _) => write!(f, "{:?}", value), + Self::StringConstant(value, _) => write!(f, "{:?}", value), + Self::Unit(_) => f.write_str("()"), + + Self::InterpolatedString(x, _) => { + f.write_str("InterpolatedString")?; + return f.debug_list().entries(x.iter()).finish(); + } + Self::Array(x, _) => { + f.write_str("Array")?; + f.debug_list().entries(x.iter()).finish() + } + Self::Map(x, _) => { + f.write_str("Map")?; + f.debug_map() + .entries(x.0.iter().map(|(k, v)| (k, v))) + .finish() + } + Self::Variable(i, _, x) => { + f.write_str("Variable(")?; + if let Some((_, ref namespace)) = x.1 { + write!(f, "{}{}", namespace, Token::DoubleColon.literal_syntax())? + } + f.write_str(&x.2)?; + if let Some(n) = i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) { + write!(f, " #{}", n)? + } + f.write_str(")") + } + Self::Property(x) => write!(f, "Property({})", (x.2).0), + Self::Stack(x, _) => write!(f, "StackSlot({})", x), + Self::Stmt(x) => { + f.write_str("ExprStmtBlock")?; + f.debug_list().entries(x.iter()).finish() + } + Self::FnCall(x, _) => { + let mut ff = f.debug_struct("FnCall"); + x.namespace.as_ref().map(|ns| ff.field("namespace", ns)); + ff.field("name", &x.name) + .field("hash", &x.hashes) + .field("args", &x.args); + if !x.constants.is_empty() { + ff.field("constants", &x.constants); + } + if x.capture_parent_scope { + ff.field("capture_parent_scope", &x.capture_parent_scope); + } + ff.finish() + } + Self::Index(x, term, pos) => { + display_pos = *pos; + + f.debug_struct("Index") + .field("lhs", &x.lhs) + .field("rhs", &x.rhs) + .field("terminate", term) + .finish() + } + Self::Dot(x, _, pos) | Self::And(x, pos) | Self::Or(x, pos) => { + let op_name = match self { + Self::Dot(_, _, _) => "Dot", + Self::And(_, _) => "And", + Self::Or(_, _) => "Or", + expr => unreachable!( + "Self::Dot or Self::And or Self::Or expected but gets {:?}", + expr + ), + }; + + display_pos = *pos; + + f.debug_struct(op_name) + .field("lhs", &x.lhs) + .field("rhs", &x.rhs) + .finish() + } + Self::Custom(x, _) => f.debug_tuple("Custom").field(x).finish(), + }?; + + display_pos.debug_print(f) + } +} + +impl Expr { + /// Get the [`Dynamic`] value of a literal constant expression. + /// + /// Returns [`None`] if the expression is not a literal constant. + #[inline] + #[must_use] + pub fn get_literal_value(&self) -> Option { + Some(match self { + Self::DynamicConstant(x, _) => x.as_ref().clone(), + Self::IntegerConstant(x, _) => (*x).into(), + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(x, _) => (*x).into(), + Self::CharConstant(x, _) => (*x).into(), + Self::StringConstant(x, _) => x.clone().into(), + Self::BoolConstant(x, _) => (*x).into(), + Self::Unit(_) => Dynamic::UNIT, + + #[cfg(not(feature = "no_index"))] + Self::Array(x, _) if self.is_constant() => { + let mut arr = crate::Array::with_capacity(x.len()); + arr.extend(x.iter().map(|v| v.get_literal_value().unwrap())); + Dynamic::from_array(arr) + } + + #[cfg(not(feature = "no_object"))] + Self::Map(x, _) if self.is_constant() => { + Dynamic::from_map(x.0.iter().fold(x.1.clone(), |mut map, (k, v)| { + let value_ref = map.get_mut(k.name.as_str()).unwrap(); + *value_ref = v.get_literal_value().unwrap(); + map + })) + } + + // Binary operators + Self::FnCall(x, _) if x.args.len() == 2 => match x.name.as_str() { + // x..y + OP_EXCLUSIVE_RANGE => { + if let Expr::IntegerConstant(ref start, _) = x.args[0] { + if let Expr::IntegerConstant(ref end, _) = x.args[1] { + (*start..*end).into() + } else { + return None; + } + } else { + return None; + } + } + // x..=y + OP_INCLUSIVE_RANGE => { + if let Expr::IntegerConstant(ref start, _) = x.args[0] { + if let Expr::IntegerConstant(ref end, _) = x.args[1] { + (*start..=*end).into() + } else { + return None; + } + } else { + return None; + } + } + _ => return None, + }, + + _ => return None, + }) + } + /// Create an [`Expr`] from a [`Dynamic`] value. + #[inline] + #[must_use] + pub fn from_dynamic(value: Dynamic, pos: Position) -> Self { + match value.0 { + Union::Unit(_, _, _) => Self::Unit(pos), + Union::Bool(b, _, _) => Self::BoolConstant(b, pos), + Union::Str(s, _, _) => Self::StringConstant(s, pos), + Union::Char(c, _, _) => Self::CharConstant(c, pos), + Union::Int(i, _, _) => Self::IntegerConstant(i, pos), + + #[cfg(feature = "decimal")] + Union::Decimal(value, _, _) => Self::DynamicConstant(Box::new((*value).into()), pos), + + #[cfg(not(feature = "no_float"))] + Union::Float(f, _, _) => Self::FloatConstant(f, pos), + + #[cfg(not(feature = "no_index"))] + Union::Array(a, _, _) => Self::DynamicConstant(Box::new((*a).into()), pos), + + #[cfg(not(feature = "no_object"))] + Union::Map(m, _, _) => Self::DynamicConstant(Box::new((*m).into()), pos), + + _ => Self::DynamicConstant(value.into(), pos), + } + } + /// Is the expression a simple variable access? + #[inline] + #[must_use] + pub(crate) const fn is_variable_access(&self, non_qualified: bool) -> bool { + match self { + Self::Variable(_, _, x) => !non_qualified || x.1.is_none(), + _ => false, + } + } + /// Return the variable name if the expression a simple variable access. + #[inline] + #[must_use] + pub(crate) fn get_variable_name(&self, non_qualified: bool) -> Option<&str> { + match self { + Self::Variable(_, _, x) if !non_qualified || x.1.is_none() => Some(x.2.as_str()), + _ => None, + } + } + /// Get the [position][Position] of the expression. + #[inline] + #[must_use] + pub const fn position(&self) -> Position { + match self { + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_, pos) => *pos, + + Self::DynamicConstant(_, pos) + | Self::BoolConstant(_, pos) + | Self::IntegerConstant(_, pos) + | Self::CharConstant(_, pos) + | Self::Unit(pos) + | Self::StringConstant(_, pos) + | Self::Array(_, pos) + | Self::Map(_, pos) + | Self::Variable(_, pos, _) + | Self::Stack(_, pos) + | Self::FnCall(_, pos) + | Self::Custom(_, pos) + | Self::InterpolatedString(_, pos) => *pos, + + Self::Property(x) => (x.2).1, + Self::Stmt(x) => x.position(), + + Self::And(x, _) | Self::Or(x, _) | Self::Dot(x, _, _) | Self::Index(x, _, _) => { + x.lhs.position() + } + } + } + /// Override the [position][Position] of the expression. + #[inline] + pub fn set_position(&mut self, new_pos: Position) -> &mut Self { + match self { + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_, pos) => *pos = new_pos, + + Self::DynamicConstant(_, pos) + | Self::BoolConstant(_, pos) + | Self::IntegerConstant(_, pos) + | Self::CharConstant(_, pos) + | Self::Unit(pos) + | Self::StringConstant(_, pos) + | Self::Array(_, pos) + | Self::Map(_, pos) + | Self::And(_, pos) + | Self::Or(_, pos) + | Self::Dot(_, _, pos) + | Self::Index(_, _, pos) + | Self::Variable(_, pos, _) + | Self::Stack(_, pos) + | Self::FnCall(_, pos) + | Self::Custom(_, pos) + | Self::InterpolatedString(_, pos) => *pos = new_pos, + + Self::Property(x) => (x.2).1 = new_pos, + Self::Stmt(x) => x.set_position(new_pos), + } + + self + } + /// Is the expression pure? + /// + /// A pure expression has no side effects. + #[inline] + #[must_use] + pub fn is_pure(&self) -> bool { + match self { + Self::InterpolatedString(x, _) | Self::Array(x, _) => x.iter().all(Self::is_pure), + + Self::Map(x, _) => x.0.iter().map(|(_, v)| v).all(Self::is_pure), + + Self::And(x, _) | Self::Or(x, _) => x.lhs.is_pure() && x.rhs.is_pure(), + + Self::Stmt(x) => x.iter().all(Stmt::is_pure), + + Self::Variable(_, _, _) | Self::Stack(_, _) => true, + + _ => self.is_constant(), + } + } + /// Is the expression the unit `()` literal? + #[inline(always)] + #[must_use] + pub const fn is_unit(&self) -> bool { + matches!(self, Self::Unit(_)) + } + /// Is the expression a constant? + #[inline] + #[must_use] + pub fn is_constant(&self) -> bool { + match self { + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_, _) => true, + + Self::DynamicConstant(_, _) + | Self::BoolConstant(_, _) + | Self::IntegerConstant(_, _) + | Self::CharConstant(_, _) + | Self::StringConstant(_, _) + | Self::Unit(_) + | Self::Stack(_, _) => true, + + Self::InterpolatedString(x, _) | Self::Array(x, _) => x.iter().all(Self::is_constant), + + Self::Map(x, _) => x.0.iter().map(|(_, expr)| expr).all(Self::is_constant), + + _ => false, + } + } + /// Is a particular [token][Token] allowed as a postfix operator to this expression? + #[inline] + #[must_use] + pub const fn is_valid_postfix(&self, token: &Token) -> bool { + match token { + #[cfg(not(feature = "no_object"))] + Token::Period => return true, + _ => (), + } + + match self { + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(_, _) => false, + + Self::DynamicConstant(_, _) + | Self::BoolConstant(_, _) + | Self::CharConstant(_, _) + | Self::And(_, _) + | Self::Or(_, _) + | Self::Unit(_) => false, + + Self::IntegerConstant(_, _) + | Self::StringConstant(_, _) + | Self::InterpolatedString(_, _) + | Self::FnCall(_, _) + | Self::Stmt(_) + | Self::Dot(_, _, _) + | Self::Index(_, _, _) + | Self::Array(_, _) + | Self::Map(_, _) + | Self::Custom(_, _) => match token { + #[cfg(not(feature = "no_index"))] + Token::LeftBracket => true, + _ => false, + }, + + Self::Variable(_, _, _) => match token { + #[cfg(not(feature = "no_index"))] + Token::LeftBracket => true, + Token::LeftParen => true, + Token::Bang => true, + Token::DoubleColon => true, + _ => false, + }, + + Self::Property(_) => match token { + #[cfg(not(feature = "no_index"))] + Token::LeftBracket => true, + Token::LeftParen => true, + _ => false, + }, + + Self::Stack(_, _) => false, + } + } + /// Recursively walk this expression. + /// Return `false` from the callback to terminate the walk. + pub fn walk<'a>( + &'a self, + path: &mut Vec>, + on_node: &mut impl FnMut(&[ASTNode]) -> bool, + ) -> bool { + // Push the current node onto the path + path.push(self.into()); + + if !on_node(path) { + return false; + } + + match self { + Self::Stmt(x) => { + for s in x.iter() { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::InterpolatedString(x, _) | Self::Array(x, _) => { + for e in x.as_ref() { + if !e.walk(path, on_node) { + return false; + } + } + } + Self::Map(x, _) => { + for (_, e) in &x.0 { + if !e.walk(path, on_node) { + return false; + } + } + } + Self::Index(x, _, _) | Self::Dot(x, _, _) | Expr::And(x, _) | Expr::Or(x, _) => { + if !x.lhs.walk(path, on_node) { + return false; + } + if !x.rhs.walk(path, on_node) { + return false; + } + } + Self::FnCall(x, _) => { + for e in &x.args { + if !e.walk(path, on_node) { + return false; + } + } + } + Self::Custom(x, _) => { + for e in &x.inputs { + if !e.walk(path, on_node) { + return false; + } + } + } + _ => (), + } + + path.pop().unwrap(); + + true + } +} diff --git a/src/ast/flags.rs b/src/ast/flags.rs new file mode 100644 index 00000000..0b548135 --- /dev/null +++ b/src/ast/flags.rs @@ -0,0 +1,169 @@ +//! Module defining script options. + +use std::ops::{Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, Not, Sub, SubAssign}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +/// A type representing the access mode of a function. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +pub enum FnAccess { + /// Public function. + Public, + /// Private function. + Private, +} + +/// A type that holds a configuration option with bit-flags. +/// Exported under the `internals` feature only. +#[derive(PartialEq, Eq, Copy, Clone, Hash, Default)] +pub struct OptionFlags(u8); + +impl OptionFlags { + /// Does this [`OptionFlags`] contain a particular option flag? + #[inline(always)] + #[must_use] + pub const fn contains(self, flag: Self) -> bool { + self.0 & flag.0 != 0 + } +} + +impl Not for OptionFlags { + type Output = Self; + + /// Return the negation of the [`OptionFlags`]. + #[inline(always)] + fn not(self) -> Self::Output { + Self(!self.0) & AST_OPTION_FLAGS::AST_OPTION_ALL + } +} + +impl Add for OptionFlags { + type Output = Self; + + /// Return the union of two [`OptionFlags`]. + #[inline(always)] + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} + +impl AddAssign for OptionFlags { + /// Add the option flags in one [`OptionFlags`] to another. + #[inline(always)] + fn add_assign(&mut self, rhs: Self) { + self.0 |= rhs.0 + } +} + +impl BitOr for OptionFlags { + type Output = Self; + + /// Return the union of two [`OptionFlags`]. + #[inline(always)] + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} + +impl BitOrAssign for OptionFlags { + /// Add the option flags in one [`OptionFlags`] to another. + #[inline(always)] + fn bitor_assign(&mut self, rhs: Self) { + self.0 |= rhs.0 + } +} + +impl Sub for OptionFlags { + type Output = Self; + + /// Return the difference of two [`OptionFlags`]. + #[inline(always)] + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 & !rhs.0) + } +} + +impl SubAssign for OptionFlags { + /// Remove the option flags in one [`OptionFlags`] from another. + #[inline(always)] + fn sub_assign(&mut self, rhs: Self) { + self.0 &= !rhs.0 + } +} + +impl BitAnd for OptionFlags { + type Output = Self; + + /// Return the intersection of two [`OptionFlags`]. + #[inline(always)] + fn bitand(self, rhs: Self) -> Self::Output { + Self(self.0 & !rhs.0) + } +} + +impl BitAndAssign for OptionFlags { + /// Keep only the intersection of one [`OptionFlags`] with another. + #[inline(always)] + fn bitand_assign(&mut self, rhs: Self) { + self.0 &= !rhs.0 + } +} + +/// Option bit-flags for [`AST`][super::AST] nodes. +#[allow(non_snake_case)] +pub mod AST_OPTION_FLAGS { + use super::OptionFlags; + + /// _(internals)_ No options for the [`AST`][crate::AST] node. + /// Exported under the `internals` feature only. + pub const AST_OPTION_NONE: OptionFlags = OptionFlags(0b0000_0000); + /// _(internals)_ The [`AST`][crate::AST] node is constant. + /// Exported under the `internals` feature only. + pub const AST_OPTION_CONSTANT: OptionFlags = OptionFlags(0b0000_0001); + /// _(internals)_ The [`AST`][crate::AST] node is public. + /// Exported under the `internals` feature only. + pub const AST_OPTION_PUBLIC: OptionFlags = OptionFlags(0b0000_0010); + /// _(internals)_ The [`AST`][crate::AST] node is in negated mode. + /// Exported under the `internals` feature only. + pub const AST_OPTION_NEGATED: OptionFlags = OptionFlags(0b0000_0100); + /// _(internals)_ The [`AST`][crate::AST] node breaks out of normal control flow. + /// Exported under the `internals` feature only. + pub const AST_OPTION_BREAK_OUT: OptionFlags = OptionFlags(0b0000_1000); + /// _(internals)_ Mask of all options. + /// Exported under the `internals` feature only. + pub(crate) const AST_OPTION_ALL: OptionFlags = OptionFlags( + AST_OPTION_CONSTANT.0 | AST_OPTION_PUBLIC.0 | AST_OPTION_NEGATED.0 | AST_OPTION_BREAK_OUT.0, + ); + + impl std::fmt::Debug for OptionFlags { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn write_option( + options: &OptionFlags, + f: &mut std::fmt::Formatter<'_>, + num_flags: &mut usize, + flag: OptionFlags, + name: &str, + ) -> std::fmt::Result { + if options.contains(flag) { + if *num_flags > 0 { + f.write_str("+")?; + } + f.write_str(name)?; + *num_flags += 1; + } + Ok(()) + } + + let num_flags = &mut 0; + + f.write_str("(")?; + write_option(self, f, num_flags, AST_OPTION_CONSTANT, "Constant")?; + write_option(self, f, num_flags, AST_OPTION_PUBLIC, "Public")?; + write_option(self, f, num_flags, AST_OPTION_NEGATED, "Negated")?; + write_option(self, f, num_flags, AST_OPTION_BREAK_OUT, "Break")?; + f.write_str(")")?; + + Ok(()) + } + } +} diff --git a/src/ast/ident.rs b/src/ast/ident.rs new file mode 100644 index 00000000..4a19878d --- /dev/null +++ b/src/ast/ident.rs @@ -0,0 +1,37 @@ +//! Module defining script identifiers. + +use crate::{Identifier, Position}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; +use std::{fmt, hash::Hash}; + +/// _(internals)_ An identifier containing a name and a [position][Position]. +/// Exported under the `internals` feature only. +#[derive(Clone, Eq, PartialEq, Hash)] +pub struct Ident { + /// Identifier name. + pub name: Identifier, + /// Position. + pub pos: Position, +} + +impl fmt::Debug for Ident { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.name)?; + self.pos.debug_print(f) + } +} + +impl AsRef for Ident { + #[inline(always)] + fn as_ref(&self) -> &str { + self.name.as_ref() + } +} + +impl Ident { + #[inline(always)] + pub fn as_str(&self) -> &str { + self.name.as_str() + } +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs new file mode 100644 index 00000000..3b269a71 --- /dev/null +++ b/src/ast/mod.rs @@ -0,0 +1,22 @@ +//! Module defining the AST (abstract syntax tree). + +pub mod ast; +pub mod expr; +pub mod flags; +pub mod ident; +pub mod script_fn; +pub mod stmt; + +pub use ast::{ASTNode, AST}; +pub use expr::{BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes}; +pub use flags::{FnAccess, OptionFlags, AST_OPTION_FLAGS}; +pub use ident::Ident; +#[cfg(not(feature = "no_function"))] +pub use script_fn::{ScriptFnDef, ScriptFnMetadata}; +pub use stmt::{OpAssignment, Stmt, StmtBlock}; + +#[cfg(not(feature = "no_float"))] +pub use expr::FloatWrapper; + +#[cfg(feature = "no_function")] +pub struct ScriptFnDef; diff --git a/src/ast/script_fn.rs b/src/ast/script_fn.rs new file mode 100644 index 00000000..a9d924e7 --- /dev/null +++ b/src/ast/script_fn.rs @@ -0,0 +1,128 @@ +//! Module defining script-defined functions. +#![cfg(not(feature = "no_function"))] + +use super::{FnAccess, StmtBlock}; +use crate::{Identifier, Module, Shared, StaticVec}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; +use std::{fmt, hash::Hash}; + +/// _(internals)_ A type containing information on a script-defined function. +/// Exported under the `internals` feature only. +#[derive(Debug, Clone)] +pub struct ScriptFnDef { + /// Function body. + pub body: StmtBlock, + /// Encapsulated running environment, if any. + pub lib: Option>, + /// Encapsulated stack of imported modules, if any. + /// + /// Not available under `no_module`. + #[cfg(not(feature = "no_module"))] + pub global: Option)]>>, + /// Function name. + pub name: Identifier, + /// Function access mode. + pub access: FnAccess, + /// Names of function parameters. + pub params: StaticVec, + /// _(metadata)_ Function doc-comments (if any). + /// Exported under the `metadata` feature only. + #[cfg(feature = "metadata")] + pub comments: Option]>>, +} + +impl fmt::Display for ScriptFnDef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}{}({})", + match self.access { + FnAccess::Public => "", + FnAccess::Private => "private ", + }, + self.name, + self.params + .iter() + .map(|s| s.as_str()) + .collect::>() + .join(", ") + ) + } +} + +/// A type containing the metadata of a script-defined function. +/// +/// Not available under `no_function`. +/// +/// Created by [`AST::iter_functions`][super::AST::iter_functions]. +#[derive(Debug, Eq, PartialEq, Clone, Hash)] +pub struct ScriptFnMetadata<'a> { + /// _(metadata)_ Function doc-comments (if any). + /// Exported under the `metadata` feature only. + /// + /// 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. + /// + /// Leading white-spaces are stripped, and each string slice always starts with the corresponding + /// doc-comment leader: `///` or `/**`. + #[cfg(feature = "metadata")] + pub comments: Vec<&'a str>, + /// Function access mode. + pub access: FnAccess, + /// Function name. + pub name: &'a str, + /// Function parameters (if any). + pub params: Vec<&'a str>, +} + +impl fmt::Display for ScriptFnMetadata<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}{}({})", + match self.access { + FnAccess::Public => "", + FnAccess::Private => "private ", + }, + self.name, + self.params + .iter() + .cloned() + .collect::>() + .join(", ") + ) + } +} + +impl<'a> From<&'a ScriptFnDef> for ScriptFnMetadata<'a> { + #[inline] + fn from(value: &'a ScriptFnDef) -> Self { + Self { + #[cfg(feature = "metadata")] + comments: value + .comments + .as_ref() + .map_or_else(|| Vec::new(), |v| v.iter().map(Box::as_ref).collect()), + access: value.access, + name: &value.name, + params: value.params.iter().map(|s| s.as_str()).collect(), + } + } +} + +impl std::cmp::PartialOrd for ScriptFnMetadata<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl std::cmp::Ord for ScriptFnMetadata<'_> { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match self.name.cmp(other.name) { + std::cmp::Ordering::Equal => self.params.len().cmp(&other.params.len()), + cmp => cmp, + } + } +} diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs new file mode 100644 index 00000000..f41d566f --- /dev/null +++ b/src/ast/stmt.rs @@ -0,0 +1,667 @@ +//! Module defining script statements. + +use super::{ASTNode, Expr, FnCallExpr, Ident, OptionFlags, AST_OPTION_FLAGS}; +use crate::engine::KEYWORD_EVAL; +use crate::tokenizer::Token; +use crate::{calc_fn_hash, Position, StaticVec, INT}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; +use std::{ + collections::BTreeMap, + fmt, + hash::Hash, + mem, + ops::{Deref, DerefMut}, +}; + +/// _(internals)_ An op-assignment operator. +/// Exported under the `internals` feature only. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +pub struct OpAssignment<'a> { + /// Hash of the op-assignment call. + pub hash_op_assign: u64, + /// Hash of the underlying operator call (for fallback). + pub hash_op: u64, + /// Op-assignment operator. + pub op: &'a str, +} + +impl OpAssignment<'_> { + /// Create a new [`OpAssignment`]. + /// + /// # Panics + /// + /// Panics if the name is not an op-assignment operator. + #[must_use] + #[inline(always)] + pub fn new(name: &str) -> Self { + Self::new_from_token(Token::lookup_from_syntax(name).expect("operator")) + } + /// Create a new [`OpAssignment`] from a [`Token`]. + /// + /// # Panics + /// + /// Panics if the token is not an op-assignment operator. + #[must_use] + pub fn new_from_token(op: Token) -> Self { + let op_raw = op + .map_op_assignment() + .expect("op-assignment operator") + .literal_syntax(); + Self { + hash_op_assign: calc_fn_hash(op.literal_syntax(), 2), + hash_op: calc_fn_hash(op_raw, 2), + op: op.literal_syntax(), + } + } +} + +/// _(internals)_ A scoped block of statements. +/// Exported under the `internals` feature only. +#[derive(Clone, Hash, Default)] +pub struct StmtBlock(StaticVec, Position); + +impl StmtBlock { + /// A [`StmtBlock`] that does not exist. + pub const NONE: Self = Self::empty(Position::NONE); + + /// Create a new [`StmtBlock`]. + #[must_use] + pub fn new(statements: impl IntoIterator, pos: Position) -> Self { + let mut statements: StaticVec<_> = statements.into_iter().collect(); + statements.shrink_to_fit(); + Self(statements, pos) + } + /// Create an empty [`StmtBlock`]. + #[inline(always)] + #[must_use] + pub const fn empty(pos: Position) -> Self { + Self(StaticVec::new_const(), pos) + } + /// Is this statements block empty? + #[inline(always)] + #[must_use] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + /// Number of statements in this statements block. + #[inline(always)] + #[must_use] + pub fn len(&self) -> usize { + self.0.len() + } + /// Get the statements of this statements block. + #[inline(always)] + #[must_use] + pub fn statements(&self) -> &[Stmt] { + &self.0 + } + /// Extract the statements. + #[inline(always)] + #[must_use] + pub(crate) fn take_statements(&mut self) -> StaticVec { + mem::take(&mut self.0) + } + /// Get an iterator over the statements of this statements block. + #[inline(always)] + #[must_use] + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + /// Get the position (location of the beginning `{`) of this statements block. + #[inline(always)] + #[must_use] + pub const fn position(&self) -> Position { + self.1 + } + /// Set the position (location of the beginning `{`) of this statements block. + #[inline(always)] + pub fn set_position(&mut self, pos: Position) { + self.1 = pos; + } +} + +impl Deref for StmtBlock { + type Target = StaticVec; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for StmtBlock { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl fmt::Debug for StmtBlock { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Block")?; + fmt::Debug::fmt(&self.0, f)?; + self.1.debug_print(f) + } +} + +impl From for StmtBlock { + #[inline] + fn from(stmt: Stmt) -> Self { + match stmt { + Stmt::Block(mut block, pos) => Self(block.iter_mut().map(mem::take).collect(), pos), + Stmt::Noop(pos) => Self(StaticVec::new_const(), pos), + _ => { + let pos = stmt.position(); + Self(vec![stmt].into(), pos) + } + } + } +} + +impl IntoIterator for StmtBlock { + type Item = Stmt; + type IntoIter = smallvec::IntoIter<[Stmt; 3]>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Extend for StmtBlock { + #[inline(always)] + fn extend>(&mut self, iter: T) { + self.0.extend(iter) + } +} + +/// _(internals)_ A statement. +/// Exported under the `internals` feature only. +#[derive(Debug, Clone, Hash)] +pub enum Stmt { + /// No-op. + Noop(Position), + /// `if` expr `{` stmt `}` `else` `{` stmt `}` + If(Expr, Box<(StmtBlock, StmtBlock)>, Position), + /// `switch` expr `{` literal or range or _ `if` condition `=>` stmt `,` ... `}` + /// + /// ### Data Structure + /// + /// 0) Hash table for (condition, block) + /// 1) Default block + /// 2) List of ranges: (start, end, inclusive, condition, statement) + Switch( + Expr, + Box<( + BTreeMap, StmtBlock)>>, + StmtBlock, + StaticVec<(INT, INT, bool, Option, StmtBlock)>, + )>, + Position, + ), + /// `while` expr `{` stmt `}` | `loop` `{` stmt `}` + /// + /// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement. + While(Expr, Box, Position), + /// `do` `{` stmt `}` `while`|`until` expr + /// + /// ### Option Flags + /// + /// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `while` + /// * [`AST_OPTION_NEGATED`][AST_OPTION_FLAGS::AST_OPTION_NEGATED] = `until` + Do(Box, Expr, OptionFlags, Position), + /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` + For(Expr, Box<(Ident, Option, StmtBlock)>, Position), + /// \[`export`\] `let`|`const` id `=` expr + /// + /// ### Option Flags + /// + /// * [`AST_OPTION_PUBLIC`][AST_OPTION_FLAGS::AST_OPTION_PUBLIC] = `export` + /// * [`AST_OPTION_CONSTANT`][AST_OPTION_FLAGS::AST_OPTION_CONSTANT] = `const` + Var(Expr, Box, OptionFlags, Position), + /// expr op`=` expr + Assignment(Box<(Expr, Option>, Expr)>, Position), + /// func `(` expr `,` ... `)` + /// + /// Note - this is a duplicate of [`Expr::FnCall`] to cover the very common pattern of a single + /// function call forming one statement. + FnCall(Box, Position), + /// `{` stmt`;` ... `}` + Block(Box<[Stmt]>, Position), + /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` + TryCatch(Box<(StmtBlock, Option, StmtBlock)>, Position), + /// [expression][Expr] + Expr(Expr), + /// `continue`/`break` + /// + /// ### Option Flags + /// + /// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `continue` + /// * [`AST_OPTION_BREAK_OUT`][AST_OPTION_FLAGS::AST_OPTION_BREAK_OUT] = `break` + BreakLoop(OptionFlags, Position), + /// `return`/`throw` + /// + /// ### Option Flags + /// + /// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `return` + /// * [`AST_OPTION_BREAK_OUT`][AST_OPTION_FLAGS::AST_OPTION_BREAK_OUT] = `throw` + Return(OptionFlags, Option, Position), + /// `import` expr `as` var + /// + /// Not available under `no_module`. + #[cfg(not(feature = "no_module"))] + Import(Expr, Option>, Position), + /// `export` var `as` var `,` ... + /// + /// Not available under `no_module`. + #[cfg(not(feature = "no_module"))] + Export(Box<[(Ident, Ident)]>, Position), + /// Convert a variable to shared. + /// + /// Not available under `no_closure`. + /// + /// # Notes + /// + /// This variant does not map to any language structure. It is currently only used only to + /// convert a normal variable into a shared variable when the variable is _captured_ by a closure. + #[cfg(not(feature = "no_closure"))] + Share(crate::Identifier), +} + +impl Default for Stmt { + #[inline(always)] + fn default() -> Self { + Self::Noop(Position::NONE) + } +} + +impl From for Stmt { + #[inline(always)] + fn from(block: StmtBlock) -> Self { + Self::Block(block.0.into_boxed_slice(), block.1) + } +} + +impl Stmt { + /// Is this statement [`Noop`][Stmt::Noop]? + #[inline(always)] + #[must_use] + pub const fn is_noop(&self) -> bool { + matches!(self, Self::Noop(_)) + } + /// Get the [position][Position] of this statement. + #[must_use] + pub const fn position(&self) -> Position { + match self { + Self::Noop(pos) + | Self::BreakLoop(_, pos) + | Self::Block(_, pos) + | Self::Assignment(_, pos) + | Self::FnCall(_, pos) + | Self::If(_, _, pos) + | Self::Switch(_, _, pos) + | Self::While(_, _, pos) + | Self::Do(_, _, _, pos) + | Self::For(_, _, pos) + | Self::Return(_, _, pos) + | Self::Var(_, _, _, pos) + | Self::TryCatch(_, pos) => *pos, + + Self::Expr(x) => x.position(), + + #[cfg(not(feature = "no_module"))] + Self::Import(_, _, pos) => *pos, + #[cfg(not(feature = "no_module"))] + Self::Export(_, pos) => *pos, + + #[cfg(not(feature = "no_closure"))] + Self::Share(_) => Position::NONE, + } + } + /// Override the [position][Position] of this statement. + pub fn set_position(&mut self, new_pos: Position) -> &mut Self { + match self { + Self::Noop(pos) + | Self::BreakLoop(_, pos) + | Self::Block(_, pos) + | Self::Assignment(_, pos) + | Self::FnCall(_, pos) + | Self::If(_, _, pos) + | Self::Switch(_, _, pos) + | Self::While(_, _, pos) + | Self::Do(_, _, _, pos) + | Self::For(_, _, pos) + | Self::Return(_, _, pos) + | Self::Var(_, _, _, pos) + | Self::TryCatch(_, pos) => *pos = new_pos, + + Self::Expr(x) => { + x.set_position(new_pos); + } + + #[cfg(not(feature = "no_module"))] + Self::Import(_, _, pos) => *pos = new_pos, + #[cfg(not(feature = "no_module"))] + Self::Export(_, pos) => *pos = new_pos, + + #[cfg(not(feature = "no_closure"))] + Self::Share(_) => (), + } + + self + } + /// Does this statement return a value? + #[must_use] + pub const fn returns_value(&self) -> bool { + match self { + Self::If(_, _, _) + | Self::Switch(_, _, _) + | Self::Block(_, _) + | Self::Expr(_) + | Self::FnCall(_, _) => true, + + Self::Noop(_) + | Self::While(_, _, _) + | Self::Do(_, _, _, _) + | Self::For(_, _, _) + | Self::TryCatch(_, _) => false, + + Self::Var(_, _, _, _) + | Self::Assignment(_, _) + | Self::BreakLoop(_, _) + | Self::Return(_, _, _) => false, + + #[cfg(not(feature = "no_module"))] + Self::Import(_, _, _) | Self::Export(_, _) => false, + + #[cfg(not(feature = "no_closure"))] + Self::Share(_) => false, + } + } + /// Is this statement self-terminated (i.e. no need for a semicolon terminator)? + #[must_use] + pub const fn is_self_terminated(&self) -> bool { + match self { + Self::If(_, _, _) + | Self::Switch(_, _, _) + | Self::While(_, _, _) + | Self::For(_, _, _) + | Self::Block(_, _) + | Self::TryCatch(_, _) => true, + + // A No-op requires a semicolon in order to know it is an empty statement! + Self::Noop(_) => false, + + Self::Expr(Expr::Custom(x, _)) if x.is_self_terminated() => true, + + Self::Var(_, _, _, _) + | Self::Assignment(_, _) + | Self::Expr(_) + | Self::FnCall(_, _) + | Self::Do(_, _, _, _) + | Self::BreakLoop(_, _) + | Self::Return(_, _, _) => false, + + #[cfg(not(feature = "no_module"))] + Self::Import(_, _, _) | Self::Export(_, _) => false, + + #[cfg(not(feature = "no_closure"))] + Self::Share(_) => false, + } + } + /// Is this statement _pure_? + /// + /// A pure statement has no side effects. + #[must_use] + pub fn is_pure(&self) -> bool { + match self { + Self::Noop(_) => true, + Self::Expr(expr) => expr.is_pure(), + Self::If(condition, x, _) => { + condition.is_pure() + && (x.0).0.iter().all(Stmt::is_pure) + && (x.1).0.iter().all(Stmt::is_pure) + } + Self::Switch(expr, x, _) => { + expr.is_pure() + && x.0.values().all(|block| { + block.0.as_ref().map(Expr::is_pure).unwrap_or(true) + && (block.1).0.iter().all(Stmt::is_pure) + }) + && (x.2).iter().all(|(_, _, _, condition, stmt)| { + condition.as_ref().map(Expr::is_pure).unwrap_or(true) + && stmt.0.iter().all(Stmt::is_pure) + }) + && (x.1).0.iter().all(Stmt::is_pure) + } + + // Loops that exit can be pure because it can never be infinite. + Self::While(Expr::BoolConstant(false, _), _, _) => true, + Self::Do(body, Expr::BoolConstant(x, _), options, _) + if *x == options.contains(AST_OPTION_FLAGS::AST_OPTION_NEGATED) => + { + body.iter().all(Stmt::is_pure) + } + + // Loops are never pure since they can be infinite - and that's a side effect. + Self::While(_, _, _) | Self::Do(_, _, _, _) => false, + + // For loops can be pure because if the iterable is pure, it is finite, + // so infinite loops can never occur. + Self::For(iterable, x, _) => iterable.is_pure() && (x.2).0.iter().all(Stmt::is_pure), + + Self::Var(_, _, _, _) | Self::Assignment(_, _) | Self::FnCall(_, _) => false, + Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), + Self::BreakLoop(_, _) | Self::Return(_, _, _) => false, + Self::TryCatch(x, _) => { + (x.0).0.iter().all(Stmt::is_pure) && (x.2).0.iter().all(Stmt::is_pure) + } + + #[cfg(not(feature = "no_module"))] + Self::Import(_, _, _) => false, + #[cfg(not(feature = "no_module"))] + Self::Export(_, _) => false, + + #[cfg(not(feature = "no_closure"))] + Self::Share(_) => false, + } + } + /// Does this statement's behavior depend on its containing block? + /// + /// A statement that depends on its containing block behaves differently when promoted + /// to an upper block. + /// + /// Currently only variable definitions (i.e. `let` and `const`), `import`/`export` statements, + /// and `eval` calls (which may in turn call define variables) fall under this category. + #[inline] + #[must_use] + pub fn is_block_dependent(&self) -> bool { + match self { + Self::Var(_, _, _, _) => true, + + Self::Expr(Expr::Stmt(s)) => s.iter().all(Stmt::is_block_dependent), + + Self::FnCall(x, _) | Self::Expr(Expr::FnCall(x, _)) => { + x.namespace.is_none() && x.name == KEYWORD_EVAL + } + + #[cfg(not(feature = "no_module"))] + Self::Import(_, _, _) | Self::Export(_, _) => true, + + _ => false, + } + } + /// Is this statement _pure_ within the containing block? + /// + /// An internally pure statement only has side effects that disappear outside the block. + /// + /// Currently only variable definitions (i.e. `let` and `const`) and `import`/`export` + /// statements are internally pure, other than pure expressions. + #[inline] + #[must_use] + pub fn is_internally_pure(&self) -> bool { + match self { + Self::Var(expr, _, _, _) => expr.is_pure(), + + Self::Expr(Expr::Stmt(s)) => s.iter().all(Stmt::is_internally_pure), + + #[cfg(not(feature = "no_module"))] + Self::Import(expr, _, _) => expr.is_pure(), + #[cfg(not(feature = "no_module"))] + Self::Export(_, _) => true, + + _ => self.is_pure(), + } + } + /// Does this statement break the current control flow through the containing block? + /// + /// Currently this is only true for `return`, `throw`, `break` and `continue`. + /// + /// All statements following this statement will essentially be dead code. + #[inline] + #[must_use] + pub const fn is_control_flow_break(&self) -> bool { + match self { + Self::Return(_, _, _) | Self::BreakLoop(_, _) => true, + _ => false, + } + } + /// Recursively walk this statement. + /// Return `false` from the callback to terminate the walk. + pub fn walk<'a>( + &'a self, + path: &mut Vec>, + on_node: &mut impl FnMut(&[ASTNode]) -> bool, + ) -> bool { + // Push the current node onto the path + path.push(self.into()); + + if !on_node(path) { + return false; + } + + match self { + Self::Var(e, _, _, _) => { + if !e.walk(path, on_node) { + return false; + } + } + Self::If(e, x, _) => { + if !e.walk(path, on_node) { + return false; + } + for s in &(x.0).0 { + if !s.walk(path, on_node) { + return false; + } + } + for s in &(x.1).0 { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::Switch(e, x, _) => { + if !e.walk(path, on_node) { + return false; + } + for b in x.0.values() { + if !b.0.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) { + return false; + } + for s in &(b.1).0 { + if !s.walk(path, on_node) { + return false; + } + } + } + for (_, _, _, c, stmt) in &x.2 { + if !c.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) { + return false; + } + for s in &stmt.0 { + if !s.walk(path, on_node) { + return false; + } + } + } + for s in &(x.1).0 { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::While(e, s, _) | Self::Do(s, e, _, _) => { + if !e.walk(path, on_node) { + return false; + } + for s in &s.0 { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::For(e, x, _) => { + if !e.walk(path, on_node) { + return false; + } + for s in &(x.2).0 { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::Assignment(x, _) => { + if !x.0.walk(path, on_node) { + return false; + } + if !x.2.walk(path, on_node) { + return false; + } + } + Self::FnCall(x, _) => { + for s in &x.args { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::Block(x, _) => { + for s in x.iter() { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::TryCatch(x, _) => { + for s in &(x.0).0 { + if !s.walk(path, on_node) { + return false; + } + } + for s in &(x.2).0 { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::Expr(e) | Self::Return(_, Some(e), _) => { + if !e.walk(path, on_node) { + return false; + } + } + #[cfg(not(feature = "no_module"))] + Self::Import(e, _, _) => { + if !e.walk(path, on_node) { + return false; + } + } + _ => (), + } + + path.pop().unwrap(); + + true + } +} diff --git a/src/engine.rs b/src/engine.rs index 2795bf0c..00dadfeb 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,20 +1,18 @@ //! Main module defining the script evaluation [`Engine`]. +use crate::api::custom_syntax::CustomSyntax; use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*}; -use crate::custom_syntax::CustomSyntax; -use crate::func::{ - get_hasher, - native::{OnDebugCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback}, - CallableFunction, IteratorFn, -}; -use crate::module::NamespaceRef; +use crate::func::call::FnResolutionCache; +use crate::func::native::{OnDebugCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback}; +use crate::func::{get_hasher, CallableFunction, IteratorFn}; +use crate::module::Namespace; use crate::packages::{Package, StandardPackage}; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::tokenizer::Token; use crate::types::dynamic::{map_std_type_name, AccessMode, Union, Variant}; use crate::{ - calc_fn_params_hash, combine_hashes, Dynamic, EvalAltResult, Identifier, ImmutableString, - Module, Position, RhaiResult, Scope, Shared, StaticVec, INT, + Dynamic, Identifier, ImmutableString, Module, Position, RhaiError, RhaiResult, RhaiResultOf, + Scope, Shared, StaticVec, ERR, INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -31,7 +29,544 @@ use std::{ pub type Precedence = NonZeroU8; -/// _(internals)_ A stack of imported [modules][Module] plus mutable runtime global states. +pub const KEYWORD_PRINT: &str = "print"; +pub const KEYWORD_DEBUG: &str = "debug"; +pub const KEYWORD_TYPE_OF: &str = "type_of"; +pub const KEYWORD_EVAL: &str = "eval"; +pub const KEYWORD_FN_PTR: &str = "Fn"; +pub const KEYWORD_FN_PTR_CALL: &str = "call"; +pub const KEYWORD_FN_PTR_CURRY: &str = "curry"; +#[cfg(not(feature = "no_closure"))] +pub const KEYWORD_IS_SHARED: &str = "is_shared"; +pub const KEYWORD_IS_DEF_VAR: &str = "is_def_var"; +#[cfg(not(feature = "no_function"))] +pub const KEYWORD_IS_DEF_FN: &str = "is_def_fn"; +pub const KEYWORD_THIS: &str = "this"; +#[cfg(not(feature = "no_function"))] +#[cfg(not(feature = "no_module"))] +pub const KEYWORD_GLOBAL: &str = "global"; +#[cfg(not(feature = "no_object"))] +pub const FN_GET: &str = "get$"; +#[cfg(not(feature = "no_object"))] +pub const FN_SET: &str = "set$"; +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +pub const FN_IDX_GET: &str = "index$get$"; +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +pub const FN_IDX_SET: &str = "index$set$"; +#[cfg(not(feature = "no_function"))] +pub const FN_ANONYMOUS: &str = "anon$"; + +/// Standard equality comparison operator. +/// +/// Some standard functions (e.g. searching an [`Array`][crate::Array]) implicitly call this +/// function to compare two [`Dynamic`] values. +pub const OP_EQUALS: &str = Token::EqualsTo.literal_syntax(); + +/// Standard concatenation operator. +/// +/// Used primarily to build up interpolated strings. +pub const OP_CONCAT: &str = Token::PlusAssign.literal_syntax(); + +/// Standard containment testing function. +/// +/// The `in` operator is implemented as a call to this function. +pub const OP_CONTAINS: &str = "contains"; + +/// Standard exclusive range operator. +pub const OP_EXCLUSIVE_RANGE: &str = Token::ExclusiveRange.literal_syntax(); + +/// Standard inclusive range operator. +pub const OP_INCLUSIVE_RANGE: &str = Token::InclusiveRange.literal_syntax(); + +/// Method of chaining. +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +enum ChainType { + /// Indexing. + #[cfg(not(feature = "no_index"))] + Indexing, + /// Dotting. + #[cfg(not(feature = "no_object"))] + Dotting, +} + +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +impl From<&Expr> for ChainType { + #[inline] + fn from(expr: &Expr) -> Self { + match expr { + #[cfg(not(feature = "no_index"))] + Expr::Index(_, _, _) => Self::Indexing, + #[cfg(not(feature = "no_object"))] + Expr::Dot(_, _, _) => Self::Dotting, + expr => unreachable!("Expr::Index or Expr::Dot expected but gets {:?}", expr), + } + } +} + +/// Value of a chaining argument. +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +#[derive(Debug, Clone, Hash)] +enum ChainArgument { + /// Dot-property access. + #[cfg(not(feature = "no_object"))] + Property(Position), + /// Arguments to a dot method call. + /// Wrapped values are the arguments plus the [position][Position] of the first argument. + /// + /// Since many dotted function calls have no arguments (e.g. `string.len()`), it is better to + /// reduce the size of [`ChainArgument`] by using a boxed slice. + #[cfg(not(feature = "no_object"))] + MethodCallArgs(Option>, Position), + /// Index value and [position][Position]. + #[cfg(not(feature = "no_index"))] + IndexValue(Dynamic, Position), +} + +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +impl ChainArgument { + /// Return the index value. + #[inline(always)] + #[cfg(not(feature = "no_index"))] + #[must_use] + pub fn into_index_value(self) -> Option { + match self { + Self::IndexValue(value, _) => Some(value), + #[cfg(not(feature = "no_object"))] + _ => None, + } + } + /// Return the list of method call arguments. + /// + /// # Panics + /// + /// Panics if the [`ChainArgument`] is not [`MethodCallArgs`][ChainArgument::MethodCallArgs]. + #[inline(always)] + #[cfg(not(feature = "no_object"))] + #[must_use] + pub fn into_fn_call_args(self) -> (crate::FnArgsVec, Position) { + match self { + Self::MethodCallArgs(None, pos) => (crate::FnArgsVec::new_const(), pos), + Self::MethodCallArgs(Some(mut values), pos) => { + (values.iter_mut().map(std::mem::take).collect(), pos) + } + x => unreachable!("ChainArgument::MethodCallArgs expected but gets {:?}", x), + } + } + /// Return the [position][Position]. + #[inline(always)] + #[must_use] + #[allow(dead_code)] + pub const fn position(&self) -> Position { + match self { + #[cfg(not(feature = "no_object"))] + ChainArgument::Property(pos) => *pos, + #[cfg(not(feature = "no_object"))] + ChainArgument::MethodCallArgs(_, pos) => *pos, + #[cfg(not(feature = "no_index"))] + ChainArgument::IndexValue(_, pos) => *pos, + } + } + /// Create n [`MethodCallArgs`][ChainArgument::MethodCallArgs]. + #[inline(always)] + #[cfg(not(feature = "no_object"))] + #[must_use] + pub fn from_fn_call_args(values: crate::FnArgsVec, pos: Position) -> Self { + if values.is_empty() { + Self::MethodCallArgs(None, pos) + } else { + Self::MethodCallArgs(Some(values.into_vec().into()), pos) + } + } + /// Create an [`IndexValue`][ChainArgument::IndexValue]. + #[inline(always)] + #[cfg(not(feature = "no_index"))] + #[must_use] + pub const fn from_index_value(value: Dynamic, pos: Position) -> Self { + Self::IndexValue(value, pos) + } +} + +/// A type that encapsulates a mutation target for an expression with side effects. +#[derive(Debug)] +pub enum Target<'a> { + /// The target is a mutable reference to a [`Dynamic`]. + RefMut(&'a mut Dynamic), + /// The target is a mutable reference to a _shared_ [`Dynamic`]. + #[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, + }, + /// The target is a temporary [`Dynamic`] value (i.e. its mutation can cause no side effects). + TempValue(Dynamic), + /// The target is a bit inside an [`INT`][crate::INT]. + /// This is necessary because directly pointing to a bit inside an [`INT`][crate::INT] is impossible. + #[cfg(not(feature = "no_index"))] + Bit { + /// Mutable reference to the source [`Dynamic`]. + source: &'a mut Dynamic, + /// Copy of the boolean bit, as a [`Dynamic`]. + value: Dynamic, + /// Bit offset. + bit: u8, + }, + /// The target is a range of bits inside an [`INT`][crate::INT]. + /// This is necessary because directly pointing to a range of bits inside an [`INT`][crate::INT] is impossible. + #[cfg(not(feature = "no_index"))] + BitField { + /// Mutable reference to the source [`Dynamic`]. + source: &'a mut Dynamic, + /// Copy of the integer value of the bits, as a [`Dynamic`]. + value: Dynamic, + /// Bitmask to apply to the source value (i.e. shifted) + mask: INT, + /// Number of bits to right-shift the source value. + shift: u8, + }, + /// The target is a byte inside a [`Blob`][crate::Blob]. + /// This is necessary because directly pointing to a byte (in [`Dynamic`] form) inside a blob is impossible. + #[cfg(not(feature = "no_index"))] + BlobByte { + /// Mutable reference to the source [`Dynamic`]. + source: &'a mut Dynamic, + /// Copy of the byte at the index, as a [`Dynamic`]. + value: Dynamic, + /// Offset index. + index: usize, + }, + /// The target is a character inside a string. + /// This is necessary because directly pointing to a char inside a String is impossible. + #[cfg(not(feature = "no_index"))] + StringChar { + /// Mutable reference to the source [`Dynamic`]. + source: &'a mut Dynamic, + /// Copy of the character at the offset, as a [`Dynamic`]. + value: Dynamic, + /// Offset index. + index: usize, + }, +} + +impl<'a> Target<'a> { + /// Is the [`Target`] a reference pointing to other data? + #[allow(dead_code)] + #[inline] + #[must_use] + pub const fn is_ref(&self) -> bool { + match self { + Self::RefMut(_) => true, + #[cfg(not(feature = "no_closure"))] + Self::SharedValue { .. } => true, + Self::TempValue(_) => false, + #[cfg(not(feature = "no_index"))] + Self::Bit { .. } + | Self::BitField { .. } + | Self::BlobByte { .. } + | Self::StringChar { .. } => false, + } + } + /// Is the [`Target`] a temp value? + #[inline] + #[must_use] + pub const fn is_temp_value(&self) -> bool { + match self { + Self::RefMut(_) => false, + #[cfg(not(feature = "no_closure"))] + Self::SharedValue { .. } => false, + Self::TempValue(_) => true, + #[cfg(not(feature = "no_index"))] + Self::Bit { .. } + | Self::BitField { .. } + | Self::BlobByte { .. } + | Self::StringChar { .. } => false, + } + } + /// 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"))] + Self::SharedValue { .. } => true, + Self::TempValue(r) => r.is_shared(), + #[cfg(not(feature = "no_index"))] + Self::Bit { .. } + | Self::BitField { .. } + | Self::BlobByte { .. } + | Self::StringChar { .. } => false, + } + } + /// Is the [`Target`] a specific type? + #[allow(dead_code)] + #[inline] + #[must_use] + pub fn is(&self) -> bool { + match self { + Self::RefMut(r) => r.is::(), + #[cfg(not(feature = "no_closure"))] + Self::SharedValue { source, .. } => source.is::(), + Self::TempValue(r) => r.is::(), + #[cfg(not(feature = "no_index"))] + Self::Bit { .. } => TypeId::of::() == TypeId::of::(), + #[cfg(not(feature = "no_index"))] + Self::BitField { .. } => TypeId::of::() == TypeId::of::(), + #[cfg(not(feature = "no_index"))] + Self::BlobByte { .. } => TypeId::of::() == TypeId::of::(), + #[cfg(not(feature = "no_index"))] + Self::StringChar { .. } => TypeId::of::() == TypeId::of::(), + } + } + /// 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::TempValue(v) => v, // Owned value is simply taken + #[cfg(not(feature = "no_index"))] + Self::Bit { value, .. } => value, // boolean is taken + #[cfg(not(feature = "no_index"))] + Self::BitField { value, .. } => value, // INT is taken + #[cfg(not(feature = "no_index"))] + Self::BlobByte { value, .. } => value, // byte is taken + #[cfg(not(feature = "no_index"))] + Self::StringChar { value, .. } => value, // char is taken + } + } + /// Take a `&mut Dynamic` reference from the `Target`. + #[inline(always)] + #[must_use] + pub fn take_ref(self) -> Option<&'a mut Dynamic> { + match self { + Self::RefMut(r) => Some(r), + _ => None, + } + } + /// 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, + } + } + /// Get the source [`Dynamic`] of the [`Target`]. + #[allow(dead_code)] + #[inline] + #[must_use] + pub fn source(&self) -> &Dynamic { + match self { + Self::RefMut(r) => *r, + #[cfg(not(feature = "no_closure"))] + Self::SharedValue { source, .. } => source, + Self::TempValue(v) => v, + #[cfg(not(feature = "no_index"))] + Self::Bit { source, .. } => source, + #[cfg(not(feature = "no_index"))] + Self::BitField { source, .. } => source, + #[cfg(not(feature = "no_index"))] + Self::BlobByte { source, .. } => source, + #[cfg(not(feature = "no_index"))] + Self::StringChar { source, .. } => source, + } + } + /// Propagate a changed value back to the original source. + /// This has no effect for direct references. + #[inline] + pub fn propagate_changed_value(&mut self) -> RhaiResultOf<()> { + match self { + Self::RefMut(_) | Self::TempValue(_) => (), + #[cfg(not(feature = "no_closure"))] + Self::SharedValue { .. } => (), + #[cfg(not(feature = "no_index"))] + Self::Bit { source, value, bit } => { + // Replace the bit at the specified index position + let new_bit = value.as_bool().map_err(|err| { + Box::new(ERR::ErrorMismatchDataType( + "bool".to_string(), + err.to_string(), + Position::NONE, + )) + })?; + + let value = &mut *source.write_lock::().expect("`INT`"); + + let index = *bit; + + let mask = 1 << index; + if new_bit { + *value |= mask; + } else { + *value &= !mask; + } + } + #[cfg(not(feature = "no_index"))] + Self::BitField { + source, + value, + mask, + shift, + } => { + let shift = *shift; + let mask = *mask; + + // Replace the bit at the specified index position + let new_value = value.as_int().map_err(|err| { + Box::new(ERR::ErrorMismatchDataType( + "integer".to_string(), + err.to_string(), + Position::NONE, + )) + })?; + + let new_value = (new_value << shift) & mask; + let value = &mut *source.write_lock::().expect("`INT`"); + + *value &= !mask; + *value |= new_value; + } + #[cfg(not(feature = "no_index"))] + Self::BlobByte { + source, + value, + index, + } => { + // Replace the byte at the specified index position + let new_byte = value.as_int().map_err(|err| { + Box::new(ERR::ErrorMismatchDataType( + "INT".to_string(), + err.to_string(), + Position::NONE, + )) + })?; + + let value = &mut *source.write_lock::().expect("`Blob`"); + + let index = *index; + + if index < value.len() { + value[index] = (new_byte & 0x00ff) as u8; + } else { + unreachable!("blob index out of bounds: {}", index); + } + } + #[cfg(not(feature = "no_index"))] + Self::StringChar { + source, + value, + index, + } => { + // Replace the character at the specified index position + let new_ch = value.as_char().map_err(|err| { + Box::new(ERR::ErrorMismatchDataType( + "char".to_string(), + err.to_string(), + Position::NONE, + )) + })?; + + let s = &mut *source + .write_lock::() + .expect("`ImmutableString`"); + + let index = *index; + + *s = s + .chars() + .enumerate() + .map(|(i, ch)| if i == index { new_ch } else { ch }) + .collect(); + } + } + + Ok(()) + } +} + +impl<'a> From<&'a mut Dynamic> for Target<'a> { + #[inline] + fn from(value: &'a mut Dynamic) -> Self { + #[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::().expect("`Dynamic`"); + return Self::SharedValue { source, value: val }; + } + + Self::RefMut(value) + } +} + +impl Deref for Target<'_> { + type Target = Dynamic; + + #[inline] + fn deref(&self) -> &Dynamic { + match self { + Self::RefMut(r) => *r, + #[cfg(not(feature = "no_closure"))] + Self::SharedValue { source, .. } => &**source, + Self::TempValue(ref r) => r, + #[cfg(not(feature = "no_index"))] + Self::Bit { ref value, .. } + | Self::BitField { ref value, .. } + | Self::BlobByte { ref value, .. } + | Self::StringChar { ref value, .. } => value, + } + } +} + +impl AsRef for Target<'_> { + #[inline(always)] + fn as_ref(&self) -> &Dynamic { + self + } +} + +impl DerefMut for Target<'_> { + #[inline] + fn deref_mut(&mut self) -> &mut Dynamic { + match self { + Self::RefMut(r) => *r, + #[cfg(not(feature = "no_closure"))] + Self::SharedValue { source, .. } => &mut *source, + Self::TempValue(ref mut r) => r, + #[cfg(not(feature = "no_index"))] + Self::Bit { ref mut value, .. } + | Self::BitField { ref mut value, .. } + | Self::BlobByte { ref mut value, .. } + | Self::StringChar { ref mut value, .. } => value, + } + } +} + +impl AsMut for Target<'_> { + #[inline(always)] + fn as_mut(&mut self) -> &mut Dynamic { + self + } +} + +impl> From for Target<'_> { + #[inline(always)] + #[must_use] + fn from(value: T) -> Self { + Self::TempValue(value.into()) + } +} + +/// _(internals)_ A stack of imported [modules][Module] plus mutable global runtime states. /// Exported under the `internals` feature only. // // # Implementation Notes @@ -40,97 +575,83 @@ pub type Precedence = NonZeroU8; // Most usage will be looking up a particular key from the list and then getting the module that // corresponds to that key. #[derive(Clone)] -pub struct Imports { +pub struct GlobalRuntimeState { /// Stack of module names. // - // # Implementation Notes - // // We cannot use Cow here because `eval` may load a [module][Module] and // the module name will live beyond the AST of the eval script text. keys: StaticVec, - /// Stack of imported modules. + /// Stack of imported [modules][Module]. modules: StaticVec>, /// Source of the current context. - pub source: Option, + /// No source if the string is empty. + pub source: Identifier, /// Number of operations performed. pub num_operations: u64, /// Number of modules loaded. - pub num_modules: usize, + pub num_modules_loaded: usize, /// Function call hashes to index getters and setters. - /// - /// Not available under `no_index` and `no_object`. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] fn_hash_indexing: (u64, u64), - /// Embedded module resolver. - /// - /// Not available under `no_module`. + /// Embedded [module][Module] resolver. #[cfg(not(feature = "no_module"))] pub embedded_module_resolver: Option>, /// Cache of globally-defined constants. - /// - /// Not available under `no_module` and `no_function`. #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] - global_constants: Option>>>, + constants: Option>>>, } -impl Default for Imports { +impl Default for GlobalRuntimeState { #[inline(always)] fn default() -> Self { Self::new() } } -impl Imports { - /// Create a new stack of imported [modules][Module]. +impl GlobalRuntimeState { + /// Create a new [`GlobalRuntimeState`]. #[inline(always)] #[must_use] pub const fn new() -> Self { Self { keys: StaticVec::new_const(), modules: StaticVec::new_const(), - source: None, + source: Identifier::new_const(), num_operations: 0, - num_modules: 0, + num_modules_loaded: 0, #[cfg(not(feature = "no_module"))] embedded_module_resolver: None, #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] fn_hash_indexing: (0, 0), #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] - global_constants: None, + constants: None, } } - /// Get the length of this stack of imported [modules][Module]. + /// Get the length of the stack of globally-imported [modules][Module]. #[inline(always)] #[must_use] - pub fn len(&self) -> usize { + pub fn num_imported_modules(&self) -> usize { self.keys.len() } - /// Is this stack of imported [modules][Module] empty? + /// Get the globally-imported [module][Module] at a particular index. #[inline(always)] #[must_use] - pub fn is_empty(&self) -> bool { - self.keys.is_empty() - } - /// Get the imported [module][Module] at a particular index. - #[inline(always)] - #[must_use] - pub fn get(&self, index: usize) -> Option> { + pub fn get_shared_module(&self, index: usize) -> Option> { self.modules.get(index).cloned() } - /// Get the imported [module][Module] at a particular index. + /// Get a mutable reference to the globally-imported [module][Module] at a particular index. #[allow(dead_code)] #[inline(always)] #[must_use] - pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut Shared> { + pub(crate) fn get_shared_module_mut(&mut self, index: usize) -> Option<&mut Shared> { self.modules.get_mut(index) } - /// Get the index of an imported [module][Module] by name. + /// Get the index of a globally-imported [module][Module] by name. #[inline] #[must_use] - pub fn find(&self, name: impl AsRef) -> Option { - let name = name.as_ref(); + pub fn find_module(&self, name: &str) -> Option { let len = self.keys.len(); self.keys.iter().rev().enumerate().find_map(|(i, key)| { @@ -143,63 +664,63 @@ impl Imports { } /// Push an imported [module][Module] onto the stack. #[inline(always)] - pub fn push(&mut self, name: impl Into, module: impl Into>) { + pub fn push_module(&mut self, name: impl Into, module: impl Into>) { self.keys.push(name.into()); self.modules.push(module.into()); } - /// Truncate the stack of imported [modules][Module] to a particular length. + /// Truncate the stack of globally-imported [modules][Module] to a particular length. #[inline(always)] - pub fn truncate(&mut self, size: usize) { + pub fn truncate_modules(&mut self, size: usize) { self.keys.truncate(size); self.modules.truncate(size); } - /// Get an iterator to this stack of imported [modules][Module] in reverse order. + /// Get an iterator to the stack of globally-imported [modules][Module] in reverse order. #[allow(dead_code)] #[inline] - pub fn iter(&self) -> impl Iterator { + pub fn iter_modules(&self) -> impl Iterator { self.keys .iter() .rev() .zip(self.modules.iter().rev()) .map(|(name, module)| (name.as_str(), module.as_ref())) } - /// Get an iterator to this stack of imported [modules][Module] in reverse order. + /// Get an iterator to the stack of globally-imported [modules][Module] in reverse order. #[allow(dead_code)] #[inline] - pub(crate) fn iter_raw(&self) -> impl Iterator)> { + pub(crate) fn iter_modules_raw(&self) -> impl Iterator)> { self.keys.iter().rev().zip(self.modules.iter().rev()) } - /// Get an iterator to this stack of imported [modules][Module] in forward order. + /// Get an iterator to the stack of globally-imported [modules][Module] in forward order. #[allow(dead_code)] #[inline] - pub(crate) fn scan_raw(&self) -> impl Iterator)> { + pub(crate) fn scan_modules_raw(&self) -> impl Iterator)> { self.keys.iter().zip(self.modules.iter()) } - /// Does the specified function hash key exist in this stack of imported [modules][Module]? + /// Does the specified function hash key exist in the stack of globally-imported [modules][Module]? #[allow(dead_code)] #[inline] #[must_use] pub fn contains_fn(&self, hash: u64) -> bool { self.modules.iter().any(|m| m.contains_qualified_fn(hash)) } - /// Get the specified function via its hash key from this stack of imported [modules][Module]. + /// Get the specified function via its hash key from the stack of globally-imported [modules][Module]. #[inline] #[must_use] - pub fn get_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&Identifier>)> { + pub fn get_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&str>)> { self.modules .iter() .rev() - .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))) + .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id()))) } - /// Does the specified [`TypeId`][std::any::TypeId] iterator exist in this stack of - /// imported [modules][Module]? + /// Does the specified [`TypeId`][std::any::TypeId] iterator exist in the stack of + /// globally-imported [modules][Module]? #[allow(dead_code)] #[inline] #[must_use] pub fn contains_iter(&self, id: TypeId) -> bool { self.modules.iter().any(|m| m.contains_qualified_iter(id)) } - /// Get the specified [`TypeId`][std::any::TypeId] iterator from this stack of imported + /// Get the specified [`TypeId`][std::any::TypeId] iterator from the stack of globally-imported /// [modules][Module]. #[inline] #[must_use] @@ -213,10 +734,10 @@ impl Imports { #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] #[must_use] - pub(crate) fn global_constants_mut<'a>( + pub(crate) fn constants_mut<'a>( &'a mut self, ) -> Option> + 'a> { - if let Some(ref global_constants) = self.global_constants { + if let Some(ref global_constants) = self.constants { Some(crate::func::native::shared_write_lock(global_constants)) } else { None @@ -225,13 +746,13 @@ impl Imports { /// Set a constant into the cache of globally-defined constants. #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] - pub(crate) fn set_global_constant(&mut self, name: impl Into, value: Dynamic) { - if self.global_constants.is_none() { + pub(crate) fn set_constant(&mut self, name: impl Into, value: Dynamic) { + if self.constants.is_none() { let dict: crate::Locked<_> = BTreeMap::new().into(); - self.global_constants = Some(dict.into()); + self.constants = Some(dict.into()); } - crate::func::native::shared_write_lock(self.global_constants.as_mut().expect("`Some`")) + crate::func::native::shared_write_lock(self.constants.as_mut().expect("`Some`")) .insert(name.into(), value); } /// Get the pre-calculated index getter hash. @@ -262,7 +783,7 @@ impl Imports { } } -impl IntoIterator for Imports { +impl IntoIterator for GlobalRuntimeState { type Item = (Identifier, Shared); type IntoIter = Zip>, Rev; 3]>>>; @@ -276,7 +797,7 @@ impl IntoIterator for Imports { } } -impl, M: Into>> FromIterator<(K, M)> for Imports { +impl, M: Into>> FromIterator<(K, M)> for GlobalRuntimeState { fn from_iter>(iter: T) -> Self { let mut lib = Self::new(); lib.extend(iter); @@ -284,7 +805,7 @@ impl, M: Into>> FromIterator<(K, M)> for Impo } } -impl, M: Into>> Extend<(K, M)> for Imports { +impl, M: Into>> Extend<(K, M)> for GlobalRuntimeState { fn extend>(&mut self, iter: T) { iter.into_iter().for_each(|(k, m)| { self.keys.push(k.into()); @@ -293,520 +814,31 @@ impl, M: Into>> Extend<(K, M)> for Imports { } } -impl fmt::Debug for Imports { +impl fmt::Debug for GlobalRuntimeState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("Imports")?; - - if self.is_empty() { - f.debug_map().finish() - } else { - f.debug_map() - .entries(self.keys.iter().zip(self.modules.iter())) - .finish() - } + f.debug_map() + .entries(self.keys.iter().zip(self.modules.iter())) + .finish() } } -#[cfg(not(feature = "unchecked"))] -#[cfg(debug_assertions)] -#[cfg(not(feature = "no_function"))] -pub const MAX_CALL_STACK_DEPTH: usize = 8; -#[cfg(not(feature = "unchecked"))] -#[cfg(debug_assertions)] -pub const MAX_EXPR_DEPTH: usize = 32; -#[cfg(not(feature = "unchecked"))] -#[cfg(not(feature = "no_function"))] -#[cfg(debug_assertions)] -pub const MAX_FUNCTION_EXPR_DEPTH: usize = 16; - -#[cfg(not(feature = "unchecked"))] -#[cfg(not(debug_assertions))] -#[cfg(not(feature = "no_function"))] -pub const MAX_CALL_STACK_DEPTH: usize = 64; -#[cfg(not(feature = "unchecked"))] -#[cfg(not(debug_assertions))] -pub const MAX_EXPR_DEPTH: usize = 64; -#[cfg(not(feature = "unchecked"))] -#[cfg(not(feature = "no_function"))] -#[cfg(not(debug_assertions))] -pub const MAX_FUNCTION_EXPR_DEPTH: usize = 32; - -pub const MAX_DYNAMIC_PARAMETERS: usize = 16; - -pub const KEYWORD_PRINT: &str = "print"; -pub const KEYWORD_DEBUG: &str = "debug"; -pub const KEYWORD_TYPE_OF: &str = "type_of"; -pub const KEYWORD_EVAL: &str = "eval"; -pub const KEYWORD_FN_PTR: &str = "Fn"; -pub const KEYWORD_FN_PTR_CALL: &str = "call"; -pub const KEYWORD_FN_PTR_CURRY: &str = "curry"; -#[cfg(not(feature = "no_closure"))] -pub const KEYWORD_IS_SHARED: &str = "is_shared"; -pub const KEYWORD_IS_DEF_VAR: &str = "is_def_var"; -#[cfg(not(feature = "no_function"))] -pub const KEYWORD_IS_DEF_FN: &str = "is_def_fn"; -pub const KEYWORD_THIS: &str = "this"; -#[cfg(not(feature = "no_function"))] -#[cfg(not(feature = "no_module"))] -pub const KEYWORD_GLOBAL: &str = "global"; -#[cfg(not(feature = "no_object"))] -pub const FN_GET: &str = "get$"; -#[cfg(not(feature = "no_object"))] -pub const FN_SET: &str = "set$"; -#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] -pub const FN_IDX_GET: &str = "index$get$"; -#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] -pub const FN_IDX_SET: &str = "index$set$"; -#[cfg(not(feature = "no_function"))] -pub const FN_ANONYMOUS: &str = "anon$"; - -/// Standard equality comparison operator. -pub const OP_EQUALS: &str = "=="; - -/// Standard method function for containment testing. -/// The `in` operator is implemented as a call to this method. -pub const OP_CONTAINS: &str = "contains"; - -/// Standard exclusive range operator. -pub const OP_EXCLUSIVE_RANGE: &str = ".."; - -/// Standard inclusive range operator. -pub const OP_INCLUSIVE_RANGE: &str = "..="; - -/// Standard concatenation operator token. -pub const TOKEN_OP_CONCAT: Token = Token::PlusAssign; - -/// Method of chaining. -#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -enum ChainType { - /// Indexing. - #[cfg(not(feature = "no_index"))] - Indexing, - /// Dotting. - #[cfg(not(feature = "no_object"))] - Dotting, -} - -/// Value of a chaining argument. -#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] -#[derive(Debug, Clone, Hash)] -enum ChainArgument { - /// Dot-property access. - #[cfg(not(feature = "no_object"))] - Property(Position), - /// Arguments to a dot method call. - /// Wrapped values are the arguments plus the [position][Position] of the first argument. - #[cfg(not(feature = "no_object"))] - MethodCallArgs(StaticVec, Position), - /// Index value and [position][Position]. - #[cfg(not(feature = "no_index"))] - IndexValue(Dynamic, Position), -} - -#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] -impl ChainArgument { - /// Return the index value. - #[inline(always)] - #[cfg(not(feature = "no_index"))] - #[must_use] - pub fn into_index_value(self) -> Option { - match self { - Self::IndexValue(value, _) => Some(value), - #[cfg(not(feature = "no_object"))] - _ => None, - } - } - /// Return the list of method call arguments. - #[inline(always)] - #[cfg(not(feature = "no_object"))] - #[must_use] - pub fn into_fn_call_args(self) -> Option<(StaticVec, Position)> { - match self { - Self::MethodCallArgs(values, pos) => Some((values, pos)), - _ => None, - } - } - /// Return the [position][Position]. - #[inline(always)] - #[must_use] - #[allow(dead_code)] - pub const fn position(&self) -> Position { - match self { - #[cfg(not(feature = "no_object"))] - ChainArgument::Property(pos) => *pos, - #[cfg(not(feature = "no_object"))] - ChainArgument::MethodCallArgs(_, pos) => *pos, - #[cfg(not(feature = "no_index"))] - ChainArgument::IndexValue(_, pos) => *pos, - } - } -} - -#[cfg(not(feature = "no_object"))] -impl From<(StaticVec, Position)> for ChainArgument { - #[inline(always)] - fn from((values, pos): (StaticVec, Position)) -> Self { - Self::MethodCallArgs(values, pos) - } -} - -#[cfg(not(feature = "no_index"))] -impl From<(Dynamic, Position)> for ChainArgument { - #[inline(always)] - fn from((value, pos): (Dynamic, Position)) -> Self { - Self::IndexValue(value, pos) - } -} - -/// Get the chaining type for an [`Expr`]. -#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] -fn match_chaining_type(expr: &Expr) -> ChainType { - match expr { - #[cfg(not(feature = "no_index"))] - Expr::Index(_, _, _) => ChainType::Indexing, - #[cfg(not(feature = "no_object"))] - Expr::Dot(_, _, _) => ChainType::Dotting, - _ => unreachable!("`expr` should only be `Index` or `Dot`, but got {:?}", expr), - } -} - -/// A type that encapsulates a mutation target for an expression with side effects. -#[derive(Debug)] -pub enum Target<'a> { - /// The target is a mutable reference to a `Dynamic` value somewhere. - RefMut(&'a mut Dynamic), - /// The target is a mutable reference to a Shared `Dynamic` value. - /// It holds both the access guard and the original shared value. - #[cfg(not(feature = "no_closure"))] - LockGuard( - ( - crate::types::dynamic::DynamicWriteLock<'a, Dynamic>, - Dynamic, - ), - ), - /// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects). - TempValue(Dynamic), - /// The target is a bit inside an [`INT`][crate::INT]. - /// This is necessary because directly pointing to a bit inside an [`INT`][crate::INT] is impossible. - #[cfg(not(feature = "no_index"))] - Bit(&'a mut Dynamic, Dynamic, u8), - /// The target is a range of bits inside an [`INT`][crate::INT]. - /// This is necessary because directly pointing to a range of bits inside an [`INT`][crate::INT] is impossible. - #[cfg(not(feature = "no_index"))] - BitField(&'a mut Dynamic, INT, Dynamic, u8), - /// The target is a byte inside a Blob. - /// This is necessary because directly pointing to a byte (in [`Dynamic`] form) inside a blob is impossible. - #[cfg(not(feature = "no_index"))] - BlobByte(&'a mut Dynamic, usize, Dynamic), - /// The target is a character inside a String. - /// This is necessary because directly pointing to a char inside a String is impossible. - #[cfg(not(feature = "no_index"))] - StringChar(&'a mut Dynamic, usize, Dynamic), -} - -impl<'a> Target<'a> { - /// Is the `Target` a reference pointing to other data? - #[allow(dead_code)] - #[inline] - #[must_use] - pub const fn is_ref(&self) -> bool { - match self { - Self::RefMut(_) => true, - #[cfg(not(feature = "no_closure"))] - Self::LockGuard(_) => true, - Self::TempValue(_) => false, - #[cfg(not(feature = "no_index"))] - Self::Bit(_, _, _) - | Self::BitField(_, _, _, _) - | Self::BlobByte(_, _, _) - | Self::StringChar(_, _, _) => false, - } - } - /// Is the `Target` a temp value? - #[inline] - #[must_use] - pub const fn is_temp_value(&self) -> bool { - match self { - Self::RefMut(_) => false, - #[cfg(not(feature = "no_closure"))] - Self::LockGuard(_) => false, - Self::TempValue(_) => true, - #[cfg(not(feature = "no_index"))] - Self::Bit(_, _, _) - | Self::BitField(_, _, _, _) - | Self::BlobByte(_, _, _) - | Self::StringChar(_, _, _) => false, - } - } - /// 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"))] - Self::LockGuard(_) => true, - Self::TempValue(r) => r.is_shared(), - #[cfg(not(feature = "no_index"))] - Self::Bit(_, _, _) - | Self::BitField(_, _, _, _) - | Self::BlobByte(_, _, _) - | Self::StringChar(_, _, _) => false, - } - } - /// Is the `Target` a specific type? - #[allow(dead_code)] - #[inline] - #[must_use] - pub fn is(&self) -> bool { - match self { - Self::RefMut(r) => r.is::(), - #[cfg(not(feature = "no_closure"))] - Self::LockGuard((r, _)) => r.is::(), - Self::TempValue(r) => r.is::(), - #[cfg(not(feature = "no_index"))] - Self::Bit(_, _, _) => TypeId::of::() == TypeId::of::(), - #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, _, _) => TypeId::of::() == TypeId::of::(), - #[cfg(not(feature = "no_index"))] - Self::BlobByte(_, _, _) => TypeId::of::() == TypeId::of::(), - #[cfg(not(feature = "no_index"))] - Self::StringChar(_, _, _) => TypeId::of::() == TypeId::of::(), - } - } - /// 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::LockGuard((_, orig)) => orig, // Original value is simply taken - Self::TempValue(v) => v, // Owned value is simply taken - #[cfg(not(feature = "no_index"))] - Self::Bit(_, value, _) => value, // Boolean is taken - #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, value, _) => value, // INT is taken - #[cfg(not(feature = "no_index"))] - Self::BlobByte(_, _, value) => value, // Byte is taken - #[cfg(not(feature = "no_index"))] - Self::StringChar(_, _, ch) => ch, // Character is taken - } - } - /// Take a `&mut Dynamic` reference from the `Target`. - #[inline(always)] - #[must_use] - pub fn take_ref(self) -> Option<&'a mut Dynamic> { - match self { - Self::RefMut(r) => Some(r), - _ => None, - } - } - /// Convert a shared or reference `Target` into a target with an owned value. - #[inline(always)] - #[must_use] - pub fn into_owned(self) -> Target<'static> { - self.take_or_clone().into() - } - /// Propagate a changed value back to the original source. - /// This has no effect except for string indexing. - #[inline] - pub fn propagate_changed_value(&mut self) -> Result<(), Box> { - match self { - Self::RefMut(_) | Self::TempValue(_) => (), - #[cfg(not(feature = "no_closure"))] - Self::LockGuard(_) => (), - #[cfg(not(feature = "no_index"))] - Self::Bit(value, new_val, index) => { - // Replace the bit at the specified index position - let new_bit = new_val.as_bool().map_err(|err| { - Box::new(EvalAltResult::ErrorMismatchDataType( - "bool".to_string(), - err.to_string(), - Position::NONE, - )) - })?; - - let value = &mut *value.write_lock::().expect("`INT`"); - - let index = *index; - - let mask = 1 << index; - if new_bit { - *value |= mask; - } else { - *value &= !mask; - } - } - #[cfg(not(feature = "no_index"))] - Self::BitField(value, mask, new_val, shift) => { - let shift = *shift; - let mask = *mask; - - // Replace the bit at the specified index position - let new_value = new_val.as_int().map_err(|err| { - Box::new(EvalAltResult::ErrorMismatchDataType( - "integer".to_string(), - err.to_string(), - Position::NONE, - )) - })?; - - let new_value = (new_value << shift) & mask; - let value = &mut *value.write_lock::().expect("`INT`"); - - *value &= !mask; - *value |= new_value; - } - #[cfg(not(feature = "no_index"))] - Self::BlobByte(value, index, new_val) => { - // Replace the byte at the specified index position - let new_byte = new_val.as_int().map_err(|err| { - Box::new(EvalAltResult::ErrorMismatchDataType( - "INT".to_string(), - err.to_string(), - Position::NONE, - )) - })?; - - let value = &mut *value.write_lock::().expect("`Blob`"); - - let index = *index; - - if index < value.len() { - value[index] = (new_byte & 0x00ff) as u8; - } else { - unreachable!("blob index out of bounds: {}", index); - } - } - #[cfg(not(feature = "no_index"))] - Self::StringChar(s, index, new_val) => { - // Replace the character at the specified index position - let new_ch = new_val.as_char().map_err(|err| { - Box::new(EvalAltResult::ErrorMismatchDataType( - "char".to_string(), - err.to_string(), - Position::NONE, - )) - })?; - - let s = &mut *s - .write_lock::() - .expect("`ImmutableString`"); - - let index = *index; - - *s = s - .chars() - .enumerate() - .map(|(i, ch)| if i == index { new_ch } else { ch }) - .collect(); - } - } - - Ok(()) - } -} - -impl<'a> From<&'a mut Dynamic> for Target<'a> { - #[inline] - fn from(value: &'a mut Dynamic) -> Self { - #[cfg(not(feature = "no_closure"))] - if value.is_shared() { - // Cloning is cheap for a shared value - let container = value.clone(); - return Self::LockGuard((value.write_lock::().expect("`Dynamic`"), container)); - } - - Self::RefMut(value) - } -} - -impl Deref for Target<'_> { - type Target = Dynamic; - - #[inline] - fn deref(&self) -> &Dynamic { - match self { - Self::RefMut(r) => *r, - #[cfg(not(feature = "no_closure"))] - Self::LockGuard((r, _)) => &**r, - Self::TempValue(ref r) => r, - #[cfg(not(feature = "no_index"))] - Self::Bit(_, ref r, _) - | Self::BitField(_, _, ref r, _) - | Self::BlobByte(_, _, ref r) - | Self::StringChar(_, _, ref r) => r, - } - } -} - -impl AsRef for Target<'_> { - #[inline(always)] - fn as_ref(&self) -> &Dynamic { - self - } -} - -impl DerefMut for Target<'_> { - #[inline] - fn deref_mut(&mut self) -> &mut Dynamic { - match self { - Self::RefMut(r) => *r, - #[cfg(not(feature = "no_closure"))] - Self::LockGuard((r, _)) => r.deref_mut(), - Self::TempValue(ref mut r) => r, - #[cfg(not(feature = "no_index"))] - Self::Bit(_, ref mut r, _) - | Self::BitField(_, _, ref mut r, _) - | Self::BlobByte(_, _, ref mut r) - | Self::StringChar(_, _, ref mut r) => r, - } - } -} - -impl AsMut for Target<'_> { - #[inline(always)] - fn as_mut(&mut self) -> &mut Dynamic { - self - } -} - -impl> From for Target<'_> { - #[inline(always)] - #[must_use] - fn from(value: T) -> Self { - Self::TempValue(value.into()) - } -} - -/// _(internals)_ An entry in a function resolution cache. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone)] -pub struct FnResolutionCacheEntry { - /// Function. - pub func: CallableFunction, - /// Optional source. - pub source: Option, -} - -/// _(internals)_ A function resolution cache. -/// Exported under the `internals` feature only. -pub type FnResolutionCache = BTreeMap>>; - /// _(internals)_ A type that holds all the current states of the [`Engine`]. /// Exported under the `internals` feature only. #[derive(Debug, Clone)] pub struct EvalState { + /// Force a [`Scope`] search by name. + /// /// Normally, access to variables are parsed with a relative offset into the [`Scope`] to avoid a lookup. + /// /// In some situation, e.g. after running an `eval` statement, or after a custom syntax statement, /// subsequent offsets may become mis-aligned. - /// When that happens, this flag is turned on to force a [`Scope`] search by name. + /// + /// When that happens, this flag is turned on. pub always_search_scope: bool, - /// Level of the current scope. The global (root) level is zero, a new block (or function call) - /// is one level higher, and so on. + /// Level of the current scope. + /// + /// The global (root) level is zero, a new block (or function call) is one level higher, and so on. pub scope_level: usize, /// Stack of function resolution caches. fn_resolution_caches: StaticVec, @@ -823,12 +855,6 @@ impl EvalState { fn_resolution_caches: StaticVec::new_const(), } } - /// Is the state currently at global (root) level? - #[inline(always)] - #[must_use] - pub const fn is_global(&self) -> bool { - self.scope_level == 0 - } /// Get the number of function resolution cache(s) in the stack. #[inline(always)] #[must_use] @@ -843,7 +869,7 @@ impl EvalState { // Push a new function resolution cache if the stack is empty self.push_fn_resolution_cache(); } - self.fn_resolution_caches.last_mut().expect("not empty") + self.fn_resolution_caches.last_mut().unwrap() } /// Push an empty function resolution cache onto the stack and make it current. #[allow(dead_code)] @@ -861,12 +887,19 @@ impl EvalState { /// Context of a script evaluation process. #[derive(Debug)] pub struct EvalContext<'a, 'x, 'px, 'm, 's, 'b, 't, 'pt> { + /// The current [`Engine`]. pub(crate) engine: &'a Engine, + /// The current [`Scope`]. pub(crate) scope: &'x mut Scope<'px>, - pub(crate) mods: &'m mut Imports, + /// The current [`GlobalRuntimeState`]. + pub(crate) global: &'m mut GlobalRuntimeState, + /// The current [evaluation state][EvalState]. pub(crate) state: &'s mut EvalState, + /// The current stack of imported [modules][Module]. pub(crate) lib: &'b [&'b Module], + /// The current bound `this` pointer, if any. pub(crate) this_ptr: &'t mut Option<&'pt mut Dynamic>, + /// The current nesting level of function calls. pub(crate) level: usize, } @@ -881,7 +914,10 @@ impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, 'pt> { #[inline(always)] #[must_use] pub fn source(&self) -> Option<&str> { - self.mods.source.as_ref().map(|s| s.as_str()) + match self.global.source.as_str() { + "" => None, + s => Some(s), + } } /// The current [`Scope`]. #[inline(always)] @@ -899,16 +935,16 @@ impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, 'pt> { #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn iter_imports(&self) -> impl Iterator { - self.mods.iter() + self.global.iter_modules() } - /// _(internals)_ The current set of modules imported via `import` statements. + /// _(internals)_ The current [`GlobalRuntimeState`]. /// Exported under the `internals` feature only. #[cfg(feature = "internals")] #[cfg(not(feature = "no_module"))] #[inline(always)] #[must_use] - pub const fn imports(&self) -> &Imports { - self.mods + pub const fn global_runtime_state(&self) -> &GlobalRuntimeState { + self.global } /// Get an iterator over the namespaces containing definition of all script-defined functions. #[inline] @@ -1029,26 +1065,32 @@ impl Default for Engine { /// Make getter function #[cfg(not(feature = "no_object"))] -#[inline] +#[inline(always)] #[must_use] -pub fn make_getter(id: impl AsRef) -> String { - format!("{}{}", FN_GET, id.as_ref()) +pub fn make_getter(id: &str) -> Identifier { + let mut buf = Identifier::new_const(); + buf.push_str(FN_GET); + buf.push_str(id); + buf } /// Make setter function #[cfg(not(feature = "no_object"))] -#[inline] +#[inline(always)] #[must_use] -pub fn make_setter(id: impl AsRef) -> String { - format!("{}{}", FN_SET, id.as_ref()) +pub fn make_setter(id: &str) -> Identifier { + let mut buf = Identifier::new_const(); + buf.push_str(FN_SET); + buf.push_str(id); + buf } /// Is this function an anonymous function? #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] -pub fn is_anonymous_fn(fn_name: impl AsRef) -> bool { - fn_name.as_ref().starts_with(FN_ANONYMOUS) +pub fn is_anonymous_fn(fn_name: &str) -> bool { + fn_name.starts_with(FN_ANONYMOUS) } /// Print to `stdout` @@ -1056,7 +1098,8 @@ pub fn is_anonymous_fn(fn_name: impl AsRef) -> bool { #[allow(unused_variables)] fn print_to_stdout(s: &str) { #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_arch = "wasm64"))] println!("{}", s); } @@ -1065,7 +1108,8 @@ fn print_to_stdout(s: &str) { #[allow(unused_variables)] fn debug_to_stdout(s: &str, source: Option<&str>, pos: Position) { #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_arch = "wasm64"))] if let Some(source) = source { println!("{}{:?} | {}", source, pos, s); } else if pos.is_none() { @@ -1085,7 +1129,8 @@ impl Engine { #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_arch = "wasm64"))] { engine.module_resolver = Some(Box::new(crate::module::resolvers::FileModuleResolver::new())); @@ -1160,9 +1205,9 @@ impl Engine { #[must_use] pub(crate) fn search_imports( &self, - mods: &Imports, + global: &GlobalRuntimeState, state: &mut EvalState, - namespace: &NamespaceRef, + namespace: &Namespace, ) -> Option> { let root = &namespace[0].name; @@ -1174,11 +1219,12 @@ impl Engine { }; if let Some(index) = index { - let offset = mods.len() - index.get(); - Some(mods.get(offset).expect("within range")) + let offset = global.num_imported_modules() - index.get(); + Some(global.get_shared_module(offset).unwrap()) } else { - mods.find(root) - .map(|n| mods.get(n).expect("valid index")) + global + .find_module(root) + .map(|n| global.get_shared_module(n).unwrap()) .or_else(|| self.global_sub_modules.get(root).cloned()) } } @@ -1188,24 +1234,24 @@ impl Engine { pub(crate) fn search_namespace<'s>( &self, scope: &'s mut Scope, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], this_ptr: &'s mut Option<&mut Dynamic>, expr: &Expr, - ) -> Result<(Target<'s>, Position), Box> { + ) -> RhaiResultOf<(Target<'s>, Position)> { match expr { Expr::Variable(Some(_), _, _) => { - self.search_scope_only(scope, mods, state, lib, this_ptr, expr) + self.search_scope_only(scope, global, state, lib, this_ptr, expr) } Expr::Variable(None, _var_pos, v) => match v.as_ref() { // Normal variable access - (_, None, _) => self.search_scope_only(scope, mods, state, lib, this_ptr, expr), + (_, None, _) => self.search_scope_only(scope, global, state, lib, this_ptr, expr), // Qualified variable access #[cfg(not(feature = "no_module"))] (_, Some((namespace, hash_var)), var_name) => { - if let Some(module) = self.search_imports(mods, state, namespace) { + if let Some(module) = self.search_imports(global, state, namespace) { // foo:bar::baz::VARIABLE return match module.get_qualified_var(*hash_var) { Ok(target) => { @@ -1215,18 +1261,16 @@ impl Engine { Ok((target.into(), *_var_pos)) } Err(err) => Err(match *err { - EvalAltResult::ErrorVariableNotFound(_, _) => { - EvalAltResult::ErrorVariableNotFound( - format!( - "{}{}{}", - namespace, - Token::DoubleColon.literal_syntax(), - var_name - ), - namespace[0].pos, - ) - .into() - } + ERR::ErrorVariableNotFound(_, _) => ERR::ErrorVariableNotFound( + format!( + "{}{}{}", + namespace, + Token::DoubleColon.literal_syntax(), + var_name + ), + namespace[0].pos, + ) + .into(), _ => err.fill_position(*_var_pos), }), }; @@ -1235,7 +1279,7 @@ impl Engine { #[cfg(not(feature = "no_function"))] if namespace.len() == 1 && namespace[0].name == KEYWORD_GLOBAL { // global::VARIABLE - let global_constants = mods.global_constants_mut(); + let global_constants = global.constants_mut(); if let Some(mut guard) = global_constants { if let Some(value) = guard.get_mut(var_name) { @@ -1246,7 +1290,7 @@ impl Engine { } } - return Err(EvalAltResult::ErrorVariableNotFound( + return Err(ERR::ErrorVariableNotFound( format!( "{}{}{}", namespace, @@ -1258,16 +1302,13 @@ impl Engine { .into()); } - Err( - EvalAltResult::ErrorModuleNotFound(namespace.to_string(), namespace[0].pos) - .into(), - ) + Err(ERR::ErrorModuleNotFound(namespace.to_string(), namespace[0].pos).into()) } #[cfg(feature = "no_module")] (_, Some((_, _)), _) => unreachable!("qualified access under no_module"), }, - _ => unreachable!("Expr::Variable expected, but gets {:?}", expr), + _ => unreachable!("Expr::Variable expected but gets {:?}", expr), } } @@ -1279,12 +1320,12 @@ impl Engine { pub(crate) fn search_scope_only<'s>( &self, scope: &'s mut Scope, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], this_ptr: &'s mut Option<&mut Dynamic>, expr: &Expr, - ) -> Result<(Target<'s>, Position), Box> { + ) -> RhaiResultOf<(Target<'s>, Position)> { // Make sure that the pointer indirection is taken only when absolutely necessary. let (index, var_pos) = match expr { @@ -1293,13 +1334,13 @@ impl Engine { return if let Some(val) = this_ptr { Ok(((*val).into(), *pos)) } else { - Err(EvalAltResult::ErrorUnboundThis(*pos).into()) + Err(ERR::ErrorUnboundThis(*pos).into()) } } _ if state.always_search_scope => (0, expr.position()), Expr::Variable(Some(i), pos, _) => (i.get() as usize, *pos), Expr::Variable(None, pos, v) => (v.0.map(NonZeroUsize::get).unwrap_or(0), *pos), - _ => unreachable!("Expr::Variable expected, but gets {:?}", expr), + _ => unreachable!("Expr::Variable expected but gets {:?}", expr), }; // Check the variable resolver, if any @@ -1307,14 +1348,14 @@ impl Engine { let context = EvalContext { engine: self, scope, - mods, + global, state, lib, this_ptr, level: 0, }; match resolve_var( - expr.get_variable_name(true).expect("`Variable`"), + expr.get_variable_name(true).expect("`Expr::Variable`"), index, &context, ) { @@ -1331,10 +1372,10 @@ impl Engine { scope.len() - index } else { // Find the variable in the scope - let var_name = expr.get_variable_name(true).expect("`Variable`"); + let var_name = expr.get_variable_name(true).expect("`Expr::Variable`"); scope .get_index(var_name) - .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(var_name.to_string(), var_pos))? + .ok_or_else(|| ERR::ErrorVariableNotFound(var_name.to_string(), var_pos))? .0 }; @@ -1348,7 +1389,7 @@ impl Engine { #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] fn eval_dot_index_chain_helper( &self, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, @@ -1360,12 +1401,12 @@ impl Engine { chain_type: ChainType, level: usize, new_val: Option<((Dynamic, Position), (Option, Position))>, - ) -> Result<(Dynamic, bool), Box> { + ) -> RhaiResultOf<(Dynamic, bool)> { let is_ref_mut = target.is_ref(); let _terminate_chaining = terminate_chaining; // Pop the last index value - let idx_val = idx_values.pop().expect("not empty"); + let idx_val = idx_values.pop().unwrap(); match chain_type { #[cfg(not(feature = "no_index"))] @@ -1381,17 +1422,17 @@ impl Engine { { let mut idx_val_for_setter = idx_val.clone(); let idx_pos = x.lhs.position(); - let rhs_chain = match_chaining_type(rhs); + let rhs_chain = rhs.into(); let (try_setter, result) = { let mut obj = self.get_indexed_mut( - mods, state, lib, target, idx_val, idx_pos, false, true, level, + global, state, lib, target, idx_val, idx_pos, false, true, level, )?; let is_obj_temp_val = obj.is_temp_value(); let obj_ptr = &mut obj; match self.eval_dot_index_chain_helper( - mods, state, lib, this_ptr, obj_ptr, root, &x.rhs, *term, + global, state, lib, this_ptr, obj_ptr, root, &x.rhs, *term, idx_values, rhs_chain, level, new_val, ) { Ok((result, true)) if is_obj_temp_val => { @@ -1405,23 +1446,20 @@ impl Engine { if let Some(mut new_val) = try_setter { // Try to call index setter if value is changed let hash_set = - crate::ast::FnCallHashes::from_native(mods.hash_idx_set()); + crate::ast::FnCallHashes::from_native(global.hash_idx_set()); let args = &mut [target, &mut idx_val_for_setter, &mut new_val]; if let Err(err) = self.exec_fn_call( - mods, state, lib, FN_IDX_SET, hash_set, args, is_ref_mut, true, + global, state, lib, FN_IDX_SET, hash_set, args, is_ref_mut, true, root_pos, None, level, ) { // Just ignore if there is no index setter - if !matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) { + if !matches!(*err, ERR::ErrorFunctionNotFound(_, _)) { return Err(err); } } } - self.check_data_size(target.as_ref()) - .map_err(|err| err.fill_position(root.1))?; - Ok(result) } // xxx[rhs] op= new_val @@ -1430,19 +1468,21 @@ impl Engine { let mut idx_val_for_setter = idx_val.clone(); let try_setter = match self.get_indexed_mut( - mods, state, lib, target, idx_val, pos, true, false, level, + global, state, lib, target, idx_val, pos, true, false, level, ) { // Indexed value is a reference - update directly Ok(ref mut obj_ptr) => { self.eval_op_assignment( - mods, state, lib, op_info, op_pos, obj_ptr, root, new_val, + global, state, lib, op_info, op_pos, obj_ptr, root, new_val, ) .map_err(|err| err.fill_position(new_pos))?; + #[cfg(not(feature = "unchecked"))] + self.check_data_size(obj_ptr, new_pos)?; None } // Can't index - try to call an index setter #[cfg(not(feature = "no_index"))] - Err(err) if matches!(*err, EvalAltResult::ErrorIndexingType(_, _)) => { + Err(err) if matches!(*err, ERR::ErrorIndexingType(_, _)) => { Some(new_val) } // Any other error @@ -1452,23 +1492,22 @@ impl Engine { if let Some(mut new_val) = try_setter { // Try to call index setter let hash_set = - crate::ast::FnCallHashes::from_native(mods.hash_idx_set()); + crate::ast::FnCallHashes::from_native(global.hash_idx_set()); let args = &mut [target, &mut idx_val_for_setter, &mut new_val]; self.exec_fn_call( - mods, state, lib, FN_IDX_SET, hash_set, args, is_ref_mut, true, + global, state, lib, FN_IDX_SET, hash_set, args, is_ref_mut, true, root_pos, None, level, )?; } - self.check_data_size(target.as_ref()) - .map_err(|err| err.fill_position(root.1))?; - Ok((Dynamic::UNIT, true)) } // xxx[rhs] _ => self - .get_indexed_mut(mods, state, lib, target, idx_val, pos, false, true, level) + .get_indexed_mut( + global, state, lib, target, idx_val, pos, false, true, level, + ) .map(|v| (v.take_or_clone(), false)), } } @@ -1479,11 +1518,9 @@ impl Engine { // xxx.fn_name(arg_expr_list) Expr::FnCall(x, pos) if !x.is_qualified() && new_val.is_none() => { let FnCallExpr { name, hashes, .. } = x.as_ref(); - let call_args = &mut idx_val - .into_fn_call_args() - .expect("`ChainType::Dot` with `Expr::FnCallExpr`"); + let call_args = &mut idx_val.into_fn_call_args(); self.make_method_call( - mods, state, lib, name, *hashes, target, call_args, *pos, level, + global, state, lib, name, *hashes, target, call_args, *pos, level, ) } // xxx.fn_name(...) = ??? @@ -1501,15 +1538,15 @@ impl Engine { let index = name.into(); { let val_target = &mut self.get_indexed_mut( - mods, state, lib, target, index, *pos, true, false, level, + global, state, lib, target, index, *pos, true, false, level, )?; self.eval_op_assignment( - mods, state, lib, op_info, op_pos, val_target, root, new_val, + global, state, lib, op_info, op_pos, val_target, root, new_val, ) .map_err(|err| err.fill_position(new_pos))?; } - self.check_data_size(target.as_ref()) - .map_err(|err| err.fill_position(root.1))?; + #[cfg(not(feature = "unchecked"))] + self.check_data_size(target.source(), new_pos)?; Ok((Dynamic::UNIT, true)) } // {xxx:map}.id @@ -1517,7 +1554,7 @@ impl Engine { let (name, pos) = &x.2; let index = name.into(); let val = self.get_indexed_mut( - mods, state, lib, target, index, *pos, false, false, level, + global, state, lib, target, index, *pos, false, false, level, )?; Ok((val.take_or_clone(), false)) } @@ -1531,21 +1568,21 @@ impl Engine { let args = &mut [target.as_mut()]; let (mut orig_val, _) = self .exec_fn_call( - mods, state, lib, getter, hash, args, is_ref_mut, true, *pos, + global, state, lib, getter, hash, args, is_ref_mut, true, *pos, None, level, ) .or_else(|err| match *err { // Try an indexer if property does not exist - EvalAltResult::ErrorDotExpr(_, _) => { + ERR::ErrorDotExpr(_, _) => { let prop = name.into(); self.get_indexed_mut( - mods, state, lib, target, prop, *pos, false, true, + global, state, lib, target, prop, *pos, false, true, level, ) .map(|v| (v.take_or_clone(), false)) .map_err( |idx_err| match *idx_err { - EvalAltResult::ErrorIndexingType(_, _) => err, + ERR::ErrorIndexingType(_, _) => err, _ => idx_err, }, ) @@ -1554,7 +1591,7 @@ impl Engine { })?; self.eval_op_assignment( - mods, + global, state, lib, op_info, @@ -1565,33 +1602,30 @@ impl Engine { ) .map_err(|err| err.fill_position(new_pos))?; - self.check_data_size(target.as_ref()) - .map_err(|err| err.fill_position(root.1))?; - new_val = orig_val; } let hash = crate::ast::FnCallHashes::from_native(*hash_set); let args = &mut [target.as_mut(), &mut new_val]; self.exec_fn_call( - mods, state, lib, setter, hash, args, is_ref_mut, true, *pos, None, + global, state, lib, setter, hash, args, is_ref_mut, true, *pos, None, level, ) .or_else(|err| match *err { // Try an indexer if property does not exist - EvalAltResult::ErrorDotExpr(_, _) => { + ERR::ErrorDotExpr(_, _) => { let args = &mut [target, &mut name.into(), &mut new_val]; let hash_set = - crate::ast::FnCallHashes::from_native(mods.hash_idx_set()); + crate::ast::FnCallHashes::from_native(global.hash_idx_set()); let pos = Position::NONE; self.exec_fn_call( - mods, state, lib, FN_IDX_SET, hash_set, args, is_ref_mut, true, - pos, None, level, + global, state, lib, FN_IDX_SET, hash_set, args, is_ref_mut, + true, pos, None, level, ) .map_err( |idx_err| match *idx_err { - EvalAltResult::ErrorIndexingType(_, _) => err, + ERR::ErrorIndexingType(_, _) => err, _ => idx_err, }, ) @@ -1605,21 +1639,21 @@ impl Engine { let hash = crate::ast::FnCallHashes::from_native(*hash_get); let args = &mut [target.as_mut()]; self.exec_fn_call( - mods, state, lib, getter, hash, args, is_ref_mut, true, *pos, None, + global, state, lib, getter, hash, args, is_ref_mut, true, *pos, None, level, ) .map_or_else( |err| match *err { // Try an indexer if property does not exist - EvalAltResult::ErrorDotExpr(_, _) => { + ERR::ErrorDotExpr(_, _) => { let prop = name.into(); self.get_indexed_mut( - mods, state, lib, target, prop, *pos, false, true, level, + global, state, lib, target, prop, *pos, false, true, level, ) .map(|v| (v.take_or_clone(), false)) .map_err(|idx_err| { match *idx_err { - EvalAltResult::ErrorIndexingType(_, _) => err, + ERR::ErrorIndexingType(_, _) => err, _ => idx_err, } }) @@ -1639,17 +1673,16 @@ impl Engine { let (name, pos) = &p.2; let index = name.into(); self.get_indexed_mut( - mods, state, lib, target, index, *pos, false, true, level, + global, state, lib, target, index, *pos, false, true, level, )? } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr Expr::FnCall(ref x, pos) if !x.is_qualified() => { let FnCallExpr { name, hashes, .. } = x.as_ref(); - let call_args = &mut idx_val.into_fn_call_args().expect( - "`chain_type` is `ChainType::Dot` with `Expr::FnCallExpr`", - ); + let call_args = &mut idx_val.into_fn_call_args(); let (val, _) = self.make_method_call( - mods, state, lib, name, *hashes, target, call_args, pos, level, + global, state, lib, name, *hashes, target, call_args, pos, + level, )?; val.into() } @@ -1660,10 +1693,10 @@ impl Engine { // Others - syntax error ref expr => unreachable!("invalid dot expression: {:?}", expr), }; - let rhs_chain = match_chaining_type(rhs); + let rhs_chain = rhs.into(); self.eval_dot_index_chain_helper( - mods, state, lib, this_ptr, val_target, root, &x.rhs, *term, + global, state, lib, this_ptr, val_target, root, &x.rhs, *term, idx_values, rhs_chain, level, new_val, ) .map_err(|err| err.fill_position(*x_pos)) @@ -1675,7 +1708,7 @@ impl Engine { Expr::Property(ref p) => { let ((getter, hash_get), (setter, hash_set), (name, pos)) = p.as_ref(); - let rhs_chain = match_chaining_type(rhs); + let rhs_chain = rhs.into(); let hash_get = crate::ast::FnCallHashes::from_native(*hash_get); let hash_set = crate::ast::FnCallHashes::from_native(*hash_set); let mut arg_values = [target.as_mut(), &mut Dynamic::UNIT.clone()]; @@ -1684,21 +1717,21 @@ impl Engine { // Assume getters are always pure let (mut val, _) = self .exec_fn_call( - mods, state, lib, getter, hash_get, args, is_ref_mut, true, - *pos, None, level, + global, state, lib, getter, hash_get, args, is_ref_mut, + true, *pos, None, level, ) .or_else(|err| match *err { // Try an indexer if property does not exist - EvalAltResult::ErrorDotExpr(_, _) => { + ERR::ErrorDotExpr(_, _) => { let prop = name.into(); self.get_indexed_mut( - mods, state, lib, target, prop, *pos, false, true, - level, + global, state, lib, target, prop, *pos, false, + true, level, ) .map(|v| (v.take_or_clone(), false)) .map_err( |idx_err| match *idx_err { - EvalAltResult::ErrorIndexingType(_, _) => err, + ERR::ErrorIndexingType(_, _) => err, _ => idx_err, }, ) @@ -1710,7 +1743,7 @@ impl Engine { let (result, may_be_changed) = self .eval_dot_index_chain_helper( - mods, + global, state, lib, this_ptr, @@ -1731,25 +1764,25 @@ impl Engine { let mut arg_values = [target.as_mut(), val]; let args = &mut arg_values; self.exec_fn_call( - mods, state, lib, setter, hash_set, args, is_ref_mut, true, - *pos, None, level, + global, state, lib, setter, hash_set, args, is_ref_mut, + true, *pos, None, level, ) .or_else( |err| match *err { // Try an indexer if property does not exist - EvalAltResult::ErrorDotExpr(_, _) => { + ERR::ErrorDotExpr(_, _) => { let args = &mut [target.as_mut(), &mut name.into(), val]; let hash_set = crate::ast::FnCallHashes::from_native( - mods.hash_idx_set(), + global.hash_idx_set(), ); self.exec_fn_call( - mods, state, lib, FN_IDX_SET, hash_set, args, + global, state, lib, FN_IDX_SET, hash_set, args, is_ref_mut, true, *pos, None, level, ) .or_else(|idx_err| match *idx_err { - EvalAltResult::ErrorIndexingType(_, _) => { + ERR::ErrorIndexingType(_, _) => { // If there is no setter, no need to feed it back because // the property is read-only Ok((Dynamic::UNIT, false)) @@ -1760,8 +1793,6 @@ impl Engine { _ => Err(err), }, )?; - self.check_data_size(target.as_ref()) - .map_err(|err| err.fill_position(root.1))?; } Ok((result, may_be_changed)) @@ -1769,18 +1800,16 @@ impl Engine { // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr Expr::FnCall(ref f, pos) if !f.is_qualified() => { let FnCallExpr { name, hashes, .. } = f.as_ref(); - let rhs_chain = match_chaining_type(rhs); - let args = &mut idx_val.into_fn_call_args().expect( - "`chain_type` is `ChainType::Dot` with `Expr::FnCallExpr`", - ); + let rhs_chain = rhs.into(); + let args = &mut idx_val.into_fn_call_args(); let (mut val, _) = self.make_method_call( - mods, state, lib, name, *hashes, target, args, pos, level, + global, state, lib, name, *hashes, target, args, pos, level, )?; let val = &mut val; let target = &mut val.into(); self.eval_dot_index_chain_helper( - mods, state, lib, this_ptr, target, root, &x.rhs, *term, + global, state, lib, this_ptr, target, root, &x.rhs, *term, idx_values, rhs_chain, level, new_val, ) .map_err(|err| err.fill_position(pos)) @@ -1794,7 +1823,7 @@ impl Engine { } } // Syntax error - _ => Err(EvalAltResult::ErrorDotExpr("".into(), rhs.position()).into()), + _ => Err(ERR::ErrorDotExpr("".into(), rhs.position()).into()), } } } @@ -1805,7 +1834,7 @@ impl Engine { fn eval_dot_index_chain( &self, scope: &mut Scope, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, @@ -1818,46 +1847,48 @@ impl Engine { Expr::Index(x, term, pos) => (x.as_ref(), ChainType::Indexing, *term, *pos), #[cfg(not(feature = "no_object"))] Expr::Dot(x, term, pos) => (x.as_ref(), ChainType::Dotting, *term, *pos), - _ => unreachable!("index or dot chain expected, but gets {:?}", expr), + expr => unreachable!("Expr::Index or Expr::Dot expected but gets {:?}", expr), }; let idx_values = &mut StaticVec::new_const(); self.eval_dot_index_chain_arguments( - scope, mods, state, lib, this_ptr, rhs, term, chain_type, idx_values, 0, level, + scope, global, state, lib, this_ptr, rhs, term, chain_type, idx_values, 0, level, )?; + let is_assignment = new_val.is_some(); + match lhs { // id.??? or id[???] Expr::Variable(_, var_pos, x) => { #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut mods.num_operations, *var_pos)?; + self.inc_operations(&mut global.num_operations, *var_pos)?; let (mut target, _) = - self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?; + self.search_namespace(scope, global, state, lib, this_ptr, lhs)?; let obj_ptr = &mut target; let root = (x.2.as_str(), *var_pos); self.eval_dot_index_chain_helper( - mods, state, lib, &mut None, obj_ptr, root, rhs, term, idx_values, chain_type, - level, new_val, + global, state, lib, &mut None, obj_ptr, root, rhs, term, idx_values, + chain_type, level, new_val, ) .map(|(v, _)| v) .map_err(|err| err.fill_position(op_pos)) } // {expr}.??? = ??? or {expr}[???] = ??? - _ if new_val.is_some() => unreachable!("cannot assign to an expression"), + _ if is_assignment => unreachable!("cannot assign to an expression"), // {expr}.??? or {expr}[???] expr => { - let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let value = self.eval_expr(scope, global, state, lib, this_ptr, expr, level)?; let obj_ptr = &mut value.into(); let root = ("", expr.position()); self.eval_dot_index_chain_helper( - mods, state, lib, this_ptr, obj_ptr, root, rhs, term, idx_values, chain_type, + global, state, lib, this_ptr, obj_ptr, root, rhs, term, idx_values, chain_type, level, new_val, ) - .map(|(v, _)| v) + .map(|(v, _)| if is_assignment { Dynamic::UNIT } else { v }) .map_err(|err| err.fill_position(op_pos)) } } @@ -1870,7 +1901,7 @@ impl Engine { fn eval_dot_index_chain_arguments( &self, scope: &mut Scope, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, @@ -1880,9 +1911,9 @@ impl Engine { idx_values: &mut StaticVec, size: usize, level: usize, - ) -> Result<(), Box> { + ) -> RhaiResultOf<()> { #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut mods.num_operations, expr.position())?; + self.inc_operations(&mut global.num_operations, expr.position())?; let _parent_chain_type = parent_chain_type; @@ -1894,10 +1925,10 @@ impl Engine { } = x.as_ref(); let (values, pos) = args.iter().try_fold( - (StaticVec::with_capacity(args.len()), Position::NONE), - |(mut values, mut pos), expr| -> Result<_, Box> { + (crate::FnArgsVec::with_capacity(args.len()), Position::NONE), + |(mut values, mut pos), expr| -> RhaiResultOf<_> { let (value, arg_pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, expr, constants, + scope, global, state, lib, this_ptr, level, expr, constants, )?; if values.is_empty() { pos = arg_pos; @@ -1907,7 +1938,7 @@ impl Engine { }, )?; - idx_values.push((values, pos).into()); + idx_values.push(ChainArgument::from_fn_call_args(values, pos)); } #[cfg(not(feature = "no_object"))] Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dotting => { @@ -1924,7 +1955,7 @@ impl Engine { let crate::ast::BinaryExpr { lhs, rhs, .. } = x.as_ref(); // Evaluate in left-to-right order - let lhs_val = match lhs { + let lhs_arg_val = match lhs { #[cfg(not(feature = "no_object"))] Expr::Property(x) if _parent_chain_type == ChainType::Dotting => { ChainArgument::Property((x.2).1) @@ -1939,21 +1970,20 @@ impl Engine { args, constants, .. } = x.as_ref(); - args.iter() - .try_fold( - (StaticVec::with_capacity(args.len()), Position::NONE), - |(mut values, mut pos), expr| -> Result<_, Box> { - let (value, arg_pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, expr, constants, - )?; - if values.is_empty() { - pos = arg_pos - } - values.push(value.flatten()); - Ok((values, pos)) - }, - )? - .into() + let (values, pos) = args.iter().try_fold( + (crate::FnArgsVec::with_capacity(args.len()), Position::NONE), + |(mut values, mut pos), expr| -> RhaiResultOf<_> { + let (value, arg_pos) = self.get_arg_value( + scope, global, state, lib, this_ptr, level, expr, constants, + )?; + if values.is_empty() { + pos = arg_pos + } + values.push(value.flatten()); + Ok((values, pos)) + }, + )?; + ChainArgument::from_fn_call_args(values, pos) } #[cfg(not(feature = "no_object"))] Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dotting => { @@ -1965,20 +1995,20 @@ impl Engine { } #[cfg(not(feature = "no_index"))] _ if _parent_chain_type == ChainType::Indexing => self - .eval_expr(scope, mods, state, lib, this_ptr, lhs, level) - .map(|v| (v.flatten(), lhs.position()).into())?, + .eval_expr(scope, global, state, lib, this_ptr, lhs, level) + .map(|v| ChainArgument::from_index_value(v.flatten(), lhs.position()))?, expr => unreachable!("unknown chained expression: {:?}", expr), }; // Push in reverse order - let chain_type = match_chaining_type(expr); + let chain_type = expr.into(); self.eval_dot_index_chain_arguments( - scope, mods, state, lib, this_ptr, rhs, *term, chain_type, idx_values, size, + scope, global, state, lib, this_ptr, rhs, *term, chain_type, idx_values, size, level, )?; - idx_values.push(lhs_val); + idx_values.push(lhs_arg_val); } #[cfg(not(feature = "no_object"))] @@ -1987,8 +2017,8 @@ impl Engine { } #[cfg(not(feature = "no_index"))] _ if _parent_chain_type == ChainType::Indexing => idx_values.push( - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - .map(|v| (v.flatten(), expr.position()).into())?, + self.eval_expr(scope, global, state, lib, this_ptr, expr, level) + .map(|v| ChainArgument::from_index_value(v.flatten(), expr.position()))?, ), _ => unreachable!("unknown chained expression: {:?}", expr), } @@ -2001,7 +2031,7 @@ impl Engine { #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] fn get_indexed_mut<'t>( &self, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], target: &'t mut Dynamic, @@ -2010,9 +2040,9 @@ impl Engine { add_if_not_found: bool, use_indexers: bool, level: usize, - ) -> Result, Box> { + ) -> RhaiResultOf> { #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut mods.num_operations, Position::NONE)?; + self.inc_operations(&mut global.num_operations, Position::NONE)?; let mut idx = idx; let _add_if_not_found = add_if_not_found; @@ -2033,11 +2063,10 @@ impl Engine { arr_len - index .checked_abs() - .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos)) + .ok_or_else(|| ERR::ErrorArrayBounds(arr_len, index, idx_pos)) .and_then(|n| { if n as usize > arr_len { - Err(EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos) - .into()) + Err(ERR::ErrorArrayBounds(arr_len, index, idx_pos).into()) } else { Ok(n as usize) } @@ -2055,7 +2084,7 @@ impl Engine { arr.get_mut(arr_idx) .map(Target::from) - .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos).into()) + .ok_or_else(|| ERR::ErrorArrayBounds(arr_len, index, idx_pos).into()) } #[cfg(not(feature = "no_index"))] @@ -2073,11 +2102,10 @@ impl Engine { arr_len - index .checked_abs() - .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos)) + .ok_or_else(|| ERR::ErrorArrayBounds(arr_len, index, idx_pos)) .and_then(|n| { if n as usize > arr_len { - Err(EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos) - .into()) + Err(ERR::ErrorArrayBounds(arr_len, index, idx_pos).into()) } else { Ok(n as usize) } @@ -2096,10 +2124,12 @@ impl Engine { let value = arr .get(arr_idx) .map(|&v| (v as INT).into()) - .ok_or_else(|| { - Box::new(EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos)) - })?; - Ok(Target::BlobByte(target, arr_idx, value)) + .ok_or_else(|| Box::new(ERR::ErrorArrayBounds(arr_len, index, idx_pos)))?; + Ok(Target::BlobByte { + source: target, + value, + index: arr_idx, + }) } #[cfg(not(feature = "no_object"))] @@ -2136,9 +2166,9 @@ impl Engine { let end = range.end; if start < 0 || start as usize >= BITS { - return Err(EvalAltResult::ErrorBitFieldBounds(BITS, start, idx_pos).into()); + return Err(ERR::ErrorBitFieldBounds(BITS, start, idx_pos).into()); } else if end < 0 || end as usize >= BITS { - return Err(EvalAltResult::ErrorBitFieldBounds(BITS, end, idx_pos).into()); + return Err(ERR::ErrorBitFieldBounds(BITS, end, idx_pos).into()); } else if end <= start { (0, 0) } else if end as usize == BITS && start == 0 { @@ -2156,9 +2186,9 @@ impl Engine { let end = *range.end(); if start < 0 || start as usize >= BITS { - return Err(EvalAltResult::ErrorBitFieldBounds(BITS, start, idx_pos).into()); + return Err(ERR::ErrorBitFieldBounds(BITS, start, idx_pos).into()); } else if end < 0 || end as usize >= BITS { - return Err(EvalAltResult::ErrorBitFieldBounds(BITS, end, idx_pos).into()); + return Err(ERR::ErrorBitFieldBounds(BITS, end, idx_pos).into()); } else if end < start { (0, 0) } else if end as usize == BITS - 1 && start == 0 { @@ -2172,12 +2202,17 @@ impl Engine { ) } } else { - unreachable!("`Range` or `RangeInclusive`"); + unreachable!("Range or RangeInclusive expected but gets {:?}", idx); }; let field_value = (*value & mask) >> shift; - Ok(Target::BitField(target, mask, field_value.into(), shift)) + Ok(Target::BitField { + source: target, + value: field_value.into(), + mask, + shift, + }) } #[cfg(not(feature = "no_index"))] @@ -2193,9 +2228,7 @@ impl Engine { let offset = index as usize; ( if offset >= BITS { - return Err( - EvalAltResult::ErrorBitFieldBounds(BITS, index, idx_pos).into() - ); + return Err(ERR::ErrorBitFieldBounds(BITS, index, idx_pos).into()); } else { (*value & (1 << offset)) != 0 }, @@ -2206,19 +2239,21 @@ impl Engine { ( // Count from end if negative if offset > BITS { - return Err( - EvalAltResult::ErrorBitFieldBounds(BITS, index, idx_pos).into() - ); + return Err(ERR::ErrorBitFieldBounds(BITS, index, idx_pos).into()); } else { (*value & (1 << (BITS - offset))) != 0 }, offset as u8, ) } else { - return Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, idx_pos).into()); + return Err(ERR::ErrorBitFieldBounds(BITS, index, idx_pos).into()); }; - Ok(Target::Bit(target, bit_value.into(), offset)) + Ok(Target::Bit { + source: target, + value: bit_value.into(), + bit: offset, + }) } #[cfg(not(feature = "no_index"))] @@ -2233,7 +2268,7 @@ impl Engine { ( s.chars().nth(offset).ok_or_else(|| { let chars_len = s.chars().count(); - EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos) + ERR::ErrorStringBounds(chars_len, index, idx_pos) })?, offset, ) @@ -2243,30 +2278,35 @@ impl Engine { // Count from end if negative s.chars().rev().nth(offset - 1).ok_or_else(|| { let chars_len = s.chars().count(); - EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos) + ERR::ErrorStringBounds(chars_len, index, idx_pos) })?, offset, ) } else { let chars_len = s.chars().count(); - return Err(EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos).into()); + return Err(ERR::ErrorStringBounds(chars_len, index, idx_pos).into()); }; - Ok(Target::StringChar(target, offset, ch.into())) + Ok(Target::StringChar { + source: target, + value: ch.into(), + index: offset, + }) } _ if use_indexers => { let args = &mut [target, &mut idx]; - let hash_get = crate::ast::FnCallHashes::from_native(mods.hash_idx_get()); + let hash_get = crate::ast::FnCallHashes::from_native(global.hash_idx_get()); let idx_pos = Position::NONE; self.exec_fn_call( - mods, state, lib, FN_IDX_GET, hash_get, args, true, true, idx_pos, None, level, + global, state, lib, FN_IDX_GET, hash_get, args, true, true, idx_pos, None, + level, ) .map(|(v, _)| v.into()) } - _ => Err(EvalAltResult::ErrorIndexingType( + _ => Err(ERR::ErrorIndexingType( format!( "{} [{}]", self.map_type_name(target.type_name()), @@ -2278,67 +2318,113 @@ impl Engine { } } + /// Evaluate a function call expression. + fn eval_fn_call_expr( + &self, + scope: &mut Scope, + global: &mut GlobalRuntimeState, + state: &mut EvalState, + lib: &[&Module], + this_ptr: &mut Option<&mut Dynamic>, + expr: &FnCallExpr, + pos: Position, + level: usize, + ) -> RhaiResult { + let FnCallExpr { + name, + namespace, + capture_parent_scope: capture, + hashes, + args, + constants, + .. + } = expr; + + if let Some(namespace) = namespace.as_ref() { + // Qualified function call + let hash = hashes.native; + + self.make_qualified_function_call( + scope, global, state, lib, this_ptr, namespace, name, args, constants, hash, pos, + level, + ) + } else { + // Normal function call + self.make_function_call( + scope, global, state, lib, this_ptr, name, args, constants, *hashes, pos, *capture, + level, + ) + } + } + /// Evaluate an expression. pub(crate) fn eval_expr( &self, scope: &mut Scope, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, level: usize, ) -> RhaiResult { - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut mods.num_operations, expr.position())?; + // Coded this way for better branch prediction. + // Popular branches are lifted out of the `match` statement into their own branches. - let result = match expr { + // Function calls should account for a relatively larger portion of expressions because + // binary operators are also function calls. + if let Expr::FnCall(x, pos) = expr { + #[cfg(not(feature = "unchecked"))] + self.inc_operations(&mut global.num_operations, expr.position())?; + + return self.eval_fn_call_expr(scope, global, state, lib, this_ptr, x, *pos, level); + } + + // Then variable access. + // We shouldn't do this for too many variants because, soon or later, the added comparisons + // will cost more than the mis-predicted `match` branch. + if let Expr::Variable(index, var_pos, x) = expr { + #[cfg(not(feature = "unchecked"))] + self.inc_operations(&mut global.num_operations, expr.position())?; + + return if index.is_none() && x.0.is_none() && x.2 == KEYWORD_THIS { + this_ptr + .as_deref() + .cloned() + .ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into()) + } else { + self.search_namespace(scope, global, state, lib, this_ptr, expr) + .map(|(val, _)| val.take_or_clone()) + }; + } + + #[cfg(not(feature = "unchecked"))] + self.inc_operations(&mut global.num_operations, expr.position())?; + + match expr { + // Constants Expr::DynamicConstant(x, _) => Ok(x.as_ref().clone()), Expr::IntegerConstant(x, _) => Ok((*x).into()), #[cfg(not(feature = "no_float"))] Expr::FloatConstant(x, _) => Ok((*x).into()), Expr::StringConstant(x, _) => Ok(x.clone().into()), Expr::CharConstant(x, _) => Ok((*x).into()), - - Expr::Variable(None, var_pos, x) if x.0.is_none() && x.2 == KEYWORD_THIS => this_ptr - .as_deref() - .cloned() - .ok_or_else(|| EvalAltResult::ErrorUnboundThis(*var_pos).into()), - Expr::Variable(_, _, _) => self - .search_namespace(scope, mods, state, lib, this_ptr, expr) - .map(|(val, _)| val.take_or_clone()), - - // Statement block - Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT), - Expr::Stmt(x) => { - self.eval_stmt_block(scope, mods, state, lib, this_ptr, x, true, true, level) - } - - // lhs[idx_expr] - #[cfg(not(feature = "no_index"))] - Expr::Index(_, _, _) => { - self.eval_dot_index_chain(scope, mods, state, lib, this_ptr, expr, level, None) - } - - // lhs.dot_rhs - #[cfg(not(feature = "no_object"))] - Expr::Dot(_, _, _) => { - self.eval_dot_index_chain(scope, mods, state, lib, this_ptr, expr, level, None) - } + Expr::BoolConstant(x, _) => Ok((*x).into()), + Expr::Unit(_) => Ok(Dynamic::UNIT), // `... ${...} ...` Expr::InterpolatedString(x, pos) => { let mut pos = *pos; let mut result: Dynamic = self.const_empty_string().into(); - x.iter().try_for_each(|expr| { - let item = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + for expr in x.iter() { + let item = self.eval_expr(scope, global, state, lib, this_ptr, expr, level)?; self.eval_op_assignment( - mods, + global, state, lib, - Some(OpAssignment::new(TOKEN_OP_CONCAT)), + Some(OpAssignment::new(OP_CONCAT)), pos, &mut (&mut result).into(), ("", Position::NONE), @@ -2347,92 +2433,87 @@ impl Engine { .map_err(|err| err.fill_position(expr.position()))?; pos = expr.position(); - - self.check_data_size(&result) - .map_err(|err| err.fill_position(pos)) - })?; - - assert!( - result.is::(), - "interpolated string must be a string" - ); + } Ok(result) } #[cfg(not(feature = "no_index"))] - Expr::Array(x, _) => Ok(x - .iter() - .try_fold( - crate::Array::with_capacity(x.len()), - |mut arr, item| -> Result<_, Box> { - arr.push( - self.eval_expr(scope, mods, state, lib, this_ptr, item, level)? - .flatten(), + Expr::Array(x, _) => { + let mut arr = Dynamic::from_array(crate::Array::with_capacity(x.len())); + + #[cfg(not(feature = "unchecked"))] + let mut sizes = (0, 0, 0); + + for item_expr in x.iter() { + let value = self + .eval_expr(scope, global, state, lib, this_ptr, item_expr, level)? + .flatten(); + + #[cfg(not(feature = "unchecked"))] + let val_sizes = Self::calc_data_sizes(&value, true); + + arr.write_lock::() + .expect("`Array`") + .push(value); + + #[cfg(not(feature = "unchecked"))] + if self.has_data_size_limit() { + sizes = ( + sizes.0 + val_sizes.0, + sizes.1 + val_sizes.1, + sizes.2 + val_sizes.2, ); - Ok(arr) - }, - )? - .into()), + self.raise_err_if_over_data_size_limit(sizes, item_expr.position())?; + } + } - #[cfg(not(feature = "no_object"))] - Expr::Map(x, _) => Ok(x - .0 - .iter() - .try_fold( - x.1.clone(), - |mut map, (Ident { name: key, .. }, expr)| -> Result<_, Box> { - let value_ref = map.get_mut(key.as_str()).expect("contains all keys"); - *value_ref = self - .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? - .flatten(); - Ok(map) - }, - )? - .into()), - - // Namespace-qualified function call - Expr::FnCall(x, pos) if x.is_qualified() => { - let FnCallExpr { - name, - namespace, - hashes, - args, - constants, - .. - } = x.as_ref(); - let namespace = namespace.as_ref().expect("qualified function call"); - let hash = hashes.native; - self.make_qualified_function_call( - scope, mods, state, lib, this_ptr, namespace, name, args, constants, hash, - *pos, level, - ) + Ok(arr) } - // Normal function call - Expr::FnCall(x, pos) => { - let FnCallExpr { - name, - capture_parent_scope: capture, - hashes, - args, - constants, - .. - } = x.as_ref(); - self.make_function_call( - scope, mods, state, lib, this_ptr, name, args, constants, *hashes, *pos, - *capture, level, - ) + #[cfg(not(feature = "no_object"))] + Expr::Map(x, _) => { + let mut map = Dynamic::from_map(x.1.clone()); + + #[cfg(not(feature = "unchecked"))] + let mut sizes = (0, 0, 0); + + for (Ident { name, .. }, value_expr) in x.0.iter() { + let key = name.as_str(); + let value = self + .eval_expr(scope, global, state, lib, this_ptr, value_expr, level)? + .flatten(); + + #[cfg(not(feature = "unchecked"))] + let val_sizes = Self::calc_data_sizes(&value, true); + + *map.write_lock::() + .expect("`Map`") + .get_mut(key) + .unwrap() = value; + + #[cfg(not(feature = "unchecked"))] + if self.has_data_size_limit() { + sizes = ( + sizes.0 + val_sizes.0, + sizes.1 + val_sizes.1, + sizes.2 + val_sizes.2, + ); + self.raise_err_if_over_data_size_limit(sizes, value_expr.position())?; + } + } + + Ok(map) } Expr::And(x, _) => { Ok((self - .eval_expr(scope, mods, state, lib, this_ptr, &x.lhs, level)? + .eval_expr(scope, global, state, lib, this_ptr, &x.lhs, level)? .as_bool() .map_err(|typ| self.make_type_mismatch_err::(typ, x.lhs.position()))? && // Short-circuit using && self - .eval_expr(scope, mods, state, lib, this_ptr, &x.rhs, level)? + .eval_expr(scope, global, state, lib, this_ptr, &x.rhs, level)? .as_bool() .map_err(|typ| self.make_type_mismatch_err::(typ, x.rhs.position()))?) .into()) @@ -2440,54 +2521,65 @@ impl Engine { Expr::Or(x, _) => { Ok((self - .eval_expr(scope, mods, state, lib, this_ptr, &x.lhs, level)? + .eval_expr(scope, global, state, lib, this_ptr, &x.lhs, level)? .as_bool() .map_err(|typ| self.make_type_mismatch_err::(typ, x.lhs.position()))? || // Short-circuit using || self - .eval_expr(scope, mods, state, lib, this_ptr, &x.rhs, level)? + .eval_expr(scope, global, state, lib, this_ptr, &x.rhs, level)? .as_bool() .map_err(|typ| self.make_type_mismatch_err::(typ, x.rhs.position()))?) .into()) } - Expr::BoolConstant(x, _) => Ok((*x).into()), - Expr::Unit(_) => Ok(Dynamic::UNIT), - Expr::Custom(custom, _) => { let expressions: StaticVec<_> = custom.inputs.iter().map(Into::into).collect(); - let key_token = custom.tokens.first().expect("not empty"); - let custom_def = self.custom_syntax.get(key_token).expect("must match"); + let key_token = custom.tokens.first().unwrap(); + let custom_def = self.custom_syntax.get(key_token).unwrap(); let mut context = EvalContext { engine: self, scope, - mods, + global, state, lib, this_ptr, level, }; - (custom_def.func)(&mut context, &expressions) + + let result = (custom_def.func)(&mut context, &expressions); + + self.check_return_value(result, expr.position()) + } + + Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT), + Expr::Stmt(x) => { + self.eval_stmt_block(scope, global, state, lib, this_ptr, x, true, level) + } + + #[cfg(not(feature = "no_index"))] + Expr::Index(_, _, _) => { + self.eval_dot_index_chain(scope, global, state, lib, this_ptr, expr, level, None) + } + + #[cfg(not(feature = "no_object"))] + Expr::Dot(_, _, _) => { + self.eval_dot_index_chain(scope, global, state, lib, this_ptr, expr, level, None) } _ => unreachable!("expression cannot be evaluated: {:?}", expr), - }; - - self.check_return_value(result) - .map_err(|err| err.fill_position(expr.position())) + } } /// Evaluate a statements block. pub(crate) fn eval_stmt_block( &self, scope: &mut Scope, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, statements: &[Stmt], restore_orig_state: bool, - rewind_scope: bool, level: usize, ) -> RhaiResult { if statements.is_empty() { @@ -2496,24 +2588,35 @@ impl Engine { let orig_always_search_scope = state.always_search_scope; let orig_scope_len = scope.len(); - let orig_mods_len = mods.len(); + let orig_mods_len = global.num_imported_modules(); let orig_fn_resolution_caches_len = state.fn_resolution_caches_len(); - if rewind_scope { + if restore_orig_state { state.scope_level += 1; } - let result = statements.iter().try_fold(Dynamic::UNIT, |_, stmt| { - let _mods_len = mods.len(); + let mut result = Dynamic::UNIT; - let r = self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level)?; + for stmt in statements { + let _mods_len = global.num_imported_modules(); + + result = self.eval_stmt( + scope, + global, + state, + lib, + this_ptr, + stmt, + restore_orig_state, + level, + )?; #[cfg(not(feature = "no_module"))] if matches!(stmt, Stmt::Import(_, _, _)) { // Get the extra modules - see if any functions are marked global. // Without global functions, the extra modules never affect function resolution. - if mods - .scan_raw() + if global + .scan_modules_raw() .skip(_mods_len) .any(|(_, m)| m.contains_indexed_global_functions()) { @@ -2531,33 +2634,29 @@ impl Engine { } } } - - Ok(r) - }); + } // If imports list is modified, pop the functions lookup cache state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len); - if rewind_scope { + if restore_orig_state { scope.rewind(orig_scope_len); state.scope_level -= 1; - } - if restore_orig_state { - mods.truncate(orig_mods_len); + global.truncate_modules(orig_mods_len); // The impact of new local variables goes away at the end of a block // because any new variables introduced will go out of scope state.always_search_scope = orig_always_search_scope; } - result + Ok(result) } /// Evaluate an op-assignment statement. /// [`Position`] in [`EvalAltResult`] is [`NONE`][Position::NONE] and should be set afterwards. pub(crate) fn eval_op_assignment( &self, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], op_info: Option, @@ -2565,12 +2664,10 @@ impl Engine { target: &mut Target, root: (&str, Position), new_val: Dynamic, - ) -> Result<(), Box> { + ) -> RhaiResultOf<()> { if target.is_read_only() { // Assignment to constant variable - return Err( - EvalAltResult::ErrorAssignmentToConstant(root.0.to_string(), root.1).into(), - ); + return Err(ERR::ErrorAssignmentToConstant(root.0.to_string(), root.1).into()); } let mut new_val = new_val; @@ -2581,40 +2678,38 @@ impl Engine { op, }) = op_info { - { - let mut lock_guard; - let lhs_ptr_inner; + let mut lock_guard; + let lhs_ptr_inner; - #[cfg(not(feature = "no_closure"))] - let target_is_shared = target.is_shared(); - #[cfg(feature = "no_closure")] - let target_is_shared = false; + #[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 { - lock_guard = target.write_lock::().expect("`Dynamic`"); - lhs_ptr_inner = &mut *lock_guard; - } else { - lhs_ptr_inner = &mut *target; - } - - let hash = hash_op_assign; - let args = &mut [lhs_ptr_inner, &mut new_val]; - - match self.call_native_fn(mods, state, lib, op, hash, args, true, true, op_pos) { - Err(err) if matches!(*err, EvalAltResult::ErrorFunctionNotFound(ref f, _) if f.starts_with(op)) => - { - // Expand to `var = var op rhs` - let op = &op[..op.len() - 1]; // extract operator without = - - // Run function - let (value, _) = self.call_native_fn( - mods, state, lib, op, hash_op, args, true, false, op_pos, - )?; - - *args[0] = value.flatten(); - } - err => return err.map(|_| ()), + if target_is_shared { + lock_guard = target.write_lock::().expect("`Dynamic`"); + lhs_ptr_inner = &mut *lock_guard; + } else { + lhs_ptr_inner = &mut *target; + } + + let hash = hash_op_assign; + let args = &mut [lhs_ptr_inner, &mut new_val]; + + match self.call_native_fn(global, state, lib, op, hash, args, true, true, op_pos) { + Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, _) if f.starts_with(op)) => + { + // Expand to `var = var op rhs` + let op = &op[..op.len() - 1]; // extract operator without = + + // Run function + let (value, _) = self.call_native_fn( + global, state, lib, op, hash_op, args, true, false, op_pos, + )?; + + *args[0] = value.flatten(); } + err => return err.map(|_| ()), } } else { // Normal assignment @@ -2633,49 +2728,51 @@ impl Engine { pub(crate) fn eval_stmt( &self, scope: &mut Scope, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, stmt: &Stmt, + rewind_scope: bool, level: usize, ) -> RhaiResult { - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut mods.num_operations, stmt.position())?; + // Coded this way for better branch prediction. + // Popular branches are lifted out of the `match` statement into their own branches. - let result = match stmt { - // No-op - Stmt::Noop(_) => Ok(Dynamic::UNIT), + // Function calls should account for a relatively larger portion of statements. + if let Stmt::FnCall(x, pos) = stmt { + #[cfg(not(feature = "unchecked"))] + self.inc_operations(&mut global.num_operations, stmt.position())?; - // Expression as statement - Stmt::Expr(expr) => Ok(self - .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? - .flatten()), + return self.eval_fn_call_expr(scope, global, state, lib, this_ptr, x, *pos, level); + } - // var op= rhs - Stmt::Assignment(x, op_pos) if x.0.is_variable_access(false) => { + // Then assignments. + // We shouldn't do this for too many variants because, soon or later, the added comparisons + // will cost more than the mis-predicted `match` branch. + if let Stmt::Assignment(x, op_pos) = stmt { + #[cfg(not(feature = "unchecked"))] + self.inc_operations(&mut global.num_operations, stmt.position())?; + + return if x.0.is_variable_access(false) { let (lhs_expr, op_info, rhs_expr) = x.as_ref(); let rhs_val = self - .eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)? + .eval_expr(scope, global, state, lib, this_ptr, rhs_expr, level)? .flatten(); let (mut lhs_ptr, pos) = - self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?; + self.search_namespace(scope, global, state, lib, this_ptr, lhs_expr)?; - let var_name = lhs_expr.get_variable_name(false).expect("`Variable`"); + let var_name = lhs_expr.get_variable_name(false).expect("`Expr::Variable`"); if !lhs_ptr.is_ref() { - return Err(EvalAltResult::ErrorAssignmentToConstant( - var_name.to_string(), - pos, - ) - .into()); + return Err(ERR::ErrorAssignmentToConstant(var_name.to_string(), pos).into()); } #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut mods.num_operations, pos)?; + self.inc_operations(&mut global.num_operations, pos)?; self.eval_op_assignment( - mods, + global, state, lib, *op_info, @@ -2686,19 +2783,11 @@ impl Engine { ) .map_err(|err| err.fill_position(rhs_expr.position()))?; - if op_info.is_some() { - self.check_data_size(lhs_ptr.as_ref()) - .map_err(|err| err.fill_position(lhs_expr.position()))?; - } - Ok(Dynamic::UNIT) - } - - // lhs op= rhs - Stmt::Assignment(x, op_pos) => { + } else { let (lhs_expr, op_info, rhs_expr) = x.as_ref(); let rhs_val = self - .eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)? + .eval_expr(scope, global, state, lib, this_ptr, rhs_expr, level)? .flatten(); let _new_val = Some(((rhs_val, rhs_expr.position()), (*op_info, *op_pos))); @@ -2706,13 +2795,13 @@ impl Engine { match lhs_expr { // name op= rhs (handled above) Expr::Variable(_, _, _) => { - unreachable!("Expr::Variable case should already been handled") + unreachable!("Expr::Variable case is already handled") } // idx_lhs[idx_expr] op= rhs #[cfg(not(feature = "no_index"))] Expr::Index(_, _, _) => { self.eval_dot_index_chain( - scope, mods, state, lib, this_ptr, lhs_expr, level, _new_val, + scope, global, state, lib, this_ptr, lhs_expr, level, _new_val, )?; Ok(Dynamic::UNIT) } @@ -2720,40 +2809,49 @@ impl Engine { #[cfg(not(feature = "no_object"))] Expr::Dot(_, _, _) => { self.eval_dot_index_chain( - scope, mods, state, lib, this_ptr, lhs_expr, level, _new_val, + scope, global, state, lib, this_ptr, lhs_expr, level, _new_val, )?; Ok(Dynamic::UNIT) } _ => unreachable!("cannot assign to expression: {:?}", lhs_expr), } - } + }; + } + + #[cfg(not(feature = "unchecked"))] + self.inc_operations(&mut global.num_operations, stmt.position())?; + + match stmt { + // No-op + Stmt::Noop(_) => Ok(Dynamic::UNIT), + + // Expression as statement + Stmt::Expr(expr) => Ok(self + .eval_expr(scope, global, state, lib, this_ptr, expr, level)? + .flatten()), // Block scope Stmt::Block(statements, _) if statements.is_empty() => Ok(Dynamic::UNIT), - Stmt::Block(statements, _) => self.eval_stmt_block( - scope, mods, state, lib, this_ptr, statements, true, true, level, - ), + Stmt::Block(statements, _) => { + self.eval_stmt_block(scope, global, state, lib, this_ptr, statements, true, level) + } // If statement Stmt::If(expr, x, _) => { let guard_val = self - .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + .eval_expr(scope, global, state, lib, this_ptr, expr, level)? .as_bool() .map_err(|typ| self.make_type_mismatch_err::(typ, expr.position()))?; if guard_val { if !x.0.is_empty() { - self.eval_stmt_block( - scope, mods, state, lib, this_ptr, &x.0, true, true, level, - ) + self.eval_stmt_block(scope, global, state, lib, this_ptr, &x.0, true, level) } else { Ok(Dynamic::UNIT) } } else { if !x.1.is_empty() { - self.eval_stmt_block( - scope, mods, state, lib, this_ptr, &x.1, true, true, level, - ) + self.eval_stmt_block(scope, global, state, lib, this_ptr, &x.1, true, level) } else { Ok(Dynamic::UNIT) } @@ -2764,7 +2862,8 @@ impl Engine { Stmt::Switch(match_expr, x, _) => { let (table, def_stmt, ranges) = x.as_ref(); - let value = self.eval_expr(scope, mods, state, lib, this_ptr, match_expr, level)?; + let value = + self.eval_expr(scope, global, state, lib, this_ptr, match_expr, level)?; let stmt_block = if value.is_hashable() { let hasher = &mut get_hasher(); @@ -2775,7 +2874,7 @@ impl Engine { if let Some(t) = table.get(&hash) { if let Some(ref c) = t.0 { if self - .eval_expr(scope, mods, state, lib, this_ptr, &c, level) + .eval_expr(scope, global, state, lib, this_ptr, &c, level) .and_then(|v| { v.as_bool().map_err(|typ| { self.make_type_mismatch_err::(typ, c.position()) @@ -2802,7 +2901,7 @@ impl Engine { { if let Some(c) = condition { if !self - .eval_expr(scope, mods, state, lib, this_ptr, &c, level) + .eval_expr(scope, global, state, lib, this_ptr, &c, level) .and_then(|v| { v.as_bool().map_err(|typ| { self.make_type_mismatch_err::(typ, c.position()) @@ -2830,7 +2929,7 @@ impl Engine { if let Some(statements) = stmt_block { if !statements.is_empty() { self.eval_stmt_block( - scope, mods, state, lib, this_ptr, statements, true, true, level, + scope, global, state, lib, this_ptr, statements, true, level, ) } else { Ok(Dynamic::UNIT) @@ -2839,7 +2938,7 @@ impl Engine { // Default match clause if !def_stmt.is_empty() { self.eval_stmt_block( - scope, mods, state, lib, this_ptr, def_stmt, true, true, level, + scope, global, state, lib, this_ptr, def_stmt, true, level, ) } else { Ok(Dynamic::UNIT) @@ -2851,25 +2950,25 @@ impl Engine { Stmt::While(Expr::Unit(_), body, _) => loop { if !body.is_empty() { match self - .eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, true, level) + .eval_stmt_block(scope, global, state, lib, this_ptr, body, true, level) { Ok(_) => (), Err(err) => match *err { - EvalAltResult::LoopBreak(false, _) => (), - EvalAltResult::LoopBreak(true, _) => return Ok(Dynamic::UNIT), + ERR::LoopBreak(false, _) => (), + ERR::LoopBreak(true, _) => return Ok(Dynamic::UNIT), _ => return Err(err), }, } } else { #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut mods.num_operations, body.position())?; + self.inc_operations(&mut global.num_operations, body.position())?; } }, // While loop Stmt::While(expr, body, _) => loop { let condition = self - .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + .eval_expr(scope, global, state, lib, this_ptr, expr, level)? .as_bool() .map_err(|typ| self.make_type_mismatch_err::(typ, expr.position()))?; @@ -2878,12 +2977,12 @@ impl Engine { } if !body.is_empty() { match self - .eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, true, level) + .eval_stmt_block(scope, global, state, lib, this_ptr, body, true, level) { Ok(_) => (), Err(err) => match *err { - EvalAltResult::LoopBreak(false, _) => (), - EvalAltResult::LoopBreak(true, _) => return Ok(Dynamic::UNIT), + ERR::LoopBreak(false, _) => (), + ERR::LoopBreak(true, _) => return Ok(Dynamic::UNIT), _ => return Err(err), }, } @@ -2896,19 +2995,19 @@ impl Engine { if !body.is_empty() { match self - .eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, true, level) + .eval_stmt_block(scope, global, state, lib, this_ptr, body, true, level) { Ok(_) => (), Err(err) => match *err { - EvalAltResult::LoopBreak(false, _) => continue, - EvalAltResult::LoopBreak(true, _) => return Ok(Dynamic::UNIT), + ERR::LoopBreak(false, _) => continue, + ERR::LoopBreak(true, _) => return Ok(Dynamic::UNIT), _ => return Err(err), }, } } let condition = self - .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + .eval_expr(scope, global, state, lib, this_ptr, expr, level)? .as_bool() .map_err(|typ| self.make_type_mismatch_err::(typ, expr.position()))?; @@ -2921,7 +3020,7 @@ impl Engine { Stmt::For(expr, x, _) => { let (Ident { name, .. }, counter, statements) = x.as_ref(); let iter_obj = self - .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + .eval_expr(scope, global, state, lib, this_ptr, expr, level)? .flatten(); let iter_type = iter_obj.type_id(); @@ -2936,7 +3035,7 @@ impl Engine { .global_modules .iter() .find_map(|m| m.get_iter(iter_type)) - .or_else(|| mods.get_iter(iter_type)) + .or_else(|| global.get_iter(iter_type)) .or_else(|| { self.global_sub_modules .values() @@ -2946,63 +3045,76 @@ impl Engine { if let Some(func) = func { // Add the loop variables let orig_scope_len = scope.len(); - let counter_index = counter.as_ref().map(|Ident { name, .. }| { - scope.push(unsafe_cast_var_name_to_lifetime(name), 0 as INT); + let counter_index = if let Some(counter) = counter { + scope.push(unsafe_cast_var_name_to_lifetime(&counter.name), 0 as INT); scope.len() - 1 - }); + } else { + usize::MAX + }; scope.push(unsafe_cast_var_name_to_lifetime(name), ()); let index = scope.len() - 1; for (x, iter_value) in func(iter_obj).enumerate() { // Increment counter - if let Some(c) = counter_index { + if counter_index < usize::MAX { #[cfg(not(feature = "unchecked"))] if x > INT::MAX as usize { - return Err(EvalAltResult::ErrorArithmetic( + return Err(ERR::ErrorArithmetic( format!("for-loop counter overflow: {}", x), counter.as_ref().expect("`Some`").pos, ) .into()); } - let mut counter_var = scope - .get_mut_by_index(c) - .write_lock::() - .expect("`INT`"); - *counter_var = x as INT; + let index_value = (x as INT).into(); + + #[cfg(not(feature = "no_closure"))] + { + let index_var = scope.get_mut_by_index(counter_index); + if index_var.is_shared() { + *index_var.write_lock().expect("`Dynamic`") = index_value; + } else { + *index_var = index_value; + } + } + #[cfg(feature = "no_closure")] + { + *scope.get_mut_by_index(counter_index) = index_value; + } } - let loop_var = scope.get_mut_by_index(index); let value = iter_value.flatten(); #[cfg(not(feature = "no_closure"))] - let loop_var_is_shared = loop_var.is_shared(); + { + let loop_var = scope.get_mut_by_index(index); + if loop_var.is_shared() { + *loop_var.write_lock().expect("`Dynamic`") = value; + } else { + *loop_var = value; + } + } #[cfg(feature = "no_closure")] - let loop_var_is_shared = false; - - if loop_var_is_shared { - let mut value_ref = loop_var.write_lock().expect("`Dynamic`"); - *value_ref = value; - } else { - *loop_var = value; + { + *scope.get_mut_by_index(index) = value; } #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut mods.num_operations, statements.position())?; + self.inc_operations(&mut global.num_operations, statements.position())?; if statements.is_empty() { continue; } let result = self.eval_stmt_block( - scope, mods, state, lib, this_ptr, statements, true, true, level, + scope, global, state, lib, this_ptr, statements, true, level, ); match result { Ok(_) => (), Err(err) => match *err { - EvalAltResult::LoopBreak(false, _) => (), - EvalAltResult::LoopBreak(true, _) => break, + ERR::LoopBreak(false, _) => (), + ERR::LoopBreak(true, _) => break, _ => return Err(err), }, } @@ -3011,47 +3123,13 @@ impl Engine { scope.rewind(orig_scope_len); Ok(Dynamic::UNIT) } else { - Err(EvalAltResult::ErrorFor(expr.position()).into()) + Err(ERR::ErrorFor(expr.position()).into()) } } // Continue/Break statement Stmt::BreakLoop(options, pos) => { - Err(EvalAltResult::LoopBreak(options.contains(AST_OPTION_BREAK_OUT), *pos).into()) - } - - // Namespace-qualified function call - Stmt::FnCall(x, pos) if x.is_qualified() => { - let FnCallExpr { - name, - namespace, - hashes, - args, - constants, - .. - } = x.as_ref(); - let namespace = namespace.as_ref().expect("qualified function call"); - let hash = hashes.native; - self.make_qualified_function_call( - scope, mods, state, lib, this_ptr, namespace, name, args, constants, hash, - *pos, level, - ) - } - - // Normal function call - Stmt::FnCall(x, pos) => { - let FnCallExpr { - name, - capture_parent_scope: capture, - hashes, - args, - constants, - .. - } = x.as_ref(); - self.make_function_call( - scope, mods, state, lib, this_ptr, name, args, constants, *hashes, *pos, - *capture, level, - ) + Err(ERR::LoopBreak(options.contains(AST_OPTION_BREAK_OUT), *pos).into()) } // Try/Catch statement @@ -3059,9 +3137,7 @@ impl Engine { let (try_stmt, err_var, catch_stmt) = x.as_ref(); let result = self - .eval_stmt_block( - scope, mods, state, lib, this_ptr, try_stmt, true, true, level, - ) + .eval_stmt_block(scope, global, state, lib, this_ptr, try_stmt, true, level) .map(|_| Dynamic::UNIT); match result { @@ -3070,7 +3146,7 @@ impl Engine { Err(err) if !err.is_catchable() => Err(err), Err(mut err) => { let err_value = match *err { - EvalAltResult::ErrorRuntime(ref x, _) => x.clone(), + ERR::ErrorRuntime(ref x, _) => x.clone(), #[cfg(feature = "no_object")] _ => { @@ -3084,18 +3160,18 @@ impl Engine { err_map.insert("message".into(), err.to_string().into()); - if let Some(ref source) = mods.source { - err_map.insert("source".into(), source.as_str().into()); + if !global.source.is_empty() { + err_map.insert("source".into(), global.source.clone().into()); } if err_pos.is_none() { // No position info } else { - let line = err_pos.line().expect("line number") as INT; + let line = err_pos.line().unwrap() as INT; let position = if err_pos.is_beginning_of_line() { 0 } else { - err_pos.position().expect("character position") + err_pos.position().unwrap() } as INT; err_map.insert("line".into(), line.into()); err_map.insert("position".into(), position.into()); @@ -3113,7 +3189,7 @@ impl Engine { }); let result = self.eval_stmt_block( - scope, mods, state, lib, this_ptr, catch_stmt, true, true, level, + scope, global, state, lib, this_ptr, catch_stmt, true, level, ); scope.rewind(orig_scope_len); @@ -3122,7 +3198,7 @@ impl Engine { Ok(_) => Ok(Dynamic::UNIT), Err(result_err) => match *result_err { // Re-throw exception - EvalAltResult::ErrorRuntime(Dynamic(Union::Unit(_, _, _)), pos) => { + ERR::ErrorRuntime(Dynamic(Union::Unit(_, _, _)), pos) => { err.set_position(pos); Err(err) } @@ -3135,8 +3211,8 @@ impl Engine { // Throw value Stmt::Return(options, Some(expr), pos) if options.contains(AST_OPTION_BREAK_OUT) => { - Err(EvalAltResult::ErrorRuntime( - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + Err(ERR::ErrorRuntime( + self.eval_expr(scope, global, state, lib, this_ptr, expr, level)? .flatten(), *pos, ) @@ -3145,19 +3221,19 @@ impl Engine { // Empty throw Stmt::Return(options, None, pos) if options.contains(AST_OPTION_BREAK_OUT) => { - Err(EvalAltResult::ErrorRuntime(Dynamic::UNIT, *pos).into()) + Err(ERR::ErrorRuntime(Dynamic::UNIT, *pos).into()) } // Return value - Stmt::Return(_, Some(expr), pos) => Err(EvalAltResult::Return( - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + Stmt::Return(_, Some(expr), pos) => Err(ERR::Return( + self.eval_expr(scope, global, state, lib, this_ptr, expr, level)? .flatten(), *pos, ) .into()), // Empty return - Stmt::Return(_, None, pos) => Err(EvalAltResult::Return(Dynamic::UNIT, *pos).into()), + Stmt::Return(_, None, pos) => Err(ERR::Return(Dynamic::UNIT, *pos).into()), // Let/const statement Stmt::Var(expr, x, options, _) => { @@ -3170,14 +3246,18 @@ impl Engine { let export = options.contains(AST_OPTION_PUBLIC); let value = self - .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + .eval_expr(scope, global, state, lib, this_ptr, expr, level)? .flatten(); - let (var_name, _alias): (Cow<'_, str>, _) = if state.is_global() { + let (var_name, _alias): (Cow<'_, str>, _) = if !rewind_scope { #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_module"))] - if entry_type == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) { - mods.set_global_constant(name.clone(), value.clone()); + if state.scope_level == 0 + && entry_type == AccessMode::ReadOnly + && lib.iter().any(|&m| !m.is_empty()) + { + // Add a global constant if at top level and there are functions + global.set_constant(name.clone(), value.clone()); } ( @@ -3203,28 +3283,27 @@ impl Engine { Stmt::Import(expr, export, _pos) => { // Guard against too many modules #[cfg(not(feature = "unchecked"))] - if mods.num_modules >= self.max_modules() { - return Err(EvalAltResult::ErrorTooManyModules(*_pos).into()); + if global.num_modules_loaded >= self.max_modules() { + return Err(ERR::ErrorTooManyModules(*_pos).into()); } if let Some(path) = self - .eval_expr(scope, mods, state, lib, this_ptr, &expr, level)? + .eval_expr(scope, global, state, lib, this_ptr, &expr, level)? .try_cast::() { use crate::ModuleResolver; - let source = mods.source.as_ref().map(|s| s.as_str()); + let source = match global.source.as_str() { + "" => None, + s => Some(s), + }; let path_pos = expr.position(); - let module = mods + let module = global .embedded_module_resolver .as_ref() .and_then(|r| match r.resolve(self, source, &path, path_pos) { - Err(err) - if matches!(*err, EvalAltResult::ErrorModuleNotFound(_, _)) => - { - None - } + Err(err) if matches!(*err, ERR::ErrorModuleNotFound(_, _)) => None, result => Some(result), }) .or_else(|| { @@ -3233,10 +3312,7 @@ impl Engine { .map(|r| r.resolve(self, source, &path, path_pos)) }) .unwrap_or_else(|| { - Err( - EvalAltResult::ErrorModuleNotFound(path.to_string(), path_pos) - .into(), - ) + Err(ERR::ErrorModuleNotFound(path.to_string(), path_pos).into()) })?; if let Some(name) = export.as_ref().map(|x| x.name.clone()) { @@ -3244,13 +3320,13 @@ impl Engine { // Index the module (making a clone copy if necessary) if it is not indexed let mut module = crate::func::native::shared_take_or_clone(module); module.build_index(); - mods.push(name, module); + global.push_module(name, module); } else { - mods.push(name, module); + global.push_module(name, module); } } - mods.num_modules += 1; + global.num_modules_loaded += 1; Ok(Dynamic::UNIT) } else { @@ -3269,9 +3345,9 @@ impl Engine { index, if rename.is_empty() { name } else { rename }.clone(), ); - Ok(()) as Result<_, Box> + Ok(()) as RhaiResultOf<_> } else { - Err(EvalAltResult::ErrorVariableNotFound(name.to_string(), *pos).into()) + Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into()) } }, )?; @@ -3291,126 +3367,130 @@ impl Engine { } Ok(Dynamic::UNIT) } - }; - self.check_return_value(result) - .map_err(|err| err.fill_position(stmt.position())) - } - - // Has a system function a Rust-native override? - pub(crate) fn has_native_fn_override(&self, hash_script: u64, arg_types: &[TypeId]) -> bool { - let hash_params = calc_fn_params_hash(arg_types.iter().cloned()); - let hash = combine_hashes(hash_script, hash_params); - - // First check the global namespace and packages, but skip modules that are standard because - // they should never conflict with system functions. - self.global_modules.iter().filter(|m| !m.standard).any(|m| m.contains_fn(hash)) - // Then check sub-modules - || self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash)) + _ => unreachable!("statement cannot be evaluated: {:?}", stmt), + } } /// Check a result to ensure that the data size is within allowable limit. - fn check_return_value(&self, mut result: RhaiResult) -> RhaiResult { - if let Ok(ref mut r) = result { - // Concentrate all empty strings into one instance to save memory - if let Dynamic(crate::types::dynamic::Union::Str(s, _, _)) = r { - if s.is_empty() { - if !s.ptr_eq(&self.empty_string) { - *s = self.const_empty_string(); - } - return result; - } - } + pub(crate) fn check_return_value(&self, mut result: RhaiResult, pos: Position) -> RhaiResult { + let _pos = pos; - #[cfg(not(feature = "unchecked"))] - self.check_data_size(&r)?; + match result { + Ok(ref mut r) => { + // Concentrate all empty strings into one instance to save memory + if let Dynamic(Union::Str(s, _, _)) = r { + if s.is_empty() { + if !s.ptr_eq(&self.empty_string) { + *s = self.const_empty_string(); + } + return result; + } + } + + #[cfg(not(feature = "unchecked"))] + self.check_data_size(&r, _pos)?; + } + _ => (), } result } - #[cfg(feature = "unchecked")] - #[inline(always)] - fn check_data_size(&self, _value: &Dynamic) -> Result<(), Box> { - Ok(()) + /// Recursively calculate the sizes of a value. + /// + /// Sizes returned are `(`[`Array`][crate::Array], [`Map`][crate::Map] and `String)`. + /// + /// # Panics + /// + /// Panics if any interior data is shared (should never happen). + #[cfg(not(feature = "unchecked"))] + fn calc_data_sizes(value: &Dynamic, top: bool) -> (usize, usize, usize) { + match value.0 { + #[cfg(not(feature = "no_index"))] + Union::Array(ref arr, _, _) => { + arr.iter() + .fold((0, 0, 0), |(arrays, maps, strings), value| match value.0 { + Union::Array(_, _, _) => { + let (a, m, s) = Self::calc_data_sizes(value, false); + (arrays + a + 1, maps + m, strings + s) + } + Union::Blob(ref a, _, _) => (arrays + 1 + a.len(), maps, strings), + #[cfg(not(feature = "no_object"))] + Union::Map(_, _, _) => { + let (a, m, s) = Self::calc_data_sizes(value, false); + (arrays + a + 1, maps + m, strings + s) + } + Union::Str(ref s, _, _) => (arrays + 1, maps, strings + s.len()), + _ => (arrays + 1, maps, strings), + }) + } + #[cfg(not(feature = "no_index"))] + Union::Blob(ref arr, _, _) => (arr.len(), 0, 0), + #[cfg(not(feature = "no_object"))] + Union::Map(ref map, _, _) => { + map.values() + .fold((0, 0, 0), |(arrays, maps, strings), value| match value.0 { + #[cfg(not(feature = "no_index"))] + Union::Array(_, _, _) => { + let (a, m, s) = Self::calc_data_sizes(value, false); + (arrays + a, maps + m + 1, strings + s) + } + #[cfg(not(feature = "no_index"))] + Union::Blob(ref a, _, _) => (arrays + a.len(), maps, strings), + Union::Map(_, _, _) => { + let (a, m, s) = Self::calc_data_sizes(value, false); + (arrays + a, maps + m + 1, strings + s) + } + Union::Str(ref s, _, _) => (arrays, maps + 1, strings + s.len()), + _ => (arrays, maps + 1, strings), + }) + } + Union::Str(ref s, _, _) => (0, 0, s.len()), + #[cfg(not(feature = "no_closure"))] + Union::Shared(_, _, _) if top => { + Self::calc_data_sizes(&*value.read_lock::().unwrap(), true) + } + #[cfg(not(feature = "no_closure"))] + Union::Shared(_, _, _) => { + unreachable!("shared values discovered within data: {}", value) + } + _ => (0, 0, 0), + } } + /// Is there a data size limit set? #[cfg(not(feature = "unchecked"))] - fn check_data_size(&self, value: &Dynamic) -> Result<(), Box> { - // Recursively calculate the size of a value (especially `Array` and `Map`) - fn calc_size(value: &Dynamic) -> (usize, usize, usize) { - match value.0 { - #[cfg(not(feature = "no_index"))] - Union::Array(ref arr, _, _) => { - arr.iter() - .fold((0, 0, 0), |(arrays, maps, strings), value| match value.0 { - Union::Array(_, _, _) => { - let (a, m, s) = calc_size(value); - (arrays + a + 1, maps + m, strings + s) - } - Union::Blob(ref a, _, _) => (arrays + 1 + a.len(), maps, strings), - #[cfg(not(feature = "no_object"))] - Union::Map(_, _, _) => { - let (a, m, s) = calc_size(value); - (arrays + a + 1, maps + m, strings + s) - } - Union::Str(ref s, _, _) => (arrays + 1, maps, strings + s.len()), - _ => (arrays + 1, maps, strings), - }) - } - #[cfg(not(feature = "no_index"))] - Union::Blob(ref arr, _, _) => (arr.len(), 0, 0), - #[cfg(not(feature = "no_object"))] - Union::Map(ref map, _, _) => { - map.values() - .fold((0, 0, 0), |(arrays, maps, strings), value| match value.0 { - #[cfg(not(feature = "no_index"))] - Union::Array(_, _, _) => { - let (a, m, s) = calc_size(value); - (arrays + a, maps + m + 1, strings + s) - } - #[cfg(not(feature = "no_index"))] - Union::Blob(ref a, _, _) => (arrays + a.len(), maps, strings), - Union::Map(_, _, _) => { - let (a, m, s) = calc_size(value); - (arrays + a, maps + m + 1, strings + s) - } - Union::Str(ref s, _, _) => (arrays, maps + 1, strings + s.len()), - _ => (arrays, maps + 1, strings), - }) - } - Union::Str(ref s, _, _) => (0, 0, s.len()), - _ => (0, 0, 0), - } - } + fn has_data_size_limit(&self) -> bool { + let mut _limited = self.limits.max_string_size.is_some(); - // If no data size limits, just return - let mut _has_limit = self.limits.max_string_size.is_some(); #[cfg(not(feature = "no_index"))] { - _has_limit = _has_limit || self.limits.max_array_size.is_some(); + _limited = _limited || self.limits.max_array_size.is_some(); } #[cfg(not(feature = "no_object"))] { - _has_limit = _has_limit || self.limits.max_map_size.is_some(); + _limited = _limited || self.limits.max_map_size.is_some(); } - if !_has_limit { - return Ok(()); - } + _limited + } - let (_arr, _map, s) = calc_size(value); + /// Raise an error if any data size exceeds limit. + #[cfg(not(feature = "unchecked"))] + fn raise_err_if_over_data_size_limit( + &self, + sizes: (usize, usize, usize), + pos: Position, + ) -> RhaiResultOf<()> { + let (_arr, _map, s) = sizes; if s > self .limits .max_string_size .map_or(usize::MAX, NonZeroUsize::get) { - return Err(EvalAltResult::ErrorDataTooLarge( - "Length of string".to_string(), - Position::NONE, - ) - .into()); + return Err(ERR::ErrorDataTooLarge("Length of string".to_string(), pos).into()); } #[cfg(not(feature = "no_index"))] @@ -3420,11 +3500,7 @@ impl Engine { .max_array_size .map_or(usize::MAX, NonZeroUsize::get) { - return Err(EvalAltResult::ErrorDataTooLarge( - "Size of array".to_string(), - Position::NONE, - ) - .into()); + return Err(ERR::ErrorDataTooLarge("Size of array".to_string(), pos).into()); } #[cfg(not(feature = "no_object"))] @@ -3434,35 +3510,53 @@ impl Engine { .max_map_size .map_or(usize::MAX, NonZeroUsize::get) { - return Err(EvalAltResult::ErrorDataTooLarge( - "Size of object map".to_string(), - Position::NONE, - ) - .into()); + return Err(ERR::ErrorDataTooLarge("Size of object map".to_string(), pos).into()); } Ok(()) } + /// Check whether the size of a [`Dynamic`] is within limits. + #[cfg(not(feature = "unchecked"))] + pub(crate) fn check_data_size(&self, value: &Dynamic, pos: Position) -> RhaiResultOf<()> { + // If no data size limits, just return + if !self.has_data_size_limit() { + return Ok(()); + } + + let sizes = Self::calc_data_sizes(value, true); + + self.raise_err_if_over_data_size_limit(sizes, pos) + } + + /// Raise an error if the size of a [`Dynamic`] is out of limits (if any). + /// + /// Not available under `unchecked`. + #[cfg(not(feature = "unchecked"))] + #[inline(always)] + pub fn ensure_data_size_within_limits(&self, value: &Dynamic) -> RhaiResultOf<()> { + self.check_data_size(value, Position::NONE) + } + /// Check if the number of operations stay within limit. #[cfg(not(feature = "unchecked"))] pub(crate) fn inc_operations( &self, num_operations: &mut u64, pos: Position, - ) -> Result<(), Box> { + ) -> RhaiResultOf<()> { *num_operations += 1; // Guard against too many operations if self.max_operations() > 0 && *num_operations > self.max_operations() { - return Err(EvalAltResult::ErrorTooManyOperations(pos).into()); + return Err(ERR::ErrorTooManyOperations(pos).into()); } // Report progress - only in steps if let Some(ref progress) = self.progress { if let Some(token) = progress(*num_operations) { // Terminate script if progress returns a termination token - return Err(EvalAltResult::ErrorTerminated(token, pos).into()); + return Err(ERR::ErrorTerminated(token, pos).into()); } } @@ -3482,15 +3576,11 @@ impl Engine { .unwrap_or_else(|| map_std_type_name(name)) } - /// Make a `Box<`[`EvalAltResult`][EvalAltResult::ErrorMismatchDataType]`>`. + /// Make a `Box<`[`EvalAltResult`][ERR::ErrorMismatchDataType]`>`. #[inline] #[must_use] - pub(crate) fn make_type_mismatch_err(&self, typ: &str, pos: Position) -> Box { - EvalAltResult::ErrorMismatchDataType( - self.map_type_name(type_name::()).into(), - typ.into(), - pos, - ) - .into() + pub(crate) fn make_type_mismatch_err(&self, typ: &str, pos: Position) -> RhaiError { + ERR::ErrorMismatchDataType(self.map_type_name(type_name::()).into(), typ.into(), pos) + .into() } } diff --git a/src/func/args.rs b/src/func/args.rs index e2e21f6a..d612ad3f 100644 --- a/src/func/args.rs +++ b/src/func/args.rs @@ -9,7 +9,7 @@ use std::prelude::v1::*; /// Trait that parses arguments to a function call. /// -/// Any data type can implement this trait in order to pass arguments to a function call. +/// Any data type can implement this trait in order to pass arguments to [`Engine::call_fn`][crate::Engine::call_fn]. pub trait FuncArgs { /// Parse function call arguments into a container. /// @@ -26,10 +26,10 @@ pub trait FuncArgs { /// } /// /// impl FuncArgs for Options { - /// fn parse>(self, container: &mut CONTAINER) { - /// container.extend(std::iter::once(self.foo.into())); - /// container.extend(std::iter::once(self.bar.into())); - /// container.extend(std::iter::once(self.baz.into())); + /// fn parse>(self, args: &mut ARGS) { + /// args.extend(std::iter::once(self.foo.into())); + /// args.extend(std::iter::once(self.bar.into())); + /// args.extend(std::iter::once(self.baz.into())); /// } /// } /// @@ -55,13 +55,13 @@ pub trait FuncArgs { /// # Ok(()) /// # } /// ``` - fn parse>(self, container: &mut CONTAINER); + fn parse>(self, args: &mut ARGS); } impl FuncArgs for Vec { #[inline] - fn parse>(self, container: &mut CONTAINER) { - container.extend(self.into_iter().map(Variant::into_dynamic)); + fn parse>(self, args: &mut ARGS) { + args.extend(self.into_iter().map(Dynamic::from)); } } @@ -73,9 +73,9 @@ macro_rules! impl_args { { #[inline] #[allow(unused_variables)] - fn parse>(self, container: &mut CONTAINER) { + fn parse>(self, args: &mut ARGS) { let ($($p,)*) = self; - $(container.extend(Some($p.into_dynamic()));)* + $(args.extend(Some(Dynamic::from($p)));)* } } diff --git a/src/func/builtin.rs b/src/func/builtin.rs index e2bb1c74..a84b437c 100644 --- a/src/func/builtin.rs +++ b/src/func/builtin.rs @@ -1,8 +1,9 @@ //! Built-in implementations for common operators. use super::call::FnCallArgs; +use super::native::FnBuiltin; use crate::engine::OP_CONTAINS; -use crate::{Dynamic, ImmutableString, NativeCallContext, RhaiResult, INT}; +use crate::{Dynamic, ExclusiveRange, ImmutableString, InclusiveRange, INT}; use std::any::TypeId; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -24,7 +25,12 @@ const BUILTIN: &str = "data type was checked"; #[inline] #[must_use] fn is_numeric(type_id: TypeId) -> bool { - let result = type_id == TypeId::of::() + let result = false; + + #[cfg(not(feature = "only_i64"))] + #[cfg(not(feature = "only_i32"))] + let result = result + || type_id == TypeId::of::() || type_id == TypeId::of::() || type_id == TypeId::of::() || type_id == TypeId::of::() @@ -33,7 +39,10 @@ fn is_numeric(type_id: TypeId) -> bool { || type_id == TypeId::of::() || type_id == TypeId::of::(); - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + #[cfg(not(feature = "only_i64"))] + #[cfg(not(feature = "only_i32"))] + #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_arch = "wasm64"))] let result = result || type_id == TypeId::of::() || type_id == TypeId::of::(); #[cfg(not(feature = "no_float"))] @@ -46,33 +55,13 @@ fn is_numeric(type_id: TypeId) -> bool { } /// Build in common binary operator implementations to avoid the cost of calling a registered function. +/// +/// 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: &str, - x: &Dynamic, - y: &Dynamic, -) -> Option RhaiResult> { +pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option { let type1 = x.type_id(); let type2 = y.type_id(); - // One of the operands is a custom type, so it is never built-in - if x.is_variant() || y.is_variant() { - return if is_numeric(type1) && is_numeric(type2) { - // Disallow comparisons between different numeric types - None - } else if type1 != type2 { - // If the types are not the same, default to not compare - match op { - "!=" => Some(|_, _| Ok(Dynamic::TRUE)), - "==" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), - _ => None, - } - } else { - // Disallow comparisons between the same type - None - }; - } - let types_pair = (type1, type2); macro_rules! impl_op { @@ -108,7 +97,7 @@ pub fn get_builtin_binary_op_fn( ($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; - $func(x, y).map(Into::::into) + $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)); @@ -123,7 +112,7 @@ pub fn get_builtin_binary_op_fn( (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)); - $func(x, y).map(Into::::into) + $func(x, y).map(Into::into) } }; } @@ -266,7 +255,7 @@ pub fn get_builtin_binary_op_fn( OP_CONTAINS => Some(|_, args| { let s = &*args[0].read_lock::().expect(BUILTIN); let c = args[1].as_char().expect(BUILTIN); - Ok((s.contains(c)).into()) + Ok(s.contains(c).into()) }), _ => None, }; @@ -290,6 +279,30 @@ pub fn get_builtin_binary_op_fn( }; } + // blob + #[cfg(not(feature = "no_index"))] + if type1 == TypeId::of::() { + use crate::Blob; + + if type2 == TypeId::of::() { + return match op { + OP_CONTAINS => Some(|_, args| { + let blob = &*args[0].read_lock::().expect(BUILTIN); + let x = (args[1].as_int().expect("`INT`") & 0x000000ff) as u8; + Ok(blob.contains(&x).into()) + }), + _ => None, + }; + } + if type1 == type2 { + return match op { + "==" => Some(impl_op!(Blob == Blob)), + "!=" => Some(impl_op!(Blob != Blob)), + _ => None, + }; + } + } + // map op string #[cfg(not(feature = "no_object"))] if types_pair == (TypeId::of::(), TypeId::of::()) { @@ -301,6 +314,84 @@ pub fn get_builtin_binary_op_fn( }; } + // Non-compatible ranges + if types_pair + == ( + TypeId::of::(), + TypeId::of::(), + ) + || types_pair + == ( + TypeId::of::(), + TypeId::of::(), + ) + { + return match op { + "!=" => Some(|_, _| Ok(Dynamic::TRUE)), + "==" => Some(|_, _| Ok(Dynamic::FALSE)), + _ => None, + }; + } + + // Handle ranges here because ranges are implemented as custom type + if type1 == TypeId::of::() { + if type2 == TypeId::of::() { + return match op { + OP_CONTAINS => Some(|_, args| { + let range = &*args[0].read_lock::().expect(BUILTIN); + let x = args[1].as_int().expect("`INT`"); + Ok(range.contains(&x).into()) + }), + _ => None, + }; + } + if type1 == type2 { + return match op { + "==" => Some(impl_op!(ExclusiveRange == ExclusiveRange)), + "!=" => Some(impl_op!(ExclusiveRange != ExclusiveRange)), + _ => None, + }; + } + } + + if type1 == TypeId::of::() { + if type2 == TypeId::of::() { + return match op { + OP_CONTAINS => Some(|_, args| { + let range = &*args[0].read_lock::().expect(BUILTIN); + let x = args[1].as_int().expect("`INT`"); + Ok(range.contains(&x).into()) + }), + _ => None, + }; + } + if type1 == type2 { + return match op { + "==" => Some(impl_op!(InclusiveRange == InclusiveRange)), + "!=" => Some(impl_op!(InclusiveRange != InclusiveRange)), + _ => None, + }; + } + } + + // One of the operands is a custom type, so it is never built-in + if x.is_variant() || y.is_variant() { + return if is_numeric(type1) && is_numeric(type2) { + // Disallow comparisons between different numeric types + None + } else if type1 != type2 { + // If the types are not the same, default to not compare + match op { + "!=" => Some(|_, _| Ok(Dynamic::TRUE)), + "==" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), + _ => None, + } + } else { + // Disallow comparisons between the same type + None + }; + } + // Default comparison operators for different types if type2 != type1 { return match op { @@ -391,11 +482,7 @@ pub fn get_builtin_binary_op_fn( ">=" => Some(impl_op!(ImmutableString >= ImmutableString)), "<" => Some(impl_op!(ImmutableString < ImmutableString)), "<=" => Some(impl_op!(ImmutableString <= ImmutableString)), - OP_CONTAINS => Some(|_, args| { - let s1 = &*args[0].read_lock::().expect(BUILTIN); - let s2 = &*args[1].read_lock::().expect(BUILTIN); - Ok((s1.contains(s2.as_str())).into()) - }), + OP_CONTAINS => Some(impl_op!(ImmutableString.contains(ImmutableString.as_str()))), _ => None, }; } @@ -429,12 +516,10 @@ pub fn get_builtin_binary_op_fn( } /// Build in common operator assignment implementations to avoid the cost of calling a registered function. +/// +/// 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: &str, - x: &Dynamic, - y: &Dynamic, -) -> Option RhaiResult> { +pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option { let type1 = x.type_id(); let type2 = y.type_id(); @@ -571,6 +656,48 @@ pub fn get_builtin_op_assignment_fn( }; } + // array op= any + #[cfg(not(feature = "no_index"))] + if type1 == TypeId::of::() { + use crate::packages::array_basic::array_functions::*; + use crate::Array; + + if type2 == TypeId::of::() { + return match op { + "+=" => Some(|_, args| { + let array2 = std::mem::take(args[1]).cast::(); + let array1 = &mut *args[0].write_lock::().expect(BUILTIN); + Ok(append(array1, array2).into()) + }), + _ => None, + }; + } else { + return match op { + "+=" => Some(|_, args| { + let x = std::mem::take(args[1]); + let array = &mut *args[0].write_lock::().expect(BUILTIN); + Ok(push(array, x).into()) + }), + _ => None, + }; + } + } + + // blob op= int + #[cfg(not(feature = "no_index"))] + if types_pair == (TypeId::of::(), TypeId::of::()) { + use crate::Blob; + + return match op { + "+=" => Some(|_, args| { + let x = (args[1].as_int().expect("`INT`") & 0x000000ff) as u8; + let mut blob = args[0].write_lock::().expect(BUILTIN); + Ok(blob.push(x).into()) + }), + _ => None, + }; + } + // No built-in op-assignments for different types. if type2 != type1 { return None; @@ -652,5 +779,20 @@ pub fn get_builtin_op_assignment_fn( }; } + #[cfg(not(feature = "no_index"))] + if type1 == TypeId::of::() { + use crate::packages::blob_basic::blob_functions::*; + use crate::Blob; + + return match op { + "+=" => Some(|_, args| { + let blob2 = std::mem::take(args[1]).cast::(); + let blob1 = &mut *args[0].write_lock::().expect(BUILTIN); + Ok(append(blob1, blob2).into()) + }), + _ => None, + }; + } + None } diff --git a/src/func/call.rs b/src/func/call.rs index 7db44596..2af8dc00 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -1,24 +1,25 @@ //! Implement function-calling mechanism for [`Engine`]. -use super::native::{CallableFunction, FnAny}; +use super::callable_function::CallableFunction; +use super::native::FnAny; use super::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; -use crate::ast::FnCallHashes; +use crate::api::default_limits::MAX_DYNAMIC_PARAMETERS; +use crate::ast::{Expr, FnCallHashes, Stmt}; use crate::engine::{ - EvalState, FnResolutionCacheEntry, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, + EvalState, GlobalRuntimeState, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, - MAX_DYNAMIC_PARAMETERS, }; -use crate::module::NamespaceRef; +use crate::module::Namespace; use crate::tokenizer::Token; use crate::{ - ast::{Expr, Stmt}, - calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, EvalAltResult, FnPtr, - Identifier, ImmutableString, Module, Position, RhaiResult, Scope, StaticVec, + calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnArgsVec, FnPtr, + Identifier, ImmutableString, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ any::{type_name, TypeId}, + collections::BTreeMap, convert::TryFrom, mem, }; @@ -104,18 +105,18 @@ impl Drop for ArgBackup<'_> { #[cfg(not(feature = "no_closure"))] #[inline] pub fn ensure_no_data_race( - fn_name: impl AsRef, + fn_name: &str, args: &FnCallArgs, is_method_call: bool, -) -> Result<(), Box> { +) -> RhaiResultOf<()> { if let Some((n, _)) = args .iter() .enumerate() .skip(if is_method_call { 1 } else { 0 }) .find(|(_, a)| a.is_locked()) { - return Err(EvalAltResult::ErrorDataRace( - format!("argument #{} of function '{}'", n + 1, fn_name.as_ref()), + return Err(ERR::ErrorDataRace( + format!("argument #{} of function '{}'", n + 1, fn_name), Position::NONE, ) .into()); @@ -124,32 +125,50 @@ pub fn ensure_no_data_race( Ok(()) } +/// _(internals)_ An entry in a function resolution cache. +/// Exported under the `internals` feature only. +#[derive(Debug, Clone)] +pub struct FnResolutionCacheEntry { + /// Function. + pub func: CallableFunction, + /// Optional source. + /// No source if the string is empty. + pub source: Identifier, +} + +/// _(internals)_ A function resolution cache. +/// Exported under the `internals` feature only. +/// +/// [`FnResolutionCacheEntry`] is [`Box`]ed in order to pack as many entries inside a single B-Tree +/// level as possible. +pub type FnResolutionCache = BTreeMap>>; + impl Engine { /// Generate the signature for a function call. #[inline] #[must_use] fn gen_call_signature( &self, - namespace: Option<&NamespaceRef>, - fn_name: impl AsRef, + namespace: Option<&Namespace>, + fn_name: &str, args: &[&mut Dynamic], ) -> String { format!( "{}{}{} ({})", - namespace.map_or(String::new(), |ns| ns.to_string()), + namespace.map_or_else(|| String::new(), |ns| ns.to_string()), if namespace.is_some() { Token::DoubleColon.literal_syntax() } else { "" }, - fn_name.as_ref(), + fn_name, args.iter() .map(|a| if a.is::() { "&str | ImmutableString | String" } else { self.map_type_name(a.type_name()) }) - .collect::>() + .collect::>() .join(", ") ) } @@ -165,10 +184,10 @@ impl Engine { #[must_use] fn resolve_fn<'s>( &self, - mods: &Imports, + global: &GlobalRuntimeState, state: &'s mut EvalState, lib: &[&Module], - fn_name: impl AsRef, + fn_name: &str, hash_script: u64, args: Option<&mut FnCallArgs>, allow_dynamic: bool, @@ -178,8 +197,6 @@ impl Engine { return None; } - let fn_name = fn_name.as_ref(); - let mut hash = args.as_ref().map_or(hash_script, |args| { combine_hashes( hash_script, @@ -195,7 +212,7 @@ impl Engine { let max_bitmask = if !allow_dynamic { 0 } else { - 1usize << num_args.min(MAX_DYNAMIC_PARAMETERS) + 1usize << usize::min(num_args, MAX_DYNAMIC_PARAMETERS) }; let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic` @@ -205,22 +222,24 @@ impl Engine { .find_map(|m| { m.get_fn(hash).cloned().map(|func| FnResolutionCacheEntry { func, - source: m.id_raw().cloned(), + source: m.id_raw().clone(), }) }) .or_else(|| { self.global_modules.iter().find_map(|m| { m.get_fn(hash).cloned().map(|func| FnResolutionCacheEntry { func, - source: m.id_raw().cloned(), + source: m.id_raw().clone(), }) }) }) .or_else(|| { - mods.get_fn(hash) + global + .get_fn(hash) .map(|(func, source)| FnResolutionCacheEntry { func: func.clone(), - source: source.cloned(), + source: source + .map_or_else(|| Identifier::new_const(), Into::into), }) }) .or_else(|| { @@ -228,7 +247,7 @@ impl Engine { m.get_qualified_fn(hash).cloned().map(|func| { FnResolutionCacheEntry { func, - source: m.id_raw().cloned(), + source: m.id_raw().clone(), } }) }) @@ -251,19 +270,18 @@ impl Engine { func: CallableFunction::from_method( Box::new(f) as Box ), - source: None, + source: Identifier::new_const(), } }) } else { - let (first_arg, rest_args) = - args.split_first().expect("two arguments"); + let (first_arg, rest_args) = args.split_first().unwrap(); get_builtin_op_assignment_fn(fn_name, *first_arg, rest_args[0]) .map(|f| FnResolutionCacheEntry { func: CallableFunction::from_method( Box::new(f) as Box ), - source: None, + source: Identifier::new_const(), }) } .map(Box::new) @@ -308,24 +326,32 @@ impl Engine { /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! pub(crate) fn call_native_fn( &self, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], - name: impl AsRef, + name: &str, hash: u64, args: &mut FnCallArgs, is_ref_mut: bool, is_op_assign: bool, pos: Position, - ) -> Result<(Dynamic, bool), Box> { + ) -> RhaiResultOf<(Dynamic, bool)> { #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut mods.num_operations, pos)?; + self.inc_operations(&mut global.num_operations, pos)?; - let name = name.as_ref(); - let parent_source = mods.source.clone(); + let parent_source = global.source.clone(); // Check if function access already in the cache - let func = self.resolve_fn(mods, state, lib, name, hash, Some(args), true, is_op_assign); + let func = self.resolve_fn( + global, + state, + lib, + name, + hash, + Some(args), + true, + is_op_assign, + ); if let Some(FnResolutionCacheEntry { func, source }) = func { assert!(func.is_native()); @@ -342,12 +368,12 @@ impl Engine { } // Run external function - let source = source - .as_ref() - .or_else(|| parent_source.as_ref()) - .map(|s| s.as_str()); + let source = match (source.as_str(), parent_source.as_str()) { + ("", "") => None, + ("", s) | (s, _) => Some(s), + }; - let context = (self, name, source, &*mods, lib, pos).into(); + let context = (self, name, source, &*global, lib, pos).into(); let result = if func.is_plugin_fn() { func.get_plugin_fn() @@ -362,14 +388,21 @@ impl Engine { bk.restore_first_arg(args) } - let result = result.map_err(|err| err.fill_position(pos))?; + // Check the return value (including data sizes) + let result = self.check_return_value(result, pos)?; + + // Check the data size of any `&mut` object, which may be changed. + #[cfg(not(feature = "unchecked"))] + if is_ref_mut && args.len() > 0 { + self.check_data_size(&args[0], pos)?; + } // See if the function match print/debug (which requires special processing) return Ok(match name { KEYWORD_PRINT => { if let Some(ref print) = self.print { let text = result.into_immutable_string().map_err(|typ| { - EvalAltResult::ErrorMismatchOutputType( + ERR::ErrorMismatchOutputType( self.map_type_name(type_name::()).into(), typ.into(), pos, @@ -383,13 +416,16 @@ impl Engine { KEYWORD_DEBUG => { if let Some(ref debug) = self.debug { let text = result.into_immutable_string().map_err(|typ| { - EvalAltResult::ErrorMismatchOutputType( + ERR::ErrorMismatchOutputType( self.map_type_name(type_name::()).into(), typ.into(), pos, ) })?; - let source = mods.source.as_ref().map(|s| s.as_str()); + let source = match global.source.as_str() { + "" => None, + s => Some(s), + }; (debug(&text, source, pos).into(), false) } else { (Dynamic::UNIT, false) @@ -407,7 +443,7 @@ impl Engine { crate::engine::FN_IDX_GET => { assert!(args.len() == 2); - Err(EvalAltResult::ErrorIndexingType( + Err(ERR::ErrorIndexingType( format!( "{} [{}]", self.map_type_name(args[0].type_name()), @@ -423,7 +459,7 @@ impl Engine { crate::engine::FN_IDX_SET => { assert!(args.len() == 3); - Err(EvalAltResult::ErrorIndexingType( + Err(ERR::ErrorIndexingType( format!( "{} [{}] = {}", self.map_type_name(args[0].type_name()), @@ -440,7 +476,7 @@ impl Engine { _ if name.starts_with(crate::engine::FN_GET) => { assert!(args.len() == 1); - Err(EvalAltResult::ErrorDotExpr( + Err(ERR::ErrorDotExpr( format!( "Unknown property '{}' - a getter is not registered for type '{}'", &name[crate::engine::FN_GET.len()..], @@ -456,7 +492,7 @@ impl Engine { _ if name.starts_with(crate::engine::FN_SET) => { assert!(args.len() == 2); - Err(EvalAltResult::ErrorDotExpr( + Err(ERR::ErrorDotExpr( format!( "No writable property '{}' - a setter is not registered for type '{}' to handle '{}'", &name[crate::engine::FN_SET.len()..], @@ -469,11 +505,9 @@ impl Engine { } // Raise error - _ => Err(EvalAltResult::ErrorFunctionNotFound( - self.gen_call_signature(None, name, args), - pos, - ) - .into()), + _ => Err( + ERR::ErrorFunctionNotFound(self.gen_call_signature(None, name, args), pos).into(), + ), } } @@ -487,10 +521,10 @@ impl Engine { /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! pub(crate) fn exec_fn_call( &self, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], - fn_name: impl AsRef, + fn_name: &str, hashes: FnCallHashes, args: &mut FnCallArgs, is_ref_mut: bool, @@ -498,14 +532,12 @@ impl Engine { pos: Position, scope: Option<&mut Scope>, level: usize, - ) -> Result<(Dynamic, bool), Box> { - fn no_method_err(name: &str, pos: Position) -> Result<(Dynamic, bool), Box> { + ) -> RhaiResultOf<(Dynamic, bool)> { + fn no_method_err(name: &str, pos: Position) -> RhaiResultOf<(Dynamic, bool)> { let msg = format!("'{0}' should not be called this way. Try {0}(...);", name); - Err(EvalAltResult::ErrorRuntime(msg.into(), pos).into()) + Err(ERR::ErrorRuntime(msg.into(), pos).into()) } - let fn_name = fn_name.as_ref(); - // Check for data race. #[cfg(not(feature = "no_closure"))] ensure_no_data_race(fn_name, args, is_ref_mut)?; @@ -537,7 +569,7 @@ impl Engine { false } else { let hash_script = calc_fn_hash(fn_name.as_str(), num_params as usize); - self.has_script_fn(Some(mods), state, lib, hash_script) + self.has_script_fn(Some(global), state, lib, hash_script) } .into(), false, @@ -561,16 +593,25 @@ impl Engine { _ => (), } - // Scripted function call? + // Script-defined function call? #[cfg(not(feature = "no_function"))] - if let Some(FnResolutionCacheEntry { func, source }) = self - .resolve_fn(mods, state, lib, fn_name, hashes.script, None, false, false) + if let Some(FnResolutionCacheEntry { func, mut source }) = self + .resolve_fn( + global, + state, + lib, + fn_name, + hashes.script, + None, + false, + false, + ) .cloned() { // Script function call assert!(func.is_script()); - let func = func.get_script_fn_def().expect("scripted function"); + let func = func.get_script_fn_def().expect("script-defined function"); if func.body.is_empty() { return Ok((Dynamic::UNIT, false)); @@ -585,18 +626,17 @@ impl Engine { } }; + mem::swap(&mut global.source, &mut source); + let result = if _is_method_call { // Method call of script function - map first argument to `this` - let (first_arg, rest_args) = args.split_first_mut().expect("not empty"); - - let orig_source = mods.source.take(); - mods.source = source; + let (first_arg, rest_args) = args.split_first_mut().unwrap(); let level = _level + 1; let result = self.call_script_fn( scope, - mods, + global, state, lib, &mut Some(*first_arg), @@ -607,9 +647,6 @@ impl Engine { level, ); - // Restore the original source - mods.source = orig_source; - result? } else { // Normal call of script function @@ -623,18 +660,12 @@ impl Engine { .change_first_arg_to_copy(args); } - let orig_source = mods.source.take(); - mods.source = source; - let level = _level + 1; let result = self.call_script_fn( - scope, mods, state, lib, &mut None, func, args, pos, true, level, + scope, global, state, lib, &mut None, func, args, pos, true, level, ); - // Restore the original source - mods.source = orig_source; - // Restore the original reference if let Some(bk) = backup { bk.restore_first_arg(args) @@ -643,13 +674,16 @@ impl Engine { result? }; + // Restore the original source + mem::swap(&mut global.source, &mut source); + return Ok((result, false)); } // Native function call let hash = hashes.native; self.call_native_fn( - mods, state, lib, fn_name, hash, args, is_ref_mut, false, pos, + global, state, lib, fn_name, hash, args, is_ref_mut, false, pos, ) } @@ -659,81 +693,38 @@ impl Engine { pub(crate) fn eval_global_statements( &self, scope: &mut Scope, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, statements: &[Stmt], lib: &[&Module], level: usize, ) -> RhaiResult { self.eval_stmt_block( - scope, mods, state, lib, &mut None, statements, false, false, level, + scope, global, state, lib, &mut None, statements, false, level, ) .or_else(|err| match *err { - EvalAltResult::Return(out, _) => Ok(out), - EvalAltResult::LoopBreak(_, _) => { + ERR::Return(out, _) => Ok(out), + ERR::LoopBreak(_, _) => { unreachable!("no outer loop scope to break out of") } _ => Err(err), }) } - /// Evaluate a text script in place - used primarily for 'eval'. - fn eval_script_expr_in_place( - &self, - scope: &mut Scope, - mods: &mut Imports, - lib: &[&Module], - script: impl AsRef, - _pos: Position, - level: usize, - ) -> RhaiResult { - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut mods.num_operations, _pos)?; - - let script = script.as_ref().trim(); - if script.is_empty() { - return Ok(Dynamic::UNIT); - } - - // Compile the script text - // No optimizations because we only run it once - let ast = self.compile_with_scope_and_optimization_level( - &Scope::new(), - &[script], - #[cfg(not(feature = "no_optimize"))] - crate::OptimizationLevel::None, - )?; - - // If new functions are defined within the eval string, it is an error - #[cfg(not(feature = "no_function"))] - if !ast.shared_lib().is_empty() { - return Err(crate::ParseErrorType::WrongFnDefinition.into()); - } - - let statements = ast.statements(); - if statements.is_empty() { - return Ok(Dynamic::UNIT); - } - - // Evaluate the AST - self.eval_global_statements(scope, mods, &mut EvalState::new(), statements, lib, level) - } - /// Call a dot method. #[cfg(not(feature = "no_object"))] pub(crate) fn make_method_call( &self, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], - fn_name: impl AsRef, + fn_name: &str, mut hash: FnCallHashes, target: &mut crate::engine::Target, - (call_args, call_arg_pos): &mut (StaticVec, Position), + (call_args, call_arg_pos): &mut (FnArgsVec, Position), pos: Position, level: usize, - ) -> Result<(Dynamic, bool), Box> { - let fn_name = fn_name.as_ref(); + ) -> RhaiResultOf<(Dynamic, bool)> { let is_ref_mut = target.is_ref(); let (result, updated) = match fn_name { @@ -746,15 +737,16 @@ impl Engine { // Recalculate hashes let new_hash = calc_fn_hash(fn_name, args_len).into(); // Arguments are passed as-is, adding the curried arguments - let mut curry = StaticVec::with_capacity(fn_ptr.num_curried()); + let mut curry = FnArgsVec::with_capacity(fn_ptr.num_curried()); curry.extend(fn_ptr.curry().iter().cloned()); - let mut args = StaticVec::with_capacity(curry.len() + call_args.len()); + let mut args = FnArgsVec::with_capacity(curry.len() + call_args.len()); args.extend(curry.iter_mut()); args.extend(call_args.iter_mut()); // Map it to name(args) in function-call style self.exec_fn_call( - mods, state, lib, fn_name, new_hash, &mut args, false, false, pos, None, level, + global, state, lib, fn_name, new_hash, &mut args, false, false, pos, None, + level, ) } KEYWORD_FN_PTR_CALL => { @@ -784,16 +776,16 @@ impl Engine { calc_fn_hash(fn_name, args_len + 1), ); // Replace the first argument with the object pointer, adding the curried arguments - let mut curry = StaticVec::with_capacity(fn_ptr.num_curried()); + let mut curry = FnArgsVec::with_capacity(fn_ptr.num_curried()); curry.extend(fn_ptr.curry().iter().cloned()); - let mut args = StaticVec::with_capacity(curry.len() + call_args.len() + 1); + let mut args = FnArgsVec::with_capacity(curry.len() + call_args.len() + 1); args.push(target.as_mut()); args.extend(curry.iter_mut()); args.extend(call_args.iter_mut()); // Map it to name(args) in function-call style self.exec_fn_call( - mods, state, lib, fn_name, new_hash, &mut args, is_ref_mut, true, pos, None, + global, state, lib, fn_name, new_hash, &mut args, is_ref_mut, true, pos, None, level, ) } @@ -860,12 +852,13 @@ impl Engine { }; // Attached object pointer in front of the arguments - let mut args = StaticVec::with_capacity(call_args.len() + 1); + let mut args = FnArgsVec::with_capacity(call_args.len() + 1); args.push(target.as_mut()); args.extend(call_args.iter_mut()); self.exec_fn_call( - mods, state, lib, fn_name, hash, &mut args, is_ref_mut, true, pos, None, level, + global, state, lib, fn_name, hash, &mut args, is_ref_mut, true, pos, None, + level, ) } }?; @@ -885,18 +878,18 @@ impl Engine { pub(crate) fn get_arg_value( &self, scope: &mut Scope, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, level: usize, arg_expr: &Expr, constants: &[Dynamic], - ) -> Result<(Dynamic, Position), Box> { + ) -> RhaiResultOf<(Dynamic, Position)> { match arg_expr { Expr::Stack(slot, pos) => Ok((constants[*slot].clone(), *pos)), ref arg => self - .eval_expr(scope, mods, state, lib, this_ptr, arg, level) + .eval_expr(scope, global, state, lib, this_ptr, arg, level) .map(|v| (v, arg.position())), } } @@ -905,11 +898,11 @@ impl Engine { pub(crate) fn make_function_call( &self, scope: &mut Scope, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, - fn_name: impl AsRef, + fn_name: &str, args_expr: &[Expr], constants: &[Dynamic], hashes: FnCallHashes, @@ -917,10 +910,9 @@ impl Engine { capture_scope: bool, level: usize, ) -> RhaiResult { - let fn_name = fn_name.as_ref(); let mut a_expr = args_expr; let mut total_args = a_expr.len(); - let mut curry = StaticVec::new_const(); + let mut curry = FnArgsVec::new_const(); let mut name = fn_name; let mut hashes = hashes; let redirected; // Handle call() - Redirect function call @@ -929,7 +921,7 @@ impl Engine { // Handle call() KEYWORD_FN_PTR_CALL if total_args >= 1 => { let (arg, arg_pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, &a_expr[0], constants, + scope, global, state, lib, this_ptr, level, &a_expr[0], constants, )?; if !arg.is::() { @@ -961,7 +953,7 @@ impl Engine { // Handle Fn() KEYWORD_FN_PTR if total_args == 1 => { let (arg, arg_pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, &a_expr[0], constants, + scope, global, state, lib, this_ptr, level, &a_expr[0], constants, )?; // Fn - only in function call style @@ -969,14 +961,14 @@ impl Engine { .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos)) .and_then(FnPtr::try_from) - .map(Into::::into) + .map(Into::into) .map_err(|err| err.fill_position(arg_pos)); } // Handle curry() KEYWORD_FN_PTR_CURRY if total_args > 1 => { let (arg, arg_pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, &a_expr[0], constants, + scope, global, state, lib, this_ptr, level, &a_expr[0], constants, )?; if !arg.is::() { @@ -991,9 +983,9 @@ impl Engine { // Append the new curried arguments to the existing list. let fn_curry = a_expr.iter().skip(1).try_fold( fn_curry, - |mut curried, expr| -> Result<_, Box> { + |mut curried, expr| -> RhaiResultOf<_> { let (value, _) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, expr, constants, + scope, global, state, lib, this_ptr, level, expr, constants, )?; curried.push(value); Ok(curried) @@ -1007,7 +999,7 @@ impl Engine { #[cfg(not(feature = "no_closure"))] crate::engine::KEYWORD_IS_SHARED if total_args == 1 => { let (arg, _) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, &a_expr[0], constants, + scope, global, state, lib, this_ptr, level, &a_expr[0], constants, )?; return Ok(arg.is_shared().into()); } @@ -1016,7 +1008,7 @@ impl Engine { #[cfg(not(feature = "no_function"))] crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => { let (arg, arg_pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, &a_expr[0], constants, + scope, global, state, lib, this_ptr, level, &a_expr[0], constants, )?; let fn_name = arg @@ -1024,7 +1016,7 @@ impl Engine { .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; let (arg, arg_pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, &a_expr[1], constants, + scope, global, state, lib, this_ptr, level, &a_expr[1], constants, )?; let num_params = arg @@ -1035,7 +1027,7 @@ impl Engine { false } else { let hash_script = calc_fn_hash(&fn_name, num_params as usize); - self.has_script_fn(Some(mods), state, lib, hash_script) + self.has_script_fn(Some(global), state, lib, hash_script) } .into()); } @@ -1043,7 +1035,7 @@ impl Engine { // Handle is_def_var() KEYWORD_IS_DEF_VAR if total_args == 1 => { let (arg, arg_pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, &a_expr[0], constants, + scope, global, state, lib, this_ptr, level, &a_expr[0], constants, )?; let var_name = arg .into_immutable_string() @@ -1056,13 +1048,20 @@ impl Engine { // eval - only in function call style let orig_scope_len = scope.len(); let (value, pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, &a_expr[0], constants, + scope, global, state, lib, this_ptr, level, &a_expr[0], constants, )?; let script = &value .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; - let result = - self.eval_script_expr_in_place(scope, mods, lib, script, pos, level + 1); + let result = self.eval_script_expr_in_place( + scope, + global, + state, + lib, + script, + pos, + level + 1, + ); // IMPORTANT! If the eval defines new variables in the current scope, // all variable offsets from this point on will be mis-aligned. @@ -1071,12 +1070,9 @@ impl Engine { } return result.map_err(|err| { - EvalAltResult::ErrorInFunctionCall( + ERR::ErrorInFunctionCall( KEYWORD_EVAL.to_string(), - mods.source - .as_ref() - .map(Identifier::to_string) - .unwrap_or_default(), + global.source.to_string(), err, pos, ) @@ -1088,8 +1084,8 @@ impl Engine { } // Normal function call - except for Fn, curry, call and eval (handled above) - let mut arg_values = StaticVec::with_capacity(a_expr.len()); - let mut args = StaticVec::with_capacity(a_expr.len() + curry.len()); + let mut arg_values = FnArgsVec::with_capacity(a_expr.len()); + let mut args = FnArgsVec::with_capacity(a_expr.len() + curry.len()); let mut is_ref_mut = false; // Capture parent scope? @@ -1098,7 +1094,7 @@ impl Engine { // variable access) to &mut because `scope` is needed. if capture_scope && !scope.is_empty() { a_expr.iter().try_for_each(|expr| { - self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants) + self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants) .map(|(value, _)| arg_values.push(value.flatten())) })?; args.extend(curry.iter_mut()); @@ -1109,7 +1105,8 @@ impl Engine { return self .exec_fn_call( - mods, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, scope, level, + global, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, scope, + level, ) .map(|(v, _)| v); } @@ -1123,22 +1120,22 @@ impl Engine { // avoid cloning the value if curry.is_empty() && !a_expr.is_empty() && a_expr[0].is_variable_access(false) { // func(x, ...) -> x.func(...) - let (first_expr, rest_expr) = a_expr.split_first().expect("not empty"); + let (first_expr, rest_expr) = a_expr.split_first().unwrap(); rest_expr.iter().try_for_each(|expr| { - self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants) + self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants) .map(|(value, _)| arg_values.push(value.flatten())) })?; let (mut target, _pos) = - self.search_namespace(scope, mods, state, lib, this_ptr, first_expr)?; + self.search_namespace(scope, global, state, lib, this_ptr, first_expr)?; if target.as_ref().is_read_only() { target = target.into_owned(); } #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut mods.num_operations, _pos)?; + self.inc_operations(&mut global.num_operations, _pos)?; #[cfg(not(feature = "no_closure"))] let target_is_shared = target.is_shared(); @@ -1151,14 +1148,14 @@ impl Engine { } else { // Turn it into a method call only if the object is not shared and not a simple value is_ref_mut = true; - let obj_ref = target.take_ref().expect("reference"); + let obj_ref = target.take_ref().expect("ref"); args.push(obj_ref); args.extend(arg_values.iter_mut()); } } else { // func(..., ...) a_expr.iter().try_for_each(|expr| { - self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants) + self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants) .map(|(value, _)| arg_values.push(value.flatten())) })?; args.extend(curry.iter_mut()); @@ -1167,7 +1164,7 @@ impl Engine { } self.exec_fn_call( - mods, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, None, level, + global, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, None, level, ) .map(|(v, _)| v) } @@ -1176,21 +1173,20 @@ impl Engine { pub(crate) fn make_qualified_function_call( &self, scope: &mut Scope, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, - namespace: &NamespaceRef, - fn_name: impl AsRef, + namespace: &Namespace, + fn_name: &str, args_expr: &[Expr], constants: &[Dynamic], hash: u64, pos: Position, level: usize, ) -> RhaiResult { - let fn_name = fn_name.as_ref(); - let mut arg_values = StaticVec::with_capacity(args_expr.len()); - let mut args = StaticVec::with_capacity(args_expr.len()); + let mut arg_values = FnArgsVec::with_capacity(args_expr.len()); + let mut args = FnArgsVec::with_capacity(args_expr.len()); let mut first_arg_value = None; if args_expr.is_empty() { @@ -1204,16 +1200,16 @@ impl Engine { arg_values.push(Dynamic::UNIT); args_expr.iter().skip(1).try_for_each(|expr| { - self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants) + self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants) .map(|(value, _)| arg_values.push(value.flatten())) })?; // Get target reference to first argument let (target, _pos) = - self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?; + self.search_scope_only(scope, global, state, lib, this_ptr, &args_expr[0])?; #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut mods.num_operations, _pos)?; + self.inc_operations(&mut global.num_operations, _pos)?; #[cfg(not(feature = "no_closure"))] let target_is_shared = target.is_shared(); @@ -1225,32 +1221,32 @@ impl Engine { args.extend(arg_values.iter_mut()); } else { // Turn it into a method call only if the object is not shared and not a simple value - let (first, rest) = arg_values.split_first_mut().expect("not empty"); + let (first, rest) = arg_values.split_first_mut().unwrap(); first_arg_value = Some(first); - let obj_ref = target.take_ref().expect("reference"); + let obj_ref = target.take_ref().expect("ref"); args.push(obj_ref); args.extend(rest.iter_mut()); } } else { // func(..., ...) or func(mod::x, ...) args_expr.iter().try_for_each(|expr| { - self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants) + self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants) .map(|(value, _)| arg_values.push(value.flatten())) })?; args.extend(arg_values.iter_mut()); } } - let module = self.search_imports(mods, state, namespace).ok_or_else(|| { - EvalAltResult::ErrorModuleNotFound(namespace.to_string(), namespace[0].pos) - })?; + let module = self + .search_imports(global, state, namespace) + .ok_or_else(|| ERR::ErrorModuleNotFound(namespace.to_string(), namespace[0].pos))?; // First search in script-defined functions (can override built-in) let func = match module.get_qualified_fn(hash) { // Then search in Rust functions None => { #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut mods.num_operations, pos)?; + self.inc_operations(&mut global.num_operations, pos)?; let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id())); let hash_qualified_fn = combine_hashes(hash, hash_params); @@ -1271,50 +1267,99 @@ impl Engine { match func { #[cfg(not(feature = "no_function"))] Some(f) if f.is_script() => { - let fn_def = f.get_script_fn_def().expect("scripted function"); + let fn_def = f.get_script_fn_def().expect("script-defined function"); if fn_def.body.is_empty() { Ok(Dynamic::UNIT) } else { let new_scope = &mut Scope::new(); - let mut source = module.id_raw().cloned(); - mem::swap(&mut mods.source, &mut source); + let mut source = module.id_raw().clone(); + mem::swap(&mut global.source, &mut source); let level = level + 1; let result = self.call_script_fn( - new_scope, mods, state, lib, &mut None, fn_def, &mut args, pos, true, level, + new_scope, global, state, lib, &mut None, fn_def, &mut args, pos, true, + level, ); - mods.source = source; + global.source = source; result } } Some(f) if f.is_plugin_fn() => { - let context = (self, fn_name, module.id(), &*mods, lib, pos).into(); - f.get_plugin_fn() + let context = (self, fn_name, module.id(), &*global, lib, pos).into(); + let result = f + .get_plugin_fn() .expect("plugin function") .clone() - .call(context, &mut args) - .map_err(|err| err.fill_position(pos)) + .call(context, &mut args); + self.check_return_value(result, pos) } Some(f) if f.is_native() => { let func = f.get_native_fn().expect("native function"); - let context = (self, fn_name, module.id(), &*mods, lib, pos).into(); - func(context, &mut args).map_err(|err| err.fill_position(pos)) + let context = (self, fn_name, module.id(), &*global, lib, pos).into(); + let result = func(context, &mut args); + self.check_return_value(result, pos) } Some(f) => unreachable!("unknown function type: {:?}", f), - None => Err(EvalAltResult::ErrorFunctionNotFound( + None => Err(ERR::ErrorFunctionNotFound( self.gen_call_signature(Some(namespace), fn_name, &args), pos, ) .into()), } } + + /// Evaluate a text script in place - used primarily for 'eval'. + pub(crate) fn eval_script_expr_in_place( + &self, + scope: &mut Scope, + global: &mut GlobalRuntimeState, + state: &mut EvalState, + lib: &[&Module], + script: &str, + pos: Position, + level: usize, + ) -> RhaiResult { + let _pos = pos; + + #[cfg(not(feature = "unchecked"))] + self.inc_operations(&mut global.num_operations, _pos)?; + + let script = script.trim(); + + if script.is_empty() { + return Ok(Dynamic::UNIT); + } + + // Compile the script text + // No optimizations because we only run it once + let ast = self.compile_with_scope_and_optimization_level( + &Scope::new(), + &[script], + #[cfg(not(feature = "no_optimize"))] + crate::OptimizationLevel::None, + )?; + + // If new functions are defined within the eval string, it is an error + #[cfg(not(feature = "no_function"))] + if !ast.shared_lib().is_empty() { + return Err(crate::PERR::WrongFnDefinition.into()); + } + + let statements = ast.statements(); + if statements.is_empty() { + return Ok(Dynamic::UNIT); + } + + // Evaluate the AST + self.eval_global_statements(scope, global, state, statements, lib, level) + } } diff --git a/src/func/callable_function.rs b/src/func/callable_function.rs new file mode 100644 index 00000000..0402ba87 --- /dev/null +++ b/src/func/callable_function.rs @@ -0,0 +1,256 @@ +//! Module defining the standard Rhai function type. + +use super::native::{FnAny, FnPlugin, IteratorFn, SendSync}; +use crate::ast::FnAccess; +use crate::plugin::PluginFunction; +use crate::Shared; +use std::fmt; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +/// A type encapsulating a function callable by Rhai. +#[derive(Clone)] +pub enum CallableFunction { + /// A pure native Rust function with all arguments passed by value. + Pure(Shared), + /// A native Rust object method with the first argument passed by reference, + /// and the rest passed by value. + Method(Shared), + /// An iterator function. + Iterator(IteratorFn), + /// A plugin function, + Plugin(Shared), + /// A script-defined function. + /// + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] + Script(Shared), +} + +impl fmt::Debug for CallableFunction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Pure(_) => write!(f, "NativePureFunction"), + Self::Method(_) => write!(f, "NativeMethod"), + Self::Iterator(_) => write!(f, "NativeIterator"), + Self::Plugin(_) => write!(f, "PluginFunction"), + + #[cfg(not(feature = "no_function"))] + Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f), + } + } +} + +impl fmt::Display for CallableFunction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Pure(_) => write!(f, "NativePureFunction"), + Self::Method(_) => write!(f, "NativeMethod"), + Self::Iterator(_) => write!(f, "NativeIterator"), + Self::Plugin(_) => write!(f, "PluginFunction"), + + #[cfg(not(feature = "no_function"))] + Self::Script(s) => fmt::Display::fmt(s, f), + } + } +} + +impl CallableFunction { + /// Is this a pure native Rust function? + #[inline] + #[must_use] + pub fn is_pure(&self) -> bool { + match self { + Self::Pure(_) => true, + Self::Method(_) | Self::Iterator(_) => false, + + Self::Plugin(p) => !p.is_method_call(), + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => false, + } + } + /// Is this a native Rust method function? + #[inline] + #[must_use] + pub fn is_method(&self) -> bool { + match self { + Self::Method(_) => true, + Self::Pure(_) | Self::Iterator(_) => false, + + Self::Plugin(p) => p.is_method_call(), + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => false, + } + } + /// Is this an iterator function? + #[inline] + #[must_use] + pub const fn is_iter(&self) -> bool { + match self { + Self::Iterator(_) => true, + Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => false, + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => false, + } + } + /// Is this a script-defined function? + #[inline] + #[must_use] + pub const fn is_script(&self) -> bool { + #[cfg(feature = "no_function")] + return false; + + #[cfg(not(feature = "no_function"))] + match self { + Self::Script(_) => true, + Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => false, + } + } + /// Is this a plugin function? + #[inline] + #[must_use] + pub const fn is_plugin_fn(&self) -> bool { + match self { + Self::Plugin(_) => true, + Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false, + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => false, + } + } + /// Is this a native Rust function? + #[inline] + #[must_use] + pub const fn is_native(&self) -> bool { + #[cfg(feature = "no_function")] + return true; + + #[cfg(not(feature = "no_function"))] + match self { + Self::Pure(_) | Self::Method(_) => true, + Self::Plugin(_) => true, + Self::Iterator(_) => true, + Self::Script(_) => false, + } + } + /// Get the access mode. + #[inline] + #[must_use] + pub fn access(&self) -> FnAccess { + #[cfg(feature = "no_function")] + return FnAccess::Public; + + #[cfg(not(feature = "no_function"))] + match self { + Self::Plugin(_) => FnAccess::Public, + Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => FnAccess::Public, + Self::Script(f) => f.access, + } + } + /// Get a shared reference to a native Rust function. + #[inline] + #[must_use] + pub fn get_native_fn(&self) -> Option<&Shared> { + match self { + Self::Pure(f) | Self::Method(f) => Some(f), + Self::Iterator(_) | Self::Plugin(_) => None, + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => None, + } + } + /// Get a shared reference to a script-defined function definition. + /// + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] + #[inline] + #[must_use] + pub const fn get_script_fn_def(&self) -> Option<&Shared> { + match self { + Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => None, + Self::Script(f) => Some(f), + } + } + /// Get a reference to an iterator function. + #[inline] + #[must_use] + pub fn get_iter_fn(&self) -> Option { + match self { + Self::Iterator(f) => Some(*f), + Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => None, + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => None, + } + } + /// Get a shared reference to a plugin function. + #[inline] + #[must_use] + pub fn get_plugin_fn(&self) -> Option<&Shared> { + match self { + Self::Plugin(f) => Some(f), + Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => None, + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => None, + } + } + /// Create a new [`CallableFunction::Pure`]. + #[inline(always)] + #[must_use] + pub fn from_pure(func: Box) -> Self { + Self::Pure(func.into()) + } + /// Create a new [`CallableFunction::Method`]. + #[inline(always)] + #[must_use] + pub fn from_method(func: Box) -> Self { + Self::Method(func.into()) + } + /// Create a new [`CallableFunction::Plugin`]. + #[inline(always)] + #[must_use] + pub fn from_plugin(func: impl PluginFunction + 'static + SendSync) -> Self { + Self::Plugin((Box::new(func) as Box).into()) + } +} + +impl From for CallableFunction { + #[inline(always)] + fn from(func: IteratorFn) -> Self { + Self::Iterator(func) + } +} + +#[cfg(not(feature = "no_function"))] +impl From for CallableFunction { + #[inline(always)] + fn from(_func: crate::ast::ScriptFnDef) -> Self { + Self::Script(_func.into()) + } +} + +#[cfg(not(feature = "no_function"))] +impl From> for CallableFunction { + #[inline(always)] + fn from(_func: Shared) -> Self { + Self::Script(_func) + } +} + +impl From for CallableFunction { + #[inline(always)] + fn from(func: T) -> Self { + Self::from_plugin(func) + } +} + +impl From> for CallableFunction { + #[inline(always)] + fn from(func: Shared) -> Self { + Self::Plugin(func) + } +} diff --git a/src/func/func.rs b/src/func/func.rs index f442b6fb..65636fbd 100644 --- a/src/func/func.rs +++ b/src/func/func.rs @@ -3,8 +3,9 @@ #![cfg(not(feature = "no_function"))] #![allow(non_snake_case)] +use crate::parser::ParseResult; use crate::types::dynamic::Variant; -use crate::{Engine, EvalAltResult, ParseError, Scope, SmartString, AST}; +use crate::{Engine, RhaiResultOf, Scope, SmartString, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -77,11 +78,7 @@ pub trait Func { /// # Ok(()) /// # } /// ``` - fn create_from_script( - self, - script: &str, - entry_point: &str, - ) -> Result; + fn create_from_script(self, script: &str, entry_point: &str) -> ParseResult; } macro_rules! def_anonymous_fn { @@ -92,9 +89,9 @@ macro_rules! def_anonymous_fn { impl<$($par: Variant + Clone,)* RET: Variant + Clone> Func<($($par,)*), RET> for Engine { #[cfg(feature = "sync")] - type Output = Box Result> + Send + Sync>; + type Output = Box RhaiResultOf + Send + Sync>; #[cfg(not(feature = "sync"))] - type Output = Box Result>>; + type Output = Box RhaiResultOf>; #[inline] fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output { @@ -103,7 +100,7 @@ macro_rules! def_anonymous_fn { } #[inline] - fn create_from_script(self, script: &str, entry_point: &str) -> Result { + fn create_from_script(self, script: &str, entry_point: &str) -> ParseResult { let ast = self.compile(script)?; Ok(Func::<($($par,)*), RET>::create_from_ast(self, ast, entry_point)) } diff --git a/src/func/hashing.rs b/src/func/hashing.rs index 38a8b636..96bdba56 100644 --- a/src/func/hashing.rs +++ b/src/func/hashing.rs @@ -87,10 +87,7 @@ pub fn get_hasher() -> ahash::AHasher { /// The first module name is skipped. Hashing starts from the _second_ module in the chain. #[inline] #[must_use] -pub fn calc_qualified_var_hash<'a>( - modules: impl Iterator + 'a>, - var_name: impl AsRef, -) -> u64 { +pub fn calc_qualified_var_hash<'a>(modules: impl Iterator, var_name: &str) -> u64 { let s = &mut get_hasher(); // We always skip the first module @@ -98,9 +95,9 @@ pub fn calc_qualified_var_hash<'a>( modules .inspect(|_| len += 1) .skip(1) - .for_each(|m| m.as_ref().hash(s)); + .for_each(|m| m.hash(s)); len.hash(s); - var_name.as_ref().hash(s); + var_name.hash(s); match s.finish() { 0 => ALT_ZERO_HASH, @@ -123,9 +120,9 @@ pub fn calc_qualified_var_hash<'a>( /// The first module name is skipped. Hashing starts from the _second_ module in the chain. #[inline] #[must_use] -pub fn calc_qualified_fn_hash( - modules: impl Iterator>, - fn_name: impl AsRef, +pub fn calc_qualified_fn_hash<'a>( + modules: impl Iterator, + fn_name: &str, num: usize, ) -> u64 { let s = &mut get_hasher(); @@ -135,9 +132,9 @@ pub fn calc_qualified_fn_hash( modules .inspect(|_| len += 1) .skip(1) - .for_each(|m| m.as_ref().hash(s)); + .for_each(|m| m.hash(s)); len.hash(s); - fn_name.as_ref().hash(s); + fn_name.hash(s); num.hash(s); match s.finish() { @@ -156,8 +153,8 @@ pub fn calc_qualified_fn_hash( /// If the hash happens to be zero, it is mapped to `DEFAULT_HASH`. #[inline(always)] #[must_use] -pub fn calc_fn_hash(fn_name: impl AsRef, num: usize) -> u64 { - calc_qualified_fn_hash(empty::<&str>(), fn_name, num) +pub fn calc_fn_hash(fn_name: &str, num: usize) -> u64 { + calc_qualified_fn_hash(empty(), fn_name, num) } /// Calculate a non-zero [`u64`] hash key from a list of parameter types. diff --git a/src/func/mod.rs b/src/func/mod.rs index 1d5a0841..dcae0482 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -3,6 +3,7 @@ pub mod args; pub mod builtin; pub mod call; +pub mod callable_function; pub mod func; pub mod hashing; pub mod native; @@ -13,6 +14,7 @@ pub mod script; pub use args::FuncArgs; pub use builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; pub use call::FnCallArgs; +pub use callable_function::CallableFunction; #[cfg(not(feature = "no_function"))] pub use func::Func; pub use hashing::{ @@ -20,8 +22,8 @@ pub use hashing::{ combine_hashes, get_hasher, }; pub use native::{ - shared_make_mut, shared_take, shared_take_or_clone, shared_try_take, shared_write_lock, - CallableFunction, FnAny, FnPlugin, IteratorFn, Locked, NativeCallContext, SendSync, Shared, + shared_make_mut, shared_take, shared_take_or_clone, shared_try_take, shared_write_lock, FnAny, + FnPlugin, IteratorFn, Locked, NativeCallContext, SendSync, Shared, }; pub use plugin::PluginFunction; pub use register::RegisterNativeFunction; diff --git a/src/func/native.rs b/src/func/native.rs index e5cf5b8e..7d49fc84 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -1,14 +1,16 @@ //! Module defining interfaces to native-Rust functions. use super::call::FnCallArgs; -use crate::ast::{FnAccess, FnCallHashes}; -use crate::engine::{EvalState, Imports}; +use crate::ast::FnCallHashes; +use crate::engine::{EvalState, GlobalRuntimeState}; use crate::plugin::PluginFunction; use crate::tokenizer::{Token, TokenizeState}; +use crate::types::dynamic::Variant; use crate::{ - calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Module, Position, RhaiResult, + calc_fn_hash, Dynamic, Engine, EvalContext, FuncArgs, Module, Position, RhaiResult, + RhaiResultOf, StaticVec, ERR, }; -use std::fmt; +use std::any::type_name; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -56,11 +58,17 @@ pub type LockGuard<'a, T> = std::sync::RwLockWriteGuard<'a, T>; /// Context of a native Rust function call. #[derive(Debug)] pub struct NativeCallContext<'a> { + /// The current [`Engine`]. engine: &'a Engine, + /// Name of function called. fn_name: &'a str, + /// Function source, if any. source: Option<&'a str>, - mods: Option<&'a Imports>, + /// The current [`GlobalRuntimeState`], if any. + global: Option<&'a GlobalRuntimeState>, + /// The current stack of loaded [modules][Module]. lib: &'a [&'a Module], + /// [Position] of the function call. pos: Position, } @@ -69,7 +77,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef + 'a + ?Sized> &'a Engine, &'a S, Option<&'a S>, - &'a Imports, + &'a GlobalRuntimeState, &'a M, Position, )> for NativeCallContext<'a> @@ -80,7 +88,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef + 'a + ?Sized> &'a Engine, &'a S, Option<&'a S>, - &'a Imports, + &'a GlobalRuntimeState, &'a M, Position, ), @@ -88,8 +96,8 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef + 'a + ?Sized> Self { engine: value.0, fn_name: value.1.as_ref(), - source: value.2.map(|v| v.as_ref()), - mods: Some(value.3), + source: value.2.map(S::as_ref), + global: Some(value.3), lib: value.4.as_ref(), pos: value.5, } @@ -105,7 +113,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef + 'a + ?Sized> engine: value.0, fn_name: value.1.as_ref(), source: None, - mods: None, + global: None, lib: value.2.as_ref(), pos: Position::NONE, } @@ -130,7 +138,7 @@ impl<'a> NativeCallContext<'a> { engine, fn_name: fn_name.as_ref(), source: None, - mods: None, + global: None, lib, pos: Position::NONE, } @@ -147,15 +155,15 @@ impl<'a> NativeCallContext<'a> { engine: &'a Engine, fn_name: &'a (impl AsRef + 'a + ?Sized), source: Option<&'a (impl AsRef + 'a + ?Sized)>, - imports: &'a Imports, + global: &'a GlobalRuntimeState, lib: &'a [&Module], pos: Position, ) -> Self { Self { engine, fn_name: fn_name.as_ref(), - source: source.map(|v| v.as_ref()), - mods: Some(imports), + source: source.map(<_>::as_ref), + global: Some(global), lib, pos, } @@ -172,7 +180,7 @@ impl<'a> NativeCallContext<'a> { pub const fn fn_name(&self) -> &str { self.fn_name } - /// [Position][`Position`] of the function call. + /// [Position] of the function call. #[inline(always)] #[must_use] pub const fn position(&self) -> Position { @@ -184,24 +192,25 @@ impl<'a> NativeCallContext<'a> { pub const fn source(&self) -> Option<&str> { self.source } - /// Get an iterator over the current set of modules imported via `import` statements. + /// Get an iterator over the current set of modules imported via `import` statements + /// in reverse order. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline] pub fn iter_imports(&self) -> impl Iterator { - self.mods.iter().flat_map(|&m| m.iter()) + self.global.iter().flat_map(|&m| m.iter_modules()) } - /// Get an iterator over the current set of modules imported via `import` statements. + /// Get an iterator over the current set of modules imported via `import` statements in reverse order. #[cfg(not(feature = "no_module"))] #[allow(dead_code)] #[inline] pub(crate) fn iter_imports_raw( &self, ) -> impl Iterator)> { - self.mods.iter().flat_map(|&m| m.iter_raw()) + self.global.iter().flat_map(|&m| m.iter_modules_raw()) } - /// _(internals)_ The current set of modules imported via `import` statements. + /// _(internals)_ The current [`GlobalRuntimeState`], if any. /// Exported under the `internals` feature only. /// /// Not available under `no_module`. @@ -209,13 +218,14 @@ impl<'a> NativeCallContext<'a> { #[cfg(not(feature = "no_module"))] #[inline(always)] #[must_use] - pub const fn imports(&self) -> Option<&Imports> { - self.mods + pub const fn global_runtime_state(&self) -> Option<&GlobalRuntimeState> { + self.global } - /// Get an iterator over the namespaces containing definitions of all script-defined functions. + /// Get an iterator over the namespaces containing definitions of all script-defined functions + /// in reverse order. #[inline] pub fn iter_namespaces(&self) -> impl Iterator { - self.lib.iter().cloned() + self.lib.iter().rev().cloned() } /// _(internals)_ The current set of namespaces containing definitions of all script-defined functions. /// Exported under the `internals` feature only. @@ -225,6 +235,31 @@ impl<'a> NativeCallContext<'a> { pub const fn namespaces(&self) -> &[&Module] { self.lib } + /// Call a function inside the call context with the provided arguments. + #[inline] + pub fn call_fn( + &self, + fn_name: impl AsRef, + args: impl FuncArgs, + ) -> RhaiResultOf { + let mut arg_values = StaticVec::new_const(); + args.parse(&mut arg_values); + + let mut args: StaticVec<_> = arg_values.iter_mut().collect(); + + let result = self.call_fn_raw(fn_name, false, false, &mut args)?; + + let typ = self.engine().map_type_name(result.type_name()); + + result.try_cast().ok_or_else(|| { + ERR::ErrorMismatchOutputType( + self.engine().map_type_name(type_name::()).into(), + typ.into(), + Position::NONE, + ) + .into() + }) + } /// Call a function inside the call context. /// /// If `is_method_call` is [`true`], the first argument is assumed to be the `this` pointer for @@ -250,22 +285,26 @@ impl<'a> NativeCallContext<'a> { is_ref_mut: bool, is_method_call: bool, args: &mut [&mut Dynamic], - ) -> Result> { + ) -> RhaiResult { let fn_name = fn_name.as_ref(); + let len = args.len(); let hash = if is_method_call { FnCallHashes::from_all( #[cfg(not(feature = "no_function"))] - calc_fn_hash(fn_name, args.len() - 1), - calc_fn_hash(fn_name, args.len()), + calc_fn_hash(fn_name, len - 1), + calc_fn_hash(fn_name, len), ) } else { - calc_fn_hash(fn_name, args.len()).into() + calc_fn_hash(fn_name, len).into() }; self.engine() .exec_fn_call( - &mut self.mods.cloned().unwrap_or_else(|| Imports::new()), + &mut self + .global + .cloned() + .unwrap_or_else(|| GlobalRuntimeState::new()), &mut EvalState::new(), self.lib, fn_name, @@ -336,6 +375,9 @@ pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult; #[cfg(feature = "sync")] pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult + Send + Sync; +/// A trail object for built-in functions. +pub type FnBuiltin = fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult; + /// A standard function that gets an iterator from a type. pub type IteratorFn = fn(Dynamic) -> Box>; @@ -378,253 +420,8 @@ pub type OnParseTokenCallback = /// A standard callback function for variable access. #[cfg(not(feature = "sync"))] pub type OnVarCallback = - Box Result, Box> + 'static>; + Box RhaiResultOf> + 'static>; /// A standard callback function for variable access. #[cfg(feature = "sync")] -pub type OnVarCallback = Box< - dyn Fn(&str, usize, &EvalContext) -> Result, Box> - + Send - + Sync - + 'static, ->; - -/// A type encapsulating a function callable by Rhai. -#[derive(Clone)] -pub enum CallableFunction { - /// A pure native Rust function with all arguments passed by value. - Pure(Shared), - /// A native Rust object method with the first argument passed by reference, - /// and the rest passed by value. - Method(Shared), - /// An iterator function. - Iterator(IteratorFn), - /// A plugin function, - Plugin(Shared), - /// A script-defined function. - /// - /// Not available under `no_function`. - #[cfg(not(feature = "no_function"))] - Script(Shared), -} - -impl fmt::Debug for CallableFunction { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Pure(_) => write!(f, "NativePureFunction"), - Self::Method(_) => write!(f, "NativeMethod"), - Self::Iterator(_) => write!(f, "NativeIterator"), - Self::Plugin(_) => write!(f, "PluginFunction"), - - #[cfg(not(feature = "no_function"))] - Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f), - } - } -} - -impl fmt::Display for CallableFunction { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Pure(_) => write!(f, "NativePureFunction"), - Self::Method(_) => write!(f, "NativeMethod"), - Self::Iterator(_) => write!(f, "NativeIterator"), - Self::Plugin(_) => write!(f, "PluginFunction"), - - #[cfg(not(feature = "no_function"))] - CallableFunction::Script(s) => fmt::Display::fmt(s, f), - } - } -} - -impl CallableFunction { - /// Is this a pure native Rust function? - #[inline] - #[must_use] - pub fn is_pure(&self) -> bool { - match self { - Self::Pure(_) => true, - Self::Method(_) | Self::Iterator(_) => false, - - Self::Plugin(p) => !p.is_method_call(), - - #[cfg(not(feature = "no_function"))] - Self::Script(_) => false, - } - } - /// Is this a native Rust method function? - #[inline] - #[must_use] - pub fn is_method(&self) -> bool { - match self { - Self::Method(_) => true, - Self::Pure(_) | Self::Iterator(_) => false, - - Self::Plugin(p) => p.is_method_call(), - - #[cfg(not(feature = "no_function"))] - Self::Script(_) => false, - } - } - /// Is this an iterator function? - #[inline] - #[must_use] - pub const fn is_iter(&self) -> bool { - match self { - Self::Iterator(_) => true, - Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => false, - - #[cfg(not(feature = "no_function"))] - Self::Script(_) => false, - } - } - /// Is this a Rhai-scripted function? - #[inline] - #[must_use] - pub const fn is_script(&self) -> bool { - match self { - #[cfg(not(feature = "no_function"))] - Self::Script(_) => true, - - Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => false, - } - } - /// Is this a plugin function? - #[inline] - #[must_use] - pub const fn is_plugin_fn(&self) -> bool { - match self { - Self::Plugin(_) => true, - Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false, - - #[cfg(not(feature = "no_function"))] - Self::Script(_) => false, - } - } - /// Is this a native Rust function? - #[inline] - #[must_use] - pub const fn is_native(&self) -> bool { - match self { - Self::Pure(_) | Self::Method(_) => true, - Self::Plugin(_) => true, - Self::Iterator(_) => true, - - #[cfg(not(feature = "no_function"))] - Self::Script(_) => false, - } - } - /// Get the access mode. - #[inline] - #[must_use] - pub fn access(&self) -> FnAccess { - match self { - Self::Plugin(_) => FnAccess::Public, - Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => FnAccess::Public, - - #[cfg(not(feature = "no_function"))] - Self::Script(f) => f.access, - } - } - /// Get a shared reference to a native Rust function. - #[inline] - #[must_use] - pub fn get_native_fn(&self) -> Option<&Shared> { - match self { - Self::Pure(f) | Self::Method(f) => Some(f), - Self::Iterator(_) | Self::Plugin(_) => None, - - #[cfg(not(feature = "no_function"))] - Self::Script(_) => None, - } - } - /// Get a shared reference to a script-defined function definition. - /// - /// Not available under `no_function`. - #[cfg(not(feature = "no_function"))] - #[inline] - #[must_use] - pub const fn get_script_fn_def(&self) -> Option<&Shared> { - match self { - Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => None, - Self::Script(f) => Some(f), - } - } - /// Get a reference to an iterator function. - #[inline] - #[must_use] - pub fn get_iter_fn(&self) -> Option { - match self { - Self::Iterator(f) => Some(*f), - Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => None, - - #[cfg(not(feature = "no_function"))] - Self::Script(_) => None, - } - } - /// Get a shared reference to a plugin function. - #[inline] - #[must_use] - pub fn get_plugin_fn(&self) -> Option<&Shared> { - match self { - Self::Plugin(f) => Some(f), - Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => None, - - #[cfg(not(feature = "no_function"))] - Self::Script(_) => None, - } - } - /// Create a new [`CallableFunction::Pure`]. - #[inline(always)] - #[must_use] - pub fn from_pure(func: Box) -> Self { - Self::Pure(func.into()) - } - /// Create a new [`CallableFunction::Method`]. - #[inline(always)] - #[must_use] - pub fn from_method(func: Box) -> Self { - Self::Method(func.into()) - } - /// Create a new [`CallableFunction::Plugin`]. - #[inline(always)] - #[must_use] - pub fn from_plugin(func: impl PluginFunction + 'static + SendSync) -> Self { - Self::Plugin((Box::new(func) as Box).into()) - } -} - -impl From for CallableFunction { - #[inline(always)] - fn from(func: IteratorFn) -> Self { - Self::Iterator(func) - } -} - -#[cfg(not(feature = "no_function"))] -impl From for CallableFunction { - #[inline(always)] - fn from(_func: crate::ast::ScriptFnDef) -> Self { - Self::Script(_func.into()) - } -} - -#[cfg(not(feature = "no_function"))] -impl From> for CallableFunction { - #[inline(always)] - fn from(_func: Shared) -> Self { - Self::Script(_func) - } -} - -impl From for CallableFunction { - #[inline(always)] - fn from(func: T) -> Self { - Self::from_plugin(func) - } -} - -impl From> for CallableFunction { - #[inline(always)] - fn from(func: Shared) -> Self { - Self::Plugin(func) - } -} +pub type OnVarCallback = + Box RhaiResultOf> + Send + Sync + 'static>; diff --git a/src/func/plugin.rs b/src/func/plugin.rs index 3faf408b..dc20e6f3 100644 --- a/src/func/plugin.rs +++ b/src/func/plugin.rs @@ -10,7 +10,7 @@ pub use crate::{ use std::prelude::v1::*; pub use std::{any::TypeId, mem}; -pub type RhaiResult = Result>; +pub type RhaiResult = crate::RhaiResult; #[cfg(not(features = "no_module"))] pub use rhai_codegen::*; diff --git a/src/func/register.rs b/src/func/register.rs index e317f51b..70085821 100644 --- a/src/func/register.rs +++ b/src/func/register.rs @@ -2,12 +2,13 @@ #![allow(non_snake_case)] -use crate::func::call::FnCallArgs; -use crate::func::native::{CallableFunction, FnAny, SendSync}; -use crate::r#unsafe::unsafe_try_cast; +use super::call::FnCallArgs; +use super::callable_function::CallableFunction; +use super::native::{FnAny, SendSync}; +use crate::r#unsafe::unsafe_cast; use crate::tokenizer::Position; use crate::types::dynamic::{DynamicWriteLock, Variant}; -use crate::{Dynamic, EvalAltResult, NativeCallContext}; +use crate::{Dynamic, NativeCallContext, RhaiResultOf, ERR}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{any::TypeId, mem}; @@ -49,8 +50,7 @@ pub fn by_value(data: &mut Dynamic) -> T { ref_t.clone() } else if TypeId::of::() == TypeId::of::() { // If T is `String`, data must be `ImmutableString`, so map directly to it - let value = mem::take(data).into_string().expect("`ImmutableString`"); - unsafe_try_cast(value).expect("checked") + unsafe_cast(mem::take(data).into_string().expect("`ImmutableString`")) } else { // We consume the argument and then replace it with () - the argument is not supposed to be used again. // This way, we avoid having to clone the argument again, because it is already a clone when passed here. @@ -123,7 +123,7 @@ macro_rules! def_register { #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) { - return Err(EvalAltResult::ErrorAssignmentToConstant(String::new(), Position::NONE).into()); + return Err(ERR::ErrorAssignmentToConstant(String::new(), Position::NONE).into()); } // The arguments are assumed to be of the correct number and types! @@ -134,7 +134,7 @@ macro_rules! def_register { let r = self($($arg),*); // Map the result - Ok(r.into_dynamic()) + Ok(Dynamic::from(r)) }) as Box) } } @@ -151,7 +151,7 @@ macro_rules! def_register { #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) { - return Err(EvalAltResult::ErrorAssignmentToConstant(String::new(), Position::NONE).into()); + return Err(ERR::ErrorAssignmentToConstant(String::new(), Position::NONE).into()); } // The arguments are assumed to be of the correct number and types! @@ -162,24 +162,24 @@ macro_rules! def_register { let r = self(ctx, $($arg),*); // Map the result - Ok(r.into_dynamic()) + Ok(Dynamic::from(r)) }) as Box) } } impl< - FN: Fn($($param),*) -> Result> + SendSync + 'static, + FN: Fn($($param),*) -> RhaiResultOf + SendSync + 'static, $($par: Variant + Clone,)* RET: Variant + Clone - > RegisterNativeFunction<($($mark,)*), Result>> for FN { + > RegisterNativeFunction<($($mark,)*), 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::<$par>()),*].into_boxed_slice() } - #[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::>>() } - #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::>>() } + #[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::>() } + #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::>() } #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) { - return Err(EvalAltResult::ErrorAssignmentToConstant(String::new(), Position::NONE).into()); + return Err(ERR::ErrorAssignmentToConstant(String::new(), Position::NONE).into()); } // The arguments are assumed to be of the correct number and types! @@ -193,18 +193,18 @@ macro_rules! def_register { } impl< - FN: for<'a> Fn(NativeCallContext<'a>, $($param),*) -> Result> + SendSync + 'static, + FN: for<'a> Fn(NativeCallContext<'a>, $($param),*) -> RhaiResultOf + SendSync + 'static, $($par: Variant + Clone,)* RET: Variant + Clone - > RegisterNativeFunction<(NativeCallContext<'static>, $($mark,)*), Result>> for FN { + > RegisterNativeFunction<(NativeCallContext<'static>, $($mark,)*), 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::<$par>()),*].into_boxed_slice() } - #[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::>>() } - #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::>>() } + #[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::>() } + #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::>() } #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) { - return Err(EvalAltResult::ErrorAssignmentToConstant(String::new(), Position::NONE).into()); + return Err(ERR::ErrorAssignmentToConstant(String::new(), Position::NONE).into()); } // The arguments are assumed to be of the correct number and types! diff --git a/src/func/script.rs b/src/func/script.rs index 5e32bfcd..715fbcf7 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -1,11 +1,11 @@ //! Implement script function-calling mechanism for [`Engine`]. #![cfg(not(feature = "no_function"))] +use super::call::FnCallArgs; use crate::ast::ScriptFnDef; -use crate::engine::{EvalState, Imports}; -use crate::func::call::FnCallArgs; +use crate::engine::{EvalState, GlobalRuntimeState}; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; -use crate::{Dynamic, Engine, EvalAltResult, Module, Position, RhaiResult, Scope, StaticVec}; +use crate::{Dynamic, Engine, Module, Position, RhaiError, RhaiResult, Scope, StaticVec, ERR}; use std::mem; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -24,7 +24,7 @@ impl Engine { pub(crate) fn call_script_fn( &self, scope: &mut Scope, - mods: &mut Imports, + global: &mut GlobalRuntimeState, state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, @@ -38,18 +38,17 @@ impl Engine { fn make_error( name: String, fn_def: &ScriptFnDef, - mods: &Imports, - err: Box, + global: &GlobalRuntimeState, + err: RhaiError, pos: Position, ) -> RhaiResult { - Err(EvalAltResult::ErrorInFunctionCall( + Err(ERR::ErrorInFunctionCall( name, fn_def .lib .as_ref() - .and_then(|m| m.id().map(|id| id.to_string())) - .or_else(|| mods.source.as_ref().map(|s| s.to_string())) - .unwrap_or_default(), + .and_then(|m| m.id().map(str::to_string)) + .unwrap_or_else(|| global.source.to_string()), err, pos, ) @@ -59,7 +58,7 @@ impl Engine { assert!(fn_def.params.len() == args.len()); #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut mods.num_operations, pos)?; + self.inc_operations(&mut global.num_operations, pos)?; if fn_def.body.is_empty() { return Ok(Dynamic::UNIT); @@ -68,11 +67,11 @@ impl Engine { // Check for stack overflow #[cfg(not(feature = "unchecked"))] if level > self.max_call_levels() { - return Err(EvalAltResult::ErrorStackOverflow(pos).into()); + return Err(ERR::ErrorStackOverflow(pos).into()); } let orig_scope_len = scope.len(); - let orig_mods_len = mods.len(); + let orig_mods_len = global.num_imported_modules(); // Put arguments into scope as variables // Actually consume the arguments instead of cloning them @@ -106,39 +105,37 @@ impl Engine { }; #[cfg(not(feature = "no_module"))] - if !fn_def.mods.is_empty() { - fn_def - .mods - .iter_raw() - .for_each(|(n, m)| mods.push(n.clone(), m.clone())); + if let Some(ref modules) = fn_def.global { + modules + .iter() + .cloned() + .for_each(|(n, m)| global.push_module(n, m)); } // Evaluate the function - let body = &fn_def.body; let result = self .eval_stmt_block( scope, - mods, + global, state, lib, this_ptr, - body, - true, + &fn_def.body, rewind_scope, level, ) .or_else(|err| match *err { // Convert return statement to return value - EvalAltResult::Return(x, _) => Ok(x), + ERR::Return(x, _) => Ok(x), // Error in sub function call - EvalAltResult::ErrorInFunctionCall(name, src, err, _) => { + 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, mods, err, pos) + make_error(fn_name, fn_def, global, err, pos) } // System errors are passed straight-through mut err if err.is_system_exception() => { @@ -146,28 +143,29 @@ impl Engine { Err(err.into()) } // Other errors are wrapped in `ErrorInFunctionCall` - _ => make_error(fn_def.name.to_string(), fn_def, mods, err, pos), + _ => make_error(fn_def.name.to_string(), fn_def, global, err, pos), }); - // Remove all local variables + // Remove all local variables and imported modules if rewind_scope { scope.rewind(orig_scope_len); } else if !args.is_empty() { // Remove arguments only, leaving new variables in the scope scope.remove_range(orig_scope_len, args.len()) } + global.truncate_modules(orig_mods_len); - mods.truncate(orig_mods_len); + // Restore state state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len); result } - // Does a scripted function exist? + // Does a script-defined function exist? #[must_use] pub(crate) fn has_script_fn( &self, - mods: Option<&Imports>, + global: Option<&GlobalRuntimeState>, state: &mut EvalState, lib: &[&Module], hash_script: u64, @@ -183,7 +181,7 @@ impl Engine { // Then check the global namespace and packages || self.global_modules.iter().any(|m| m.contains_fn(hash_script)) // Then check imported modules - || mods.map_or(false, |m| m.contains_fn(hash_script)) + || global.map_or(false, |m| m.contains_fn(hash_script)) // Then check sub-modules || self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script)); diff --git a/src/lib.rs b/src/lib.rs index 23b851ba..f56a18ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,12 +41,12 @@ //! engine.register_fn("compute", compute_something); //! //! # #[cfg(not(feature = "no_std"))] -//! # #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] -//! assert_eq!( -//! // Evaluate the script, expects a 'bool' return -//! engine.eval_file::("my_script.rhai".into())?, -//! true -//! ); +//! # #[cfg(not(target_arch = "wasm32"))] +//! # #[cfg(not(target_arch = "wasm64"))] +//! // Evaluate the script, expecting a 'bool' result +//! let result = engine.eval_file::("my_script.rhai".into())?; +//! +//! assert_eq!(result, true); //! //! Ok(()) //! } @@ -71,7 +71,6 @@ use std::prelude::v1::*; mod api; mod ast; -mod custom_syntax; mod engine; mod func; mod module; @@ -83,7 +82,16 @@ mod tokenizer; mod types; mod r#unsafe; -type RhaiResult = Result>; +/// Error encountered when parsing a script. +type PERR = ParseErrorType; +/// Evaluation result. +type ERR = EvalAltResult; +/// General evaluation error for Rhai scripts. +type RhaiError = Box; +/// Generic [`Result`] type for Rhai functions. +type RhaiResultOf = Result; +/// General [`Result`] type for Rhai functions returning [`Dynamic`] values. +type RhaiResult = RhaiResultOf; /// The system integer type. It is defined as [`i64`]. /// @@ -98,6 +106,20 @@ pub type INT = i64; #[cfg(feature = "only_i32")] pub type INT = i32; +/// The unsigned system base integer type. It is defined as [`u64`]. +/// +/// If the `only_i32` feature is enabled, this will be [`u32`] instead. +#[cfg(not(feature = "only_i32"))] +#[allow(non_camel_case_types)] +type UNSIGNED_INT = u64; +/// The unsigned system integer base type. +/// It is defined as [`u32`] since the `only_i32` feature is used. +/// +/// If the `only_i32` feature is not used, this will be `u64` instead. +#[cfg(feature = "only_i32")] +#[allow(non_camel_case_types)] +type UNSIGNED_INT = u32; + /// The system floating-point type. It is defined as [`f64`]. /// Not available under `no_float`. /// @@ -115,14 +137,15 @@ pub type FLOAT = f64; #[cfg(feature = "f32_float")] pub type FLOAT = f32; -pub type ExclusiveRange = std::ops::Range; -pub type InclusiveRange = std::ops::RangeInclusive; +/// An exclusive integer range. +type ExclusiveRange = std::ops::Range; +/// An inclusive integer range. +type InclusiveRange = std::ops::RangeInclusive; + +pub use api::custom_syntax::Expression; pub use ast::{FnAccess, AST}; -pub use custom_syntax::Expression; -pub use engine::{ - Engine, EvalContext, OP_CONTAINS, OP_EQUALS, OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE, -}; +pub use engine::{Engine, EvalContext, OP_CONTAINS, OP_EQUALS}; pub use func::{NativeCallContext, RegisterNativeFunction}; pub use module::{FnNamespace, Module}; pub use tokenizer::Position; @@ -171,8 +194,11 @@ pub type Array = Vec; #[cfg(not(feature = "no_index"))] pub type Blob = Vec; -/// Hash map of [`Dynamic`] values with [`SmartString`](https://crates.io/crates/smartstring) keys. +/// A dictionary of [`Dynamic`] values with string keys. /// Not available under `no_object`. +/// +/// [`SmartString`](https://crates.io/crates/smartstring) is used as the key type because most +/// property names are ASCII and short, fewer than 23 characters, so they can be stored inline. #[cfg(not(feature = "no_object"))] pub type Map = std::collections::BTreeMap; @@ -204,7 +230,10 @@ pub use tokenizer::{ }; #[cfg(feature = "internals")] -pub use parser::{IdentifierBuilder, ParseState}; +pub use types::StringsInterner; + +#[cfg(feature = "internals")] +pub use parser::ParseState; #[cfg(feature = "internals")] pub use ast::{ @@ -217,10 +246,13 @@ pub use ast::{ pub use ast::FloatWrapper; #[cfg(feature = "internals")] -pub use engine::{EvalState, FnResolutionCache, FnResolutionCacheEntry, Imports}; +pub use engine::{EvalState, GlobalRuntimeState}; #[cfg(feature = "internals")] -pub use module::NamespaceRef; +pub use func::call::{FnResolutionCache, FnResolutionCacheEntry}; + +#[cfg(feature = "internals")] +pub use module::Namespace; /// Alias to [`smallvec::SmallVec<[T; 3]>`](https://crates.io/crates/smallvec), which is a /// specialized [`Vec`] backed by a small, inline, fixed-size array when there are ≤ 3 items stored. @@ -291,6 +323,26 @@ type StaticVec = smallvec::SmallVec<[T; 3]>; #[cfg(feature = "internals")] pub type StaticVec = smallvec::SmallVec<[T; 3]>; +/// Inline arguments storage for function calls. +/// +/// # Notes +/// +/// Since most usage of this is during a function call to gather up arguments, this is mostly +/// allocated on the stack, so we can tolerate a larger number of values stored inline. +/// +/// Most functions have few parameters, but closures with a lot of captured variables can +/// potentially have many. Having a larger inline storage for arguments reduces allocations in +/// scripts with heavy closure usage. +/// +/// Under `no_closure`, this type aliases to [`StaticVec`][crate::StaticVec] instead. +#[cfg(not(feature = "no_closure"))] +type FnArgsVec = smallvec::SmallVec<[T; 8]>; + +/// Inline arguments storage for function calls. +/// This type aliases to [`StaticVec`][crate::StaticVec]. +#[cfg(feature = "no_closure")] +type FnArgsVec = crate::StaticVec; + pub(crate) type SmartString = smartstring::SmartString; // Compiler guards against mutually-exclusive feature flags @@ -315,11 +367,13 @@ compile_error!("`stdweb` cannot be used with `no-std`"); #[cfg(feature = "no_std")] compile_error!("`no_std` cannot be used for WASM target"); -#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_arch = "wasm64"))] #[cfg(feature = "wasm-bindgen")] compile_error!("`wasm-bindgen` cannot be used for non-WASM target"); -#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_arch = "wasm64"))] #[cfg(feature = "stdweb")] compile_error!("`stdweb` cannot be used non-WASM target"); diff --git a/src/module/mod.rs b/src/module/mod.rs index 8152f0a9..4de4b678 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -5,12 +5,11 @@ use crate::func::{ shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn, RegisterNativeFunction, SendSync, }; -use crate::parser::IdentifierBuilder; use crate::tokenizer::Token; use crate::types::dynamic::Variant; use crate::{ - calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, EvalAltResult, - Identifier, ImmutableString, NativeCallContext, Shared, StaticVec, + calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, Identifier, + ImmutableString, NativeCallContext, RhaiResultOf, Shared, StaticVec, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -47,9 +46,15 @@ pub struct FuncInfo { pub params: usize, /// Parameter types (if applicable). pub param_types: StaticVec, - /// Parameter names (if available). + /// Parameter names and types (if available). #[cfg(feature = "metadata")] - pub param_names: StaticVec, + pub param_names_and_types: StaticVec, + /// Return type name. + #[cfg(feature = "metadata")] + pub return_type_name: Identifier, + /// Comments. + #[cfg(feature = "metadata")] + pub comments: Option]>>, } impl FuncInfo { @@ -60,16 +65,21 @@ impl FuncInfo { pub fn gen_signature(&self) -> String { let mut sig = format!("{}(", self.name); - if !self.param_names.is_empty() { - let mut params: StaticVec> = - self.param_names.iter().map(|s| s.as_str().into()).collect(); - let return_type = params.pop().unwrap_or_else(|| "()".into()); + if !self.param_names_and_types.is_empty() { + let params: StaticVec<_> = self + .param_names_and_types + .iter() + .map(|s| s.as_str()) + .collect(); sig.push_str(¶ms.join(", ")); - if &*return_type != "()" { - sig.push_str(") -> "); - sig.push_str(&return_type); - } else { - sig.push(')'); + sig.push_str(")"); + + match self.return_type_name.as_str() { + "" | "()" => (), + ty => { + sig.push_str(" -> "); + sig.push_str(ty); + } } } else { for x in 0..self.params { @@ -82,7 +92,12 @@ impl FuncInfo { if self.func.is_script() { sig.push(')'); } else { - sig.push_str(") -> ?"); + sig.push_str(")"); + + match self.return_type_name.as_str() { + "()" => (), + _ => sig.push_str(" -> ?"), + } } } @@ -100,12 +115,12 @@ impl FuncInfo { /// /// The first module name is skipped. Hashing starts from the _second_ module in the chain. #[inline] -pub fn calc_native_fn_hash( - modules: impl Iterator>, - fn_name: impl AsRef, +pub fn calc_native_fn_hash<'a>( + modules: impl Iterator, + fn_name: &str, params: &[TypeId], ) -> u64 { - let hash_script = calc_qualified_fn_hash(modules, fn_name.as_ref(), params.len()); + let hash_script = calc_qualified_fn_hash(modules, fn_name, params.len()); let hash_params = calc_fn_params_hash(params.iter().cloned()); combine_hashes(hash_script, hash_params) } @@ -115,7 +130,8 @@ pub fn calc_native_fn_hash( #[derive(Clone)] pub struct Module { /// ID identifying the module. - id: Option, + /// No ID if string is empty. + id: Identifier, /// Is this module internal? pub(crate) internal: bool, /// Is this module part of a standard library? @@ -139,8 +155,6 @@ pub struct Module { indexed: bool, /// Does the [`Module`] contain indexed functions that have been exposed to the global namespace? contains_indexed_global_functions: bool, - /// Interned strings - identifiers: IdentifierBuilder, } impl Default for Module { @@ -154,8 +168,9 @@ impl fmt::Debug for Module { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut d = f.debug_struct("Module"); - self.id.as_ref().map(|id| d.field("id", id)); - + if !self.id.is_empty() { + d.field("id", &self.id); + } if !self.modules.is_empty() { d.field( "modules", @@ -225,7 +240,7 @@ impl Module { #[must_use] pub fn new() -> Self { Self { - id: None, + id: Identifier::new_const(), internal: false, standard: false, modules: BTreeMap::new(), @@ -237,7 +252,6 @@ impl Module { all_type_iterators: BTreeMap::new(), indexed: true, contains_indexed_global_functions: false, - identifiers: IdentifierBuilder::new(), } } @@ -254,18 +268,24 @@ impl Module { #[inline] #[must_use] pub fn id(&self) -> Option<&str> { - self.id_raw().map(|s| s.as_str()) + if self.id_raw().is_empty() { + None + } else { + Some(self.id_raw()) + } } /// Get the ID of the [`Module`] as an [`Identifier`], if any. #[inline(always)] #[must_use] - pub(crate) const fn id_raw(&self) -> Option<&Identifier> { - self.id.as_ref() + pub(crate) const fn id_raw(&self) -> &Identifier { + &self.id } /// Set the ID of the [`Module`]. /// + /// If the string is empty, it is equivalent to clearing the ID. + /// /// # Example /// /// ``` @@ -276,7 +296,7 @@ impl Module { /// ``` #[inline(always)] pub fn set_id(&mut self, id: impl Into) -> &mut Self { - self.id = Some(id.into()); + self.id = id.into(); self } /// Clear the ID of the [`Module`]. @@ -293,7 +313,7 @@ impl Module { /// ``` #[inline(always)] pub fn clear_id(&mut self) -> &mut Self { - self.id = None; + self.id.clear(); self } @@ -440,9 +460,9 @@ impl Module { /// Name and Position in [`EvalAltResult`] are [`None`] and [`NONE`][Position::NONE] and must be set afterwards. #[cfg(not(feature = "no_module"))] #[inline] - pub(crate) fn get_qualified_var(&self, hash_var: u64) -> Result<&Dynamic, Box> { + pub(crate) fn get_qualified_var(&self, hash_var: u64) -> RhaiResultOf<&Dynamic> { self.all_variables.get(&hash_var).ok_or_else(|| { - EvalAltResult::ErrorVariableNotFound(String::new(), crate::Position::NONE).into() + crate::ERR::ErrorVariableNotFound(String::new(), crate::Position::NONE).into() }) } @@ -457,8 +477,8 @@ impl Module { // None + function name + number of arguments. let num_params = fn_def.params.len(); let hash_script = crate::calc_fn_hash(&fn_def.name, num_params); - let mut param_names = fn_def.params.clone(); - param_names.push("Dynamic".into()); + #[cfg(feature = "metadata")] + let param_names_and_types = fn_def.params.iter().cloned().collect(); self.functions.insert( hash_script, FuncInfo { @@ -468,7 +488,11 @@ impl Module { params: num_params, param_types: StaticVec::new_const(), #[cfg(feature = "metadata")] - param_names, + param_names_and_types, + #[cfg(feature = "metadata")] + return_type_name: "Dynamic".into(), + #[cfg(feature = "metadata")] + comments: None, func: Into::::into(fn_def).into(), } .into(), @@ -597,7 +621,7 @@ impl Module { self.functions.contains_key(&hash_fn) } - /// Update the metadata (parameter names/types and return type) of a registered function. + /// _(metadata)_ Update the metadata (parameter names/types and return type) of a registered function. /// Exported under the `metadata` feature only. /// /// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call. @@ -612,14 +636,69 @@ 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(&mut self, hash_fn: u64, arg_names: &[impl AsRef]) -> &mut Self { - let param_names = arg_names + pub fn update_fn_metadata>( + &mut self, + hash_fn: u64, + arg_names: impl AsRef<[S]>, + ) -> &mut Self { + let mut param_names: StaticVec<_> = arg_names + .as_ref() .iter() - .map(|name| self.identifiers.get(name.as_ref())) + .map(|s| s.as_ref().into()) .collect(); if let Some(f) = self.functions.get_mut(&hash_fn) { - f.param_names = param_names; + let (param_names, return_type_name) = if param_names.len() > f.params { + let return_type = param_names.pop().unwrap(); + (param_names, return_type) + } else { + (param_names, Default::default()) + }; + f.param_names_and_types = param_names; + f.return_type_name = return_type_name; + } + + self + } + + /// _(metadata)_ Update the metadata (parameter names/types, return type and doc-comments) of a + /// registered function. + /// Exported under the `metadata` feature only. + /// + /// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call. + /// + /// ## Parameter Names and Types + /// + /// Each parameter name/type pair should be a single string of the format: `var_name: type`. + /// + /// ## Return Type + /// + /// The _last entry_ in the list should be the _return type_ of the function. In other words, + /// the number of entries should be one larger than the number of parameters. + /// + /// ## Comments + /// + /// Block doc-comments should be kept in a single line. + /// + /// Line doc-comments should be kept in one string slice per line without the termination line-break. + /// + /// Leading white-spaces should be stripped, and each string slice always starts with the corresponding + /// doc-comment leader: `///` or `/**`. + #[cfg(feature = "metadata")] + #[inline] + pub fn update_fn_metadata_with_comments, C: AsRef>( + &mut self, + hash_fn: u64, + arg_names: impl AsRef<[A]>, + comments: impl AsRef<[C]>, + ) -> &mut Self { + self.update_fn_metadata(hash_fn, arg_names); + + let comments = comments.as_ref(); + + if !comments.is_empty() { + let f = self.functions.get_mut(&hash_fn).unwrap(); + f.comments = Some(comments.iter().map(|s| s.as_ref().into()).collect()); } self @@ -664,20 +743,30 @@ impl Module { /// # WARNING - Low Level API /// /// This function is very low level. + /// + /// ## Parameter Names and Types + /// + /// Each parameter name/type pair should be a single string of the format: `var_name: type`. + /// + /// ## Return Type + /// + /// The _last entry_ in the list should be the _return type_ of the function. + /// In other words, the number of entries should be one larger than the number of parameters. #[inline] pub fn set_fn( &mut self, - name: impl AsRef + Into, + name: impl AsRef, namespace: FnNamespace, access: FnAccess, arg_names: Option<&[&str]>, - arg_types: &[TypeId], + arg_types: impl AsRef<[TypeId]>, func: CallableFunction, ) -> u64 { let _arg_names = arg_names; let is_method = func.is_method(); let mut param_types: StaticVec<_> = arg_types + .as_ref() .iter() .cloned() .enumerate() @@ -686,26 +775,37 @@ impl Module { param_types.shrink_to_fit(); #[cfg(feature = "metadata")] - let mut param_names: StaticVec<_> = _arg_names - .iter() - .flat_map(|&p| p.iter()) - .map(|&arg| self.identifiers.get(arg)) - .collect(); - #[cfg(feature = "metadata")] - param_names.shrink_to_fit(); + let (param_names, return_type_name) = { + let mut names = _arg_names + .iter() + .flat_map(|&p| p.iter()) + .map(|&s| s.into()) + .collect::>(); + let return_type = if names.len() > arg_types.as_ref().len() { + names.pop().unwrap() + } else { + Default::default() + }; + names.shrink_to_fit(); + (names, return_type) + }; let hash_fn = calc_native_fn_hash(empty::<&str>(), name.as_ref(), ¶m_types); self.functions.insert( hash_fn, FuncInfo { - name: self.identifiers.get(name), + name: name.as_ref().into(), namespace, access, params: param_types.len(), param_types, #[cfg(feature = "metadata")] - param_names, + param_names_and_types: param_names, + #[cfg(feature = "metadata")] + return_type_name, + #[cfg(feature = "metadata")] + comments: None, func: func.into(), } .into(), @@ -717,6 +817,56 @@ impl Module { hash_fn } + /// _(metadata)_ Set a Rust function into the [`Module`], returning a non-zero hash key. + /// Exported under the `metadata` feature only. + /// + /// If there is an existing Rust function of the same hash, it is replaced. + /// + /// # WARNING - Low Level API + /// + /// This function is very low level. + /// + /// ## Parameter Names and Types + /// + /// Each parameter name/type pair should be a single string of the format: `var_name: type`. + /// + /// ## Return Type + /// + /// The _last entry_ in the list should be the _return type_ of the function. + /// In other words, the number of entries should be one larger than the number of parameters. + /// + /// ## Comments + /// + /// Block doc-comments should be kept in a single line. + /// + /// Line doc-comments should be kept in one string slice per line without the termination line-break. + /// + /// Leading white-spaces should be stripped, and each string slice always starts with the corresponding + /// doc-comment leader: `///` or `/**`. + #[cfg(feature = "metadata")] + #[inline] + pub fn set_fn_with_comments>( + &mut self, + name: impl AsRef, + namespace: FnNamespace, + access: FnAccess, + arg_names: Option<&[&str]>, + arg_types: impl AsRef<[TypeId]>, + comments: impl AsRef<[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.get_mut(&hash).unwrap(); + f.comments = Some(comments.iter().map(|s| s.as_ref().into()).collect()); + } + + hash + } + /// Set a Rust function taking a reference to the scripting [`Engine`][crate::Engine], /// the current set of functions, plus a list of mutable [`Dynamic`] references /// into the [`Module`], returning a non-zero hash key. @@ -731,7 +881,7 @@ impl Module { /// /// This function is very low level. /// - /// ## Arguments + /// # Arguments /// /// A list of [`TypeId`]'s is taken as the argument types. /// @@ -750,7 +900,7 @@ impl Module { /// /// # Function Metadata /// - /// No metadata for the function is registered. Use `update_fn_metadata` to add metadata. + /// No metadata for the function is registered. Use [`update_fn_metadata`][Module::update_fn_metadata] to add metadata. /// /// # Example /// @@ -778,26 +928,23 @@ impl Module { /// *x *= 2; // the first argument can be mutated /// } /// - /// Ok(orig) // return Result> + /// Ok(orig) // return RhaiResult /// }); /// /// assert!(module.contains_fn(hash)); /// ``` #[inline(always)] - pub fn set_raw_fn( + pub fn set_raw_fn( &mut self, - name: N, + name: impl AsRef, namespace: FnNamespace, access: FnAccess, - arg_types: &[TypeId], + arg_types: impl AsRef<[TypeId]>, func: F, ) -> u64 where - N: AsRef + Into, T: Variant + Clone, - F: Fn(NativeCallContext, &mut FnCallArgs) -> Result> - + SendSync - + 'static, + F: Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResultOf + SendSync + 'static, { let f = move |ctx: NativeCallContext, args: &mut FnCallArgs| func(ctx, args).map(Dynamic::from); @@ -839,7 +986,7 @@ impl Module { where N: AsRef + Into, T: Variant + Clone, - F: RegisterNativeFunction>>, + F: RegisterNativeFunction>, { self.set_fn( name, @@ -858,7 +1005,8 @@ impl Module { /// /// # Function Metadata /// - /// No metadata for the function is registered. Use `update_fn_metadata` to add metadata. + /// No metadata for the function is registered. + /// Use [`update_fn_metadata`][Module::update_fn_metadata] to add metadata. /// /// # Example /// @@ -874,11 +1022,11 @@ impl Module { where A: Variant + Clone, T: Variant + Clone, - F: RegisterNativeFunction>>, - F: Fn(&mut A) -> Result> + SendSync + 'static, + F: RegisterNativeFunction>, + F: Fn(&mut A) -> RhaiResultOf + SendSync + 'static, { self.set_fn( - &crate::engine::make_getter(name), + crate::engine::make_getter(name.as_ref()).as_str(), FnNamespace::Global, FnAccess::Public, None, @@ -895,7 +1043,8 @@ impl Module { /// /// # Function Metadata /// - /// No metadata for the function is registered. Use `update_fn_metadata` to add metadata. + /// No metadata for the function is registered. + /// Use [`update_fn_metadata`][Module::update_fn_metadata] to add metadata. /// /// # Example /// @@ -915,11 +1064,11 @@ impl Module { where A: Variant + Clone, B: Variant + Clone, - F: RegisterNativeFunction>>, - F: Fn(&mut A, B) -> Result<(), Box> + SendSync + 'static, + F: RegisterNativeFunction>, + F: Fn(&mut A, B) -> RhaiResultOf<()> + SendSync + 'static, { self.set_fn( - &crate::engine::make_setter(name), + crate::engine::make_setter(name.as_ref()).as_str(), FnNamespace::Global, FnAccess::Public, None, @@ -941,7 +1090,8 @@ impl Module { /// /// # Function Metadata /// - /// No metadata for the function is registered. Use `update_fn_metadata` to add metadata. + /// No metadata for the function is registered. + /// Use [`update_fn_metadata`][Module::update_fn_metadata] to add metadata. /// /// # Example /// @@ -961,8 +1111,8 @@ impl Module { A: Variant + Clone, B: Variant + Clone, T: Variant + Clone, - F: RegisterNativeFunction>>, - F: Fn(&mut A, B) -> Result> + SendSync + 'static, + F: RegisterNativeFunction>, + F: Fn(&mut A, B) -> RhaiResultOf + SendSync + 'static, { #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { @@ -1002,7 +1152,8 @@ impl Module { /// /// # Function Metadata /// - /// No metadata for the function is registered. Use `update_fn_metadata` to add metadata. + /// No metadata for the function is registered. + /// Use [`update_fn_metadata`][Module::update_fn_metadata] to add metadata. /// /// # Example /// @@ -1022,8 +1173,8 @@ impl Module { A: Variant + Clone, B: Variant + Clone, C: Variant + Clone, - F: RegisterNativeFunction>>, - F: Fn(&mut A, B, C) -> Result<(), Box> + SendSync + 'static, + F: RegisterNativeFunction>, + F: Fn(&mut A, B, C) -> RhaiResultOf<()> + SendSync + 'static, { #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { @@ -1063,7 +1214,8 @@ impl Module { /// /// # Function Metadata /// - /// No metadata for the function is registered. Use `update_fn_metadata` to add metadata. + /// No metadata for the function is registered. + /// Use [`update_fn_metadata`][Module::update_fn_metadata] to add metadata. /// /// # Example /// @@ -1086,8 +1238,8 @@ impl Module { #[inline(always)] pub fn set_indexer_get_set_fn( &mut self, - get_fn: impl Fn(&mut A, B) -> Result> + SendSync + 'static, - set_fn: impl Fn(&mut A, B, T) -> Result<(), Box> + SendSync + 'static, + get_fn: impl Fn(&mut A, B) -> RhaiResultOf + SendSync + 'static, + set_fn: impl Fn(&mut A, B, T) -> RhaiResultOf<()> + SendSync + 'static, ) -> (u64, u64) where A: Variant + Clone, @@ -1142,7 +1294,6 @@ impl Module { self.all_type_iterators.clear(); self.indexed = false; self.contains_indexed_global_functions = false; - self.identifiers += other.identifiers; self } @@ -1162,7 +1313,6 @@ impl Module { self.all_type_iterators.clear(); self.indexed = false; self.contains_indexed_global_functions = false; - self.identifiers += other.identifiers; self } @@ -1191,7 +1341,6 @@ impl Module { self.all_type_iterators.clear(); self.indexed = false; self.contains_indexed_global_functions = false; - self.identifiers.merge(&other.identifiers); self } @@ -1241,7 +1390,6 @@ impl Module { self.all_type_iterators.clear(); self.indexed = false; self.contains_indexed_global_functions = false; - self.identifiers.merge(&other.identifiers); self } @@ -1284,8 +1432,8 @@ impl Module { /// Get an iterator to the sub-modules in the [`Module`]. #[inline] - pub fn iter_sub_modules(&self) -> impl Iterator)> { - self.modules.iter().map(|(k, m)| (k.as_str(), m.clone())) + pub fn iter_sub_modules(&self) -> impl Iterator)> { + self.modules.iter().map(|(k, m)| (k.as_str(), m)) } /// Get an iterator to the variables in the [`Module`]. @@ -1328,7 +1476,7 @@ impl Module { f.access, f.name.as_str(), f.params, - f.func.get_script_fn_def().expect("scripted function"), + f.func.get_script_fn_def().expect("script-defined function"), ) }) } @@ -1404,13 +1552,13 @@ impl Module { scope: crate::Scope, ast: &crate::AST, engine: &crate::Engine, - ) -> Result> { + ) -> RhaiResultOf { let mut scope = scope; - let mut mods = crate::engine::Imports::new(); - let orig_mods_len = mods.len(); + let mut global = crate::engine::GlobalRuntimeState::new(); + let orig_mods_len = global.num_imported_modules(); // Run the script - engine.eval_ast_with_scope_raw(&mut scope, &mut mods, &ast, 0)?; + engine.eval_ast_with_scope_raw(&mut scope, &mut global, &ast, 0)?; // Create new module let mut module = @@ -1421,11 +1569,11 @@ impl Module { match aliases.len() { 0 => (), 1 => { - let alias = aliases.pop().expect("not empty"); + let alias = aliases.pop().unwrap(); module.set_var(alias, value); } _ => { - let last_alias = aliases.pop().expect("not empty"); + let last_alias = aliases.pop().unwrap(); aliases.into_iter().for_each(|alias| { module.set_var(alias, value.clone()); }); @@ -1437,13 +1585,18 @@ impl Module { }); // Extra modules left in the scope become sub-modules - let mut func_mods = crate::engine::Imports::new(); + let mut func_global = None; - mods.into_iter().skip(orig_mods_len).for_each(|(alias, m)| { - func_mods.push(alias.clone(), m.clone()); - module.set_sub_module(alias, m); + global.into_iter().skip(orig_mods_len).for_each(|kv| { + if func_global.is_none() { + func_global = Some(StaticVec::new()); + } + func_global.as_mut().expect("`Some`").push(kv.clone()); + module.set_sub_module(kv.0, kv.1); }); + let func_global = func_global.map(|v| v.into_boxed_slice()); + // Non-private functions defined become module functions #[cfg(not(feature = "no_function"))] if ast.has_functions() { @@ -1459,20 +1612,16 @@ impl Module { let mut func = f .func .get_script_fn_def() - .expect("scripted function") + .expect("script-defined function") .as_ref() .clone(); func.lib = Some(ast.shared_lib().clone()); - func.mods = func_mods.clone(); + func.global = func_global.clone(); module.set_script_fn(func); }); } - if let Some(s) = ast.source_raw() { - module.set_id(s.clone()); - } else { - module.clear_id(); - } + module.set_id(ast.source_raw().clone()); module.build_index(); @@ -1647,20 +1796,21 @@ impl Module { } } -/// _(internals)_ A chain of [module][Module] names to namespace-qualify a variable or function call. -/// Exported under the `internals` feature only. +/// _(internals)_ A chain of [module][Module] names to namespace-qualify a variable or function +/// call. Exported under the `internals` feature only. /// -/// A [`u64`] offset to the current [`Scope`][crate::Scope] is cached for quick search purposes. +/// A [`u64`] offset to the current [stack of imported modules][crate::GlobalRuntimeState] is +/// cached for quick search purposes. /// -/// A [`StaticVec`] is used because most namespace-qualified access contains only one level, -/// and it is wasteful to always allocate a [`Vec`] with one element. +/// A [`StaticVec`] is used because the vast majority of namespace-qualified access contains only +/// one level, and it is wasteful to always allocate a [`Vec`] with one element. #[derive(Clone, Eq, PartialEq, Default, Hash)] -pub struct NamespaceRef { +pub struct Namespace { index: Option, path: StaticVec, } -impl fmt::Debug for NamespaceRef { +impl fmt::Debug for Namespace { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(index) = self.index { write!(f, "{} -> ", index)?; @@ -1677,7 +1827,7 @@ impl fmt::Debug for NamespaceRef { } } -impl fmt::Display for NamespaceRef { +impl fmt::Display for Namespace { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str( &self @@ -1690,7 +1840,7 @@ impl fmt::Display for NamespaceRef { } } -impl Deref for NamespaceRef { +impl Deref for Namespace { type Target = StaticVec; #[inline(always)] @@ -1699,14 +1849,14 @@ impl Deref for NamespaceRef { } } -impl DerefMut for NamespaceRef { +impl DerefMut for Namespace { #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.path } } -impl From> for NamespaceRef { +impl From> for Namespace { #[inline(always)] fn from(mut path: Vec) -> Self { path.shrink_to_fit(); @@ -1717,7 +1867,7 @@ impl From> for NamespaceRef { } } -impl From> for NamespaceRef { +impl From> for Namespace { #[inline(always)] fn from(mut path: StaticVec) -> Self { path.shrink_to_fit(); @@ -1725,8 +1875,8 @@ impl From> for NamespaceRef { } } -impl NamespaceRef { - /// Create a new [`NamespaceRef`]. +impl Namespace { + /// Create a new [`Namespace`]. #[inline(always)] #[must_use] pub const fn new() -> Self { diff --git a/src/module/resolvers/collection.rs b/src/module/resolvers/collection.rs index a3581a99..6deb7c3d 100644 --- a/src/module/resolvers/collection.rs +++ b/src/module/resolvers/collection.rs @@ -1,4 +1,4 @@ -use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; +use crate::{Engine, Module, ModuleResolver, Position, RhaiResultOf, Shared, ERR}; use std::ops::AddAssign; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -77,7 +77,7 @@ impl ModuleResolversCollection { /// Get an iterator of all the [module resolvers][ModuleResolver]. #[inline] pub fn iter(&self) -> impl Iterator { - self.0.iter().map(|v| v.as_ref()) + self.0.iter().map(<_>::as_ref) } /// Remove all [module resolvers][ModuleResolver]. #[inline(always)] @@ -123,19 +123,19 @@ impl ModuleResolver for ModuleResolversCollection { source_path: Option<&str>, path: &str, pos: Position, - ) -> Result, Box> { + ) -> RhaiResultOf> { for resolver in self.0.iter() { match resolver.resolve(engine, source_path, path, pos) { Ok(module) => return Ok(module), Err(err) => match *err { - EvalAltResult::ErrorModuleNotFound(_, _) => continue, - EvalAltResult::ErrorInModule(_, err, _) => return Err(err), + ERR::ErrorModuleNotFound(_, _) => continue, + ERR::ErrorInModule(_, err, _) => return Err(err), _ => panic!("ModuleResolver::resolve returns error that is not ErrorModuleNotFound or ErrorInModule"), }, } } - Err(EvalAltResult::ErrorModuleNotFound(path.into(), pos).into()) + Err(ERR::ErrorModuleNotFound(path.into(), pos).into()) } } diff --git a/src/module/resolvers/dummy.rs b/src/module/resolvers/dummy.rs index 904f48ce..f36148bc 100644 --- a/src/module/resolvers/dummy.rs +++ b/src/module/resolvers/dummy.rs @@ -1,4 +1,4 @@ -use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; +use crate::{Engine, Module, ModuleResolver, Position, RhaiResultOf, Shared, ERR}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -44,7 +44,7 @@ impl ModuleResolver for DummyModuleResolver { _: Option<&str>, path: &str, pos: Position, - ) -> Result, Box> { - Err(EvalAltResult::ErrorModuleNotFound(path.into(), pos).into()) + ) -> RhaiResultOf> { + Err(ERR::ErrorModuleNotFound(path.into(), pos).into()) } } diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index d9d567e7..6d65a063 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -1,8 +1,11 @@ #![cfg(not(feature = "no_std"))] -#![cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +#![cfg(not(target_arch = "wasm32"))] +#![cfg(not(target_arch = "wasm64"))] use crate::func::native::shared_write_lock; -use crate::{Engine, EvalAltResult, Identifier, Module, ModuleResolver, Position, Scope, Shared}; +use crate::{ + Engine, Identifier, Module, ModuleResolver, Position, RhaiResultOf, Scope, Shared, ERR, +}; use std::{ collections::BTreeMap, @@ -223,7 +226,7 @@ impl FileModuleResolver { path: impl AsRef, source_path: Option>, ) -> Option> { - let file_path = self.get_file_path(path.as_ref(), source_path.as_ref().map(|v| v.as_ref())); + let file_path = self.get_file_path(path.as_ref(), source_path.as_ref().map(<_>::as_ref)); shared_write_lock(&self.cache) .remove_entry(&file_path) @@ -259,7 +262,7 @@ impl ModuleResolver for FileModuleResolver { source_path: Option<&str>, path: &str, pos: Position, - ) -> Result, Box> { + ) -> RhaiResultOf> { // Load relative paths from source if there is no base path specified let source_path = source_path.and_then(|p| Path::new(p).parent().map(|p| p.to_string_lossy())); @@ -285,17 +288,17 @@ impl ModuleResolver for FileModuleResolver { let mut ast = engine .compile_file(file_path.clone()) .map_err(|err| match *err { - EvalAltResult::ErrorSystem(_, err) if err.is::() => { - Box::new(EvalAltResult::ErrorModuleNotFound(path.to_string(), pos)) + ERR::ErrorSystem(_, err) if err.is::() => { + Box::new(ERR::ErrorModuleNotFound(path.to_string(), pos)) } - _ => Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos)), + _ => Box::new(ERR::ErrorInModule(path.to_string(), err, pos)), })?; ast.set_source(path); // Make a module from the AST let m: Shared = Module::eval_ast_as_new(scope, &ast, engine) - .map_err(|err| Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos)))? + .map_err(|err| Box::new(ERR::ErrorInModule(path.to_string(), err, pos)))? .into(); // Put it into the cache @@ -315,7 +318,7 @@ impl ModuleResolver for FileModuleResolver { source_path: Option<&str>, path: &str, pos: Position, - ) -> Option>> { + ) -> Option> { // Construct the script file path let file_path = self.get_file_path(path, source_path); @@ -328,10 +331,10 @@ impl ModuleResolver for FileModuleResolver { ast }) .map_err(|err| match *err { - EvalAltResult::ErrorSystem(_, err) if err.is::() => { - EvalAltResult::ErrorModuleNotFound(path.to_string(), pos).into() + ERR::ErrorSystem(_, err) if err.is::() => { + ERR::ErrorModuleNotFound(path.to_string(), pos).into() } - _ => EvalAltResult::ErrorInModule(path.to_string(), err, pos).into(), + _ => ERR::ErrorInModule(path.to_string(), err, pos).into(), }), ) } diff --git a/src/module/resolvers/mod.rs b/src/module/resolvers/mod.rs index 47911c34..82d350c8 100644 --- a/src/module/resolvers/mod.rs +++ b/src/module/resolvers/mod.rs @@ -1,5 +1,5 @@ use crate::func::native::SendSync; -use crate::{Engine, EvalAltResult, Module, Position, Shared, AST}; +use crate::{Engine, Module, Position, RhaiResultOf, Shared, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -11,7 +11,8 @@ mod stat; pub use collection::ModuleResolversCollection; pub use dummy::DummyModuleResolver; #[cfg(not(feature = "no_std"))] -#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_arch = "wasm64"))] pub use file::FileModuleResolver; pub use stat::StaticModuleResolver; @@ -24,7 +25,7 @@ pub trait ModuleResolver: SendSync { source_path: Option<&str>, path: &str, pos: Position, - ) -> Result, Box>; + ) -> RhaiResultOf>; /// Resolve an `AST` based on a path string. /// @@ -43,7 +44,7 @@ pub trait ModuleResolver: SendSync { source_path: Option<&str>, path: &str, pos: Position, - ) -> Option>> { + ) -> Option> { None } } diff --git a/src/module/resolvers/stat.rs b/src/module/resolvers/stat.rs index 90b33ed7..dc25c563 100644 --- a/src/module/resolvers/stat.rs +++ b/src/module/resolvers/stat.rs @@ -1,5 +1,5 @@ use crate::{ - Engine, EvalAltResult, Identifier, Module, ModuleResolver, Position, Shared, SmartString, + Engine, Identifier, Module, ModuleResolver, Position, RhaiResultOf, Shared, SmartString, ERR, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -131,11 +131,11 @@ impl ModuleResolver for StaticModuleResolver { _: Option<&str>, path: &str, pos: Position, - ) -> Result, Box> { + ) -> RhaiResultOf> { self.0 .get(path) .cloned() - .ok_or_else(|| EvalAltResult::ErrorModuleNotFound(path.into(), pos).into()) + .ok_or_else(|| ERR::ErrorModuleNotFound(path.into(), pos).into()) } } diff --git a/src/optimizer.rs b/src/optimizer.rs index c1170a35..bc6e0aeb 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -3,20 +3,24 @@ use crate::ast::{Expr, OpAssignment, Stmt, AST_OPTION_FLAGS::*}; use crate::engine::{ - EvalState, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, + EvalState, GlobalRuntimeState, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, + KEYWORD_TYPE_OF, }; use crate::func::builtin::get_builtin_binary_op_fn; use crate::func::hashing::get_hasher; use crate::tokenizer::Token; use crate::types::dynamic::AccessMode; -use crate::{calc_fn_hash, Dynamic, Engine, FnPtr, Position, Scope, StaticVec, AST, INT}; +use crate::{ + calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, Position, Scope, + StaticVec, AST, INT, +}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ + any::TypeId, convert::TryFrom, hash::{Hash, Hasher}, mem, - ops::DerefMut, }; /// Level of optimization performed. @@ -106,13 +110,11 @@ impl<'a> OptimizerState<'a> { } /// Look up a constant from the list. #[inline] - pub fn find_constant(&self, name: impl AsRef) -> Option<&Dynamic> { + pub fn find_constant(&self, name: &str) -> Option<&Dynamic> { if !self.propagate_constants { return None; } - let name = name.as_ref(); - for (n, access, value) in self.variables.iter().rev() { if n == name { return match access { @@ -128,7 +130,7 @@ impl<'a> OptimizerState<'a> { #[inline] pub fn call_fn_with_constant_arguments( &self, - fn_name: impl AsRef, + fn_name: &str, arg_values: &mut [Dynamic], ) -> Option { #[cfg(not(feature = "no_function"))] @@ -138,10 +140,10 @@ impl<'a> OptimizerState<'a> { self.engine .call_native_fn( - &mut Imports::new(), + &mut GlobalRuntimeState::new(), &mut EvalState::new(), lib, - &fn_name, + fn_name, calc_fn_hash(&fn_name, arg_values.len()), &mut arg_values.iter_mut().collect::>(), false, @@ -153,6 +155,22 @@ 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_params = calc_fn_params_hash(arg_types.as_ref().iter().cloned()); + let hash = combine_hashes(hash_script, hash_params); + + // First check the global namespace and packages, but skip modules that are standard because + // they should never conflict with system functions. + engine.global_modules.iter().filter(|m| !m.standard).any(|m| m.contains_fn(hash)) + // Then check sub-modules + || engine.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash)) +} + /// Optimize a block of [statements][Stmt]. fn optimize_stmt_block( mut statements: StaticVec, @@ -173,6 +191,32 @@ fn optimize_stmt_block( Stmt::is_pure }; + // Flatten blocks + loop { + if let Some(n) = statements.iter().enumerate().find_map(|(i, s)| match s { + Stmt::Block(block, _) if !block.iter().any(Stmt::is_block_dependent) => Some(i), + _ => None, + }) { + let (first, second) = statements.split_at_mut(n); + let stmt = mem::take(&mut second[0]); + let mut stmts = match stmt { + Stmt::Block(block, _) => block, + stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt), + }; + statements = first + .iter_mut() + .map(mem::take) + .chain(stmts.iter_mut().map(mem::take)) + .chain(second.iter_mut().skip(1).map(mem::take)) + .collect(); + } else { + break; + } + + is_dirty = true; + } + + // Optimize loop { state.clear_dirty(); @@ -282,14 +326,14 @@ fn optimize_stmt_block( && !last_stmt.returns_value() => { state.set_dirty(); - statements.pop().expect(">= 2 elements"); + statements.pop().unwrap(); } // { ...; return val; } -> { ...; val } [.., Stmt::Return(options, ref mut expr, pos)] if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) => { state.set_dirty(); - *statements.last_mut().expect(">= 2 elements") = expr + *statements.last_mut().unwrap() = expr .as_mut() .map_or_else(|| Stmt::Noop(pos), |e| Stmt::Expr(mem::take(e))); } @@ -306,10 +350,9 @@ fn optimize_stmt_block( { state.set_dirty(); if second_last_stmt.returns_value() { - *statements.last_mut().expect(">= 2 elements") = - Stmt::Noop(last_stmt.position()); + *statements.last_mut().unwrap() = Stmt::Noop(last_stmt.position()); } else { - statements.pop().expect(">= 2 elements"); + statements.pop().unwrap(); } } _ => break, @@ -327,7 +370,7 @@ fn optimize_stmt_block( if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) => { state.set_dirty(); - statements.pop().expect(">= 2 elements"); + statements.pop().unwrap(); } // { ...; return pure_val; } -> { ... } [.., Stmt::Return(options, Some(ref expr), _)] @@ -336,11 +379,11 @@ fn optimize_stmt_block( && expr.is_pure() => { state.set_dirty(); - statements.pop().expect(">= 2 elements"); + statements.pop().unwrap(); } [.., ref last_stmt] if is_pure(last_stmt) => { state.set_dirty(); - statements.pop().expect("not empty"); + statements.pop().unwrap(); } _ => break, } @@ -382,20 +425,18 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b match x.2 { Expr::FnCall(ref mut x2, _) => { state.set_dirty(); - let op = Token::lookup_from_syntax(&x2.name).expect("operator"); - let op_assignment = op.make_op_assignment().expect("operator"); - x.1 = Some(OpAssignment::new(op_assignment)); + x.1 = Some(OpAssignment::new(&x2.name)); let value = mem::take(&mut x2.args[1]); if let Expr::Stack(slot, pos) = value { - let value = mem::take(x2.constants.get_mut(slot).expect("valid slot")); - x.2 = Expr::from_dynamic(value, pos); + x.2 = + Expr::from_dynamic(mem::take(x2.constants.get_mut(slot).unwrap()), pos); } else { x.2 = value; } } - _ => unreachable!(), + ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr), } } @@ -432,33 +473,33 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // if false { if_block } else { else_block } -> else_block Stmt::If(Expr::BoolConstant(false, _), x, _) => { state.set_dirty(); - let else_block = mem::take(&mut *x.1); - *stmt = match optimize_stmt_block(else_block, state, preserve_result, true, false) { - statements if statements.is_empty() => Stmt::Noop(x.1.position()), - statements => Stmt::Block(statements.into_boxed_slice(), x.1.position()), - } + *stmt = + match optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false) + { + statements if statements.is_empty() => Stmt::Noop(x.1.position()), + statements => Stmt::Block(statements.into_boxed_slice(), x.1.position()), + } } // if true { if_block } else { else_block } -> if_block Stmt::If(Expr::BoolConstant(true, _), x, _) => { state.set_dirty(); - let if_block = mem::take(&mut *x.0); - *stmt = match optimize_stmt_block(if_block, state, preserve_result, true, false) { - statements if statements.is_empty() => Stmt::Noop(x.0.position()), - statements => Stmt::Block(statements.into_boxed_slice(), x.0.position()), - } + *stmt = + match optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false) + { + statements if statements.is_empty() => Stmt::Noop(x.0.position()), + statements => Stmt::Block(statements.into_boxed_slice(), x.0.position()), + } } // if expr { if_block } else { else_block } Stmt::If(condition, x, _) => { optimize_expr(condition, state, false); - let if_block = mem::take(x.0.deref_mut()); - *x.0 = optimize_stmt_block(if_block, state, preserve_result, true, false); - let else_block = mem::take(x.1.deref_mut()); - *x.1 = optimize_stmt_block(else_block, state, preserve_result, true, false); + *x.0 = optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false); + *x.1 = optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false); } // switch const { ... } Stmt::Switch(match_expr, x, pos) if match_expr.is_constant() => { - let value = match_expr.get_literal_value().expect("constant"); + let value = match_expr.get_literal_value().unwrap(); let hasher = &mut get_hasher(); value.hash(hasher); let hash = hasher.finish(); @@ -471,11 +512,6 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def } optimize_expr(&mut condition, state, false); - let def_pos = if x.1.position().is_none() { - *pos - } else { - x.1.position() - }; let def_stmt = optimize_stmt_block(mem::take(&mut *x.1), state, true, true, false); @@ -483,16 +519,16 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b condition, Box::new(( mem::take(&mut block.1), - Stmt::Block(def_stmt.into_boxed_slice(), def_pos).into(), + Stmt::Block(def_stmt.into_boxed_slice(), x.1.position().or_else(*pos)) + .into(), )), match_expr.position(), ); } else { // Promote the matched case - let new_pos = block.1.position(); let statements = optimize_stmt_block(mem::take(&mut *block.1), state, true, true, false); - *stmt = Stmt::Block(statements.into_boxed_slice(), new_pos); + *stmt = Stmt::Block(statements.into_boxed_slice(), block.1.position()); } state.set_dirty(); @@ -519,29 +555,27 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def } optimize_expr(&mut condition, state, false); - let def_block = mem::take(&mut *x.1); - let def_stmt = optimize_stmt_block(def_block, state, true, true, false); - let def_pos = if x.1.position().is_none() { - *pos - } else { - x.1.position() - }; - + let def_stmt = + optimize_stmt_block(mem::take(&mut *x.1), state, true, true, false); *stmt = Stmt::If( condition, Box::new(( mem::take(stmt_block), - Stmt::Block(def_stmt.into_boxed_slice(), def_pos).into(), + Stmt::Block( + def_stmt.into_boxed_slice(), + x.1.position().or_else(*pos), + ) + .into(), )), match_expr.position(), ); } else { // Promote the matched case - let new_pos = stmt_block.position(); let statements = mem::take(&mut **stmt_block); let statements = optimize_stmt_block(statements, state, true, true, false); - *stmt = Stmt::Block(statements.into_boxed_slice(), new_pos); + *stmt = + Stmt::Block(statements.into_boxed_slice(), stmt_block.position()); } state.set_dirty(); @@ -551,10 +585,9 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // Multiple ranges - clear the table and just keep the right ranges if !table.is_empty() { state.set_dirty(); + table.clear(); } - table.clear(); - let old_ranges_len = ranges.len(); ranges.retain(|&mut (start, end, inclusive, _, _)| { @@ -585,19 +618,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // Promote the default case state.set_dirty(); - let def_pos = if x.1.position().is_none() { - *pos - } else { - x.1.position() - }; let def_stmt = optimize_stmt_block(mem::take(&mut *x.1), state, true, true, false); - *stmt = Stmt::Block(def_stmt.into_boxed_slice(), def_pos); + *stmt = Stmt::Block(def_stmt.into_boxed_slice(), x.1.position().or_else(*pos)); } // switch Stmt::Switch(match_expr, x, _) => { optimize_expr(match_expr, state, false); x.0.values_mut().for_each(|block| { - let statements = mem::take(block.1.deref_mut()); + let statements = mem::take(&mut *block.1); *block.1 = optimize_stmt_block(statements, state, preserve_result, true, false); if let Some(mut condition) = mem::take(&mut block.0) { @@ -618,8 +646,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b x.0.remove(&key); } - let def_block = mem::take(x.1.deref_mut()); - *x.1 = optimize_stmt_block(def_block, state, preserve_result, true, false); + *x.1 = optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false); } // while false { block } -> Noop @@ -633,8 +660,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b if let Expr::BoolConstant(true, pos) = condition { *condition = Expr::Unit(*pos); } - let block = mem::take(body.as_mut().deref_mut()); - *body.as_mut().deref_mut() = optimize_stmt_block(block, state, false, true, false); + ***body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false); if body.len() == 1 { match body[0] { @@ -661,24 +687,21 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b if *x == options.contains(AST_OPTION_NEGATED) => { state.set_dirty(); - let block_pos = body.position(); - let block = mem::take(body.as_mut().deref_mut()); *stmt = Stmt::Block( - optimize_stmt_block(block, state, false, true, false).into_boxed_slice(), - block_pos, + optimize_stmt_block(mem::take(&mut **body), state, false, true, false) + .into_boxed_slice(), + body.position(), ); } // do { block } while|until expr Stmt::Do(body, condition, _, _) => { optimize_expr(condition, state, false); - let block = mem::take(body.as_mut().deref_mut()); - *body.as_mut().deref_mut() = optimize_stmt_block(block, state, false, true, false); + ***body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false); } // for id in expr { block } Stmt::For(iterable, x, _) => { optimize_expr(iterable, state, false); - let body = mem::take(x.2.deref_mut()); - *x.2 = optimize_stmt_block(body, state, false, true, false); + *x.2 = optimize_stmt_block(mem::take(&mut *x.2), state, false, true, false); } // let id = expr; Stmt::Var(expr, _, options, _) if !options.contains(AST_OPTION_CONSTANT) => { @@ -697,8 +720,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b state.set_dirty(); *stmt = Stmt::Noop(*pos); } - // Only one statement - promote - [s] => { + // Only one statement which is not block-dependent - promote + [s] if !s.is_block_dependent() => { state.set_dirty(); *stmt = mem::take(s); } @@ -709,19 +732,16 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b Stmt::TryCatch(x, _) if x.0.iter().all(Stmt::is_pure) => { // If try block is pure, there will never be any exceptions state.set_dirty(); - let try_pos = x.0.position(); - let try_block = mem::take(&mut *x.0); *stmt = Stmt::Block( - optimize_stmt_block(try_block, state, false, true, false).into_boxed_slice(), - try_pos, + optimize_stmt_block(mem::take(&mut *x.0), state, false, true, false) + .into_boxed_slice(), + x.0.position(), ); } // try { try_block } catch ( var ) { catch_block } Stmt::TryCatch(x, _) => { - let try_block = mem::take(x.0.deref_mut()); - *x.0 = optimize_stmt_block(try_block, state, false, true, false); - let catch_block = mem::take(x.2.deref_mut()); - *x.2 = optimize_stmt_block(catch_block, state, false, true, false); + *x.0 = optimize_stmt_block(mem::take(&mut *x.0), state, false, true, false); + *x.2 = optimize_stmt_block(mem::take(&mut *x.2), state, false, true, false); } // func(...) Stmt::Expr(expr @ Expr::FnCall(_, _)) => { @@ -742,7 +762,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // {...}; Stmt::Expr(Expr::Stmt(x)) => { state.set_dirty(); - *stmt = mem::take(x.as_mut()).into(); + *stmt = mem::take(&mut **x).into(); } // expr; Stmt::Expr(expr) => optimize_expr(expr, state, false), @@ -770,8 +790,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { Expr::Stmt(x) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.position()) } // { stmt; ... } - do not count promotion as dirty because it gets turned back into an array Expr::Stmt(x) => { - *x.as_mut().deref_mut() = - optimize_stmt_block(mem::take(x.as_mut().deref_mut()), state, true, true, false); + ***x = optimize_stmt_block(mem::take(&mut **x), state, true, true, false); // { Stmt(Expr) } - promote match x.as_mut().as_mut() { @@ -805,9 +824,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { #[cfg(not(feature = "no_index"))] Expr::Index(x, _, _) if !_chaining => match (&mut x.lhs, &mut x.rhs) { // array[int] - (Expr::Array(a, pos), Expr::IntegerConstant(i, _)) - if *i >= 0 && (*i as usize) < a.len() && a.iter().all(Expr::is_pure) => - { + (Expr::Array(a, pos), Expr::IntegerConstant(i, _)) if *i >= 0 && (*i as usize) < a.len() && a.iter().all(Expr::is_pure) => { // Array literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); @@ -816,9 +833,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { *expr = result; } // array[-int] - (Expr::Array(a, pos), Expr::IntegerConstant(i, _)) - if *i < 0 && i.checked_abs().map(|n| n as usize <= a.len()).unwrap_or(false) && a.iter().all(Expr::is_pure) => - { + (Expr::Array(a, pos), Expr::IntegerConstant(i, _)) if *i < 0 && i.checked_abs().map(|n| n as usize <= a.len()).unwrap_or(false) && a.iter().all(Expr::is_pure) => { // Array literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); @@ -852,13 +867,13 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _)) if *i >= 0 && (*i as usize) < s.chars().count() => { // String literal indexing - get the character state.set_dirty(); - *expr = Expr::CharConstant(s.chars().nth(*i as usize).expect("valid index"), *pos); + *expr = Expr::CharConstant(s.chars().nth(*i as usize).unwrap(), *pos); } // string[-int] (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _)) if *i < 0 && i.checked_abs().map(|n| n as usize <= s.chars().count()).unwrap_or(false) => { // String literal indexing - get the character state.set_dirty(); - *expr = Expr::CharConstant(s.chars().rev().nth(i.abs() as usize - 1).expect("valid index"), *pos); + *expr = Expr::CharConstant(s.chars().rev().nth(i.abs() as usize - 1).unwrap(), *pos); } // var[rhs] (Expr::Variable(_, _, _), rhs) => optimize_expr(rhs, state, true), @@ -885,39 +900,14 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { let mut n = 0; // Merge consecutive strings - while n < x.len()-1 { + while n < x.len() - 1 { match (mem::take(&mut x[n]), mem::take(&mut x[n+1])) { - (Expr::StringConstant(mut s1, pos), Expr::StringConstant(s2, _)) => { - s1 += s2; - x[n] = Expr::StringConstant(s1, pos); - x.remove(n+1); - state.set_dirty(); - } - (expr1, Expr::Unit(_)) => { - x[n] = expr1; - x.remove(n+1); - state.set_dirty(); - } - (Expr::Unit(_), expr2) => { - x[n+1] = expr2; - x.remove(n); - state.set_dirty(); - } - (expr1, Expr::StringConstant(s, _)) if s.is_empty() => { - x[n] = expr1; - x.remove(n+1); - state.set_dirty(); - } - (Expr::StringConstant(s, _), expr2) if s.is_empty()=> { - x[n+1] = expr2; - x.remove(n); - state.set_dirty(); - } - (expr1, expr2) => { - x[n] = expr1; - x[n+1] = expr2; - n += 1; - } + (Expr::StringConstant(mut s1, pos), Expr::StringConstant(s2, _)) => { s1 += s2; x[n] = Expr::StringConstant(s1, pos); x.remove(n+1); state.set_dirty(); } + (expr1, Expr::Unit(_)) => { x[n] = expr1; x.remove(n+1); state.set_dirty(); } + (Expr::Unit(_), expr2) => { x[n+1] = expr2; x.remove(n); state.set_dirty(); } + (expr1, Expr::StringConstant(s, _)) if s.is_empty() => { x[n] = expr1; x.remove(n+1); state.set_dirty(); } + (Expr::StringConstant(s, _), expr2) if s.is_empty()=> { x[n+1] = expr2; x.remove(n); state.set_dirty(); } + (expr1, expr2) => { x[n] = expr1; x[n+1] = expr2; n += 1; } } } @@ -927,7 +917,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { #[cfg(not(feature = "no_index"))] Expr::Array(_, _) if expr.is_constant() => { state.set_dirty(); - *expr = Expr::DynamicConstant(expr.get_literal_value().expect("constant").into(), expr.position()); + *expr = Expr::DynamicConstant(expr.get_literal_value().unwrap().into(), expr.position()); } // [ items .. ] #[cfg(not(feature = "no_index"))] @@ -936,7 +926,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { #[cfg(not(feature = "no_object"))] Expr::Map(_, _) if expr.is_constant() => { state.set_dirty(); - *expr = Expr::DynamicConstant(expr.get_literal_value().expect("constant").into(), expr.position()); + *expr = Expr::DynamicConstant(expr.get_literal_value().unwrap().into(), expr.position()); } // #{ key:value, .. } #[cfg(not(feature = "no_object"))] @@ -944,52 +934,24 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { // lhs && rhs Expr::And(x, _) => match (&mut x.lhs, &mut x.rhs) { // true && rhs -> rhs - (Expr::BoolConstant(true, _), rhs) => { - state.set_dirty(); - optimize_expr(rhs, state, false); - *expr = mem::take(rhs); - } + (Expr::BoolConstant(true, _), rhs) => { state.set_dirty(); optimize_expr(rhs, state, false); *expr = mem::take(rhs); } // false && rhs -> false - (Expr::BoolConstant(false, pos), _) => { - state.set_dirty(); - *expr = Expr::BoolConstant(false, *pos); - } + (Expr::BoolConstant(false, pos), _) => { state.set_dirty(); *expr = Expr::BoolConstant(false, *pos); } // lhs && true -> lhs - (lhs, Expr::BoolConstant(true, _)) => { - state.set_dirty(); - optimize_expr(lhs, state, false); - *expr = mem::take(lhs); - } + (lhs, Expr::BoolConstant(true, _)) => { state.set_dirty(); optimize_expr(lhs, state, false); *expr = mem::take(lhs); } // lhs && rhs - (lhs, rhs) => { - optimize_expr(lhs, state, false); - optimize_expr(rhs, state, false); - } + (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); } }, // lhs || rhs Expr::Or(ref mut x, _) => match (&mut x.lhs, &mut x.rhs) { // false || rhs -> rhs - (Expr::BoolConstant(false, _), rhs) => { - state.set_dirty(); - optimize_expr(rhs, state, false); - *expr = mem::take(rhs); - } + (Expr::BoolConstant(false, _), rhs) => { state.set_dirty(); optimize_expr(rhs, state, false); *expr = mem::take(rhs); } // true || rhs -> true - (Expr::BoolConstant(true, pos), _) => { - state.set_dirty(); - *expr = Expr::BoolConstant(true, *pos); - } + (Expr::BoolConstant(true, pos), _) => { state.set_dirty(); *expr = Expr::BoolConstant(true, *pos); } // lhs || false - (lhs, Expr::BoolConstant(false, _)) => { - state.set_dirty(); - optimize_expr(lhs, state, false); - *expr = mem::take(lhs); - } + (lhs, Expr::BoolConstant(false, _)) => { state.set_dirty(); optimize_expr(lhs, state, false); *expr = mem::take(lhs); } // lhs || rhs - (lhs, rhs) => { - optimize_expr(lhs, state, false); - optimize_expr(rhs, state, false); - } + (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); } }, // eval! @@ -1032,7 +994,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { => { let arg_values = &mut x.args.iter().map(|e| match e { Expr::Stack(slot, _) => x.constants[*slot].clone(), - _ => e.get_literal_value().expect("constant") + _ => e.get_literal_value().unwrap() }).collect::>(); let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); @@ -1050,7 +1012,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { return; } // Overloaded operators can override built-in. - _ if x.args.len() == 2 && !state.engine.has_native_fn_override(x.hashes.native, arg_types.as_ref()) => { + _ if x.args.len() == 2 && !has_native_fn_override(state.engine, x.hashes.native, arg_types.as_ref()) => { if let Some(result) = get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1]) .and_then(|f| { #[cfg(not(feature = "no_function"))] @@ -1059,7 +1021,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { let lib = &[]; let context = (state.engine, x.name.as_str(), lib).into(); - let (first, second) = arg_values.split_first_mut().expect("not empty"); + let (first, second) = arg_values.split_first_mut().unwrap(); (f)(context, &mut [ first, &mut second[0] ]).ok() }) { state.set_dirty(); @@ -1098,7 +1060,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { if !has_script_fn { let arg_values = &mut x.args.iter().map(|e| match e { Expr::Stack(slot, _) => x.constants[*slot].clone(), - _ => e.get_literal_value().expect("constant") + _ => e.get_literal_value().unwrap() }).collect::>(); let result = match x.name.as_str() { @@ -1133,7 +1095,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { // constant-name Expr::Variable(_, pos, x) if x.1.is_none() && state.find_constant(&x.2).is_some() => { // Replace constant with value - *expr = Expr::from_dynamic(state.find_constant(&x.2).expect("exists").clone(), *pos); + *expr = Expr::from_dynamic(state.find_constant(&x.2).unwrap().clone(), *pos); state.set_dirty(); } @@ -1218,7 +1180,7 @@ pub fn optimize_into_ast( params: fn_def.params.clone(), lib: None, #[cfg(not(feature = "no_module"))] - mods: Imports::new(), + global: None, #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] comments: None, @@ -1235,7 +1197,7 @@ pub fn optimize_into_ast( let mut fn_def = crate::func::native::shared_take_or_clone(fn_def); // Optimize the function body - let body = mem::take(fn_def.body.deref_mut()); + let body = mem::take(&mut *fn_def.body); *fn_def.body = optimize_top_level(body, engine, scope, lib2, optimization_level); diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 6de5634b..b908cb49 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] use crate::plugin::*; -use crate::{def_package, EvalAltResult, Position, INT}; +use crate::{def_package, Position, RhaiError, RhaiResultOf, ERR, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -9,9 +9,9 @@ use std::prelude::v1::*; #[cfg(not(feature = "no_float"))] use num_traits::Float; -#[inline(never)] -pub fn make_err(msg: impl Into) -> Box { - EvalAltResult::ErrorArithmetic(msg.into(), Position::NONE).into() +#[inline] +pub fn make_err(msg: impl Into) -> RhaiError { + ERR::ErrorArithmetic(msg.into(), Position::NONE).into() } macro_rules! gen_arithmetic_functions { @@ -22,7 +22,7 @@ macro_rules! gen_arithmetic_functions { #[export_module] pub mod functions { #[rhai_fn(name = "+", return_raw)] - pub fn add(x: $arg_type, y: $arg_type) -> Result<$arg_type, Box> { + pub fn add(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_add(y).ok_or_else(|| make_err(format!("Addition overflow: {} + {}", x, y))) } else { @@ -30,7 +30,7 @@ macro_rules! gen_arithmetic_functions { } } #[rhai_fn(name = "-", return_raw)] - pub fn subtract(x: $arg_type, y: $arg_type) -> Result<$arg_type, Box> { + pub fn subtract(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_sub(y).ok_or_else(|| make_err(format!("Subtraction overflow: {} - {}", x, y))) } else { @@ -38,7 +38,7 @@ macro_rules! gen_arithmetic_functions { } } #[rhai_fn(name = "*", return_raw)] - pub fn multiply(x: $arg_type, y: $arg_type) -> Result<$arg_type, Box> { + pub fn multiply(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_mul(y).ok_or_else(|| make_err(format!("Multiplication overflow: {} * {}", x, y))) } else { @@ -46,7 +46,7 @@ macro_rules! gen_arithmetic_functions { } } #[rhai_fn(name = "/", return_raw)] - pub fn divide(x: $arg_type, y: $arg_type) -> Result<$arg_type, Box> { + pub fn divide(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { // Detect division by zero if y == 0 { @@ -59,7 +59,7 @@ macro_rules! gen_arithmetic_functions { } } #[rhai_fn(name = "%", return_raw)] - pub fn modulo(x: $arg_type, y: $arg_type) -> Result<$arg_type, Box> { + pub fn modulo(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_rem(y).ok_or_else(|| make_err(format!("Modulo division by zero or overflow: {} % {}", x, y))) } else { @@ -67,7 +67,7 @@ macro_rules! gen_arithmetic_functions { } } #[rhai_fn(name = "**", return_raw)] - pub fn power(x: $arg_type, y: INT) -> Result<$arg_type, Box> { + pub fn power(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { Err(make_err(format!("Integer raised to too large an index: {} ~ {}", x, y))) @@ -82,7 +82,7 @@ macro_rules! gen_arithmetic_functions { } #[rhai_fn(name = "<<", return_raw)] - pub fn shift_left(x: $arg_type, y: INT) -> Result<$arg_type, Box> { + pub fn shift_left(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { Err(make_err(format!("Left-shift by too many bits: {} << {}", x, y))) @@ -96,7 +96,7 @@ macro_rules! gen_arithmetic_functions { } } #[rhai_fn(name = ">>", return_raw)] - pub fn shift_right(x: $arg_type, y: INT) -> Result<$arg_type, Box> { + pub fn shift_right(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { Err(make_err(format!("Right-shift by too many bits: {} >> {}", x, y))) @@ -146,7 +146,7 @@ macro_rules! gen_signed_functions { #[export_module] pub mod functions { #[rhai_fn(name = "-", return_raw)] - pub fn neg(x: $arg_type) -> Result<$arg_type, Box> { + pub fn neg(x: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_neg().ok_or_else(|| make_err(format!("Negation overflow: -{}", x))) } else { @@ -158,7 +158,7 @@ macro_rules! gen_signed_functions { x } #[rhai_fn(return_raw)] - pub fn abs(x: $arg_type) -> Result<$arg_type, Box> { + pub fn abs(x: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_abs().ok_or_else(|| make_err(format!("Negation overflow: -{}", x))) } else { @@ -179,36 +179,40 @@ macro_rules! reg_functions { )* } } -def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { - lib.standard = true; +def_package! { + /// Basic arithmetic package. + crate::ArithmeticPackage => |lib| { + lib.standard = true; - combine_with_exported_module!(lib, "int", int_functions); - reg_functions!(lib += signed_basic; INT); + combine_with_exported_module!(lib, "int", int_functions); + reg_functions!(lib += signed_basic; INT); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - reg_functions!(lib += arith_numbers; i8, u8, i16, u16, i32, u32, u64); - reg_functions!(lib += signed_numbers; i8, i16, i32); - - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] { - reg_functions!(lib += arith_num_128; i128, u128); - reg_functions!(lib += signed_num_128; i128); + reg_functions!(lib += arith_numbers; i8, u8, i16, u16, i32, u32, u64); + reg_functions!(lib += signed_numbers; i8, i16, i32); + + #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_arch = "wasm64"))] + { + reg_functions!(lib += arith_num_128; i128, u128); + reg_functions!(lib += signed_num_128; i128); + } } - } - // Basic arithmetic for floating-point - #[cfg(not(feature = "no_float"))] - { - combine_with_exported_module!(lib, "f32", f32_functions); - combine_with_exported_module!(lib, "f64", f64_functions); - } + // Basic arithmetic for floating-point + #[cfg(not(feature = "no_float"))] + { + combine_with_exported_module!(lib, "f32", f32_functions); + combine_with_exported_module!(lib, "f64", f64_functions); + } - // Decimal functions - #[cfg(feature = "decimal")] - combine_with_exported_module!(lib, "decimal", decimal_functions); -}); + // Decimal functions + #[cfg(feature = "decimal")] + combine_with_exported_module!(lib, "decimal", decimal_functions); + } +} #[export_module] mod int_functions { @@ -234,7 +238,8 @@ gen_arithmetic_functions!(arith_numbers => i8, u8, i16, u16, i32, u32, u64); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] -#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_arch = "wasm64"))] gen_arithmetic_functions!(arith_num_128 => i128, u128); gen_signed_functions!(signed_basic => INT); @@ -245,14 +250,13 @@ gen_signed_functions!(signed_numbers => i8, i16, i32); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] -#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_arch = "wasm64"))] gen_signed_functions!(signed_num_128 => i128); #[cfg(not(feature = "no_float"))] #[export_module] mod f32_functions { - use crate::EvalAltResult; - #[cfg(not(feature = "f32_float"))] pub mod basic_arithmetic { #[rhai_fn(name = "+")] @@ -334,7 +338,7 @@ mod f32_functions { x.abs() } #[rhai_fn(return_raw)] - pub fn sign(x: f32) -> Result> { + pub fn sign(x: f32) -> RhaiResultOf { match x.signum() { _ if x == 0.0 => Ok(0), x if x.is_nan() => Err(make_err("Sign of NaN is undefined")), @@ -346,7 +350,7 @@ mod f32_functions { x == 0.0 } #[rhai_fn(name = "**", return_raw)] - pub fn pow_f_i(x: f32, y: INT) -> Result> { + pub fn pow_f_i(x: f32, y: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { Err(make_err(format!( "Number raised to too large an index: {} ~ {}", @@ -442,7 +446,7 @@ mod f64_functions { x.abs() } #[rhai_fn(return_raw)] - pub fn sign(x: f64) -> Result> { + pub fn sign(x: f64) -> RhaiResultOf { match x.signum() { _ if x == 0.0 => Ok(0), x if x.is_nan() => Err(make_err("Sign of NaN is undefined")), @@ -462,7 +466,7 @@ pub mod decimal_functions { use rust_decimal::{prelude::Zero, Decimal, MathematicalOps}; #[rhai_fn(skip, return_raw)] - pub fn add(x: Decimal, y: Decimal) -> Result> { + pub fn add(x: Decimal, y: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_add(y) .ok_or_else(|| make_err(format!("Addition overflow: {} + {}", x, y))) @@ -471,7 +475,7 @@ pub mod decimal_functions { } } #[rhai_fn(skip, return_raw)] - pub fn subtract(x: Decimal, y: Decimal) -> Result> { + pub fn subtract(x: Decimal, y: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_sub(y) .ok_or_else(|| make_err(format!("Subtraction overflow: {} - {}", x, y))) @@ -480,7 +484,7 @@ pub mod decimal_functions { } } #[rhai_fn(skip, return_raw)] - pub fn multiply(x: Decimal, y: Decimal) -> Result> { + pub fn multiply(x: Decimal, y: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_mul(y) .ok_or_else(|| make_err(format!("Multiplication overflow: {} * {}", x, y))) @@ -489,7 +493,7 @@ pub mod decimal_functions { } } #[rhai_fn(skip, return_raw)] - pub fn divide(x: Decimal, y: Decimal) -> Result> { + pub fn divide(x: Decimal, y: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { // Detect division by zero if y == Decimal::zero() { @@ -503,7 +507,7 @@ pub mod decimal_functions { } } #[rhai_fn(skip, return_raw)] - pub fn modulo(x: Decimal, y: Decimal) -> Result> { + pub fn modulo(x: Decimal, y: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_rem(y).ok_or_else(|| { make_err(format!( @@ -516,7 +520,7 @@ pub mod decimal_functions { } } #[rhai_fn(skip, return_raw)] - pub fn power(x: Decimal, y: Decimal) -> Result> { + pub fn power(x: Decimal, y: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_powd(y) .ok_or_else(|| make_err(format!("Exponential overflow: {} + {}", x, y))) diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 9ceeda20..c2ad47af 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -4,52 +4,56 @@ use crate::engine::OP_EQUALS; use crate::plugin::*; use crate::{ - def_package, Array, Dynamic, EvalAltResult, ExclusiveRange, FnPtr, InclusiveRange, - NativeCallContext, Position, INT, + def_package, Array, Dynamic, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext, + Position, RhaiResultOf, ERR, INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{any::TypeId, cmp::Ordering, mem}; -def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { - lib.standard = true; +def_package! { + /// Package of basic array utilities. + crate::BasicArrayPackage => |lib| { + lib.standard = true; - combine_with_exported_module!(lib, "array", array_functions); + combine_with_exported_module!(lib, "array", array_functions); - // Register array iterator - lib.set_iterable::(); -}); + // Register array iterator + lib.set_iterable::(); + } +} #[export_module] -mod array_functions { +pub mod array_functions { #[rhai_fn(name = "len", get = "len", pure)] pub fn len(array: &mut Array) -> INT { array.len() as INT } - #[rhai_fn(name = "push", name = "+=")] pub fn push(array: &mut Array, item: Dynamic) { array.push(item); } - #[rhai_fn(name = "append", name = "+=")] - pub fn append(array: &mut Array, y: Array) { - if !y.is_empty() { - if array.is_empty() { - *array = y; + pub fn append(array1: &mut Array, array2: Array) { + if !array2.is_empty() { + if array1.is_empty() { + *array1 = array2; } else { - array.extend(y); + array1.extend(array2); } } } #[rhai_fn(name = "+")] - pub fn concat(mut array: Array, y: Array) -> Array { - if !y.is_empty() { - if array.is_empty() { - array = y; + pub fn concat(array1: Array, array2: Array) -> Array { + if !array2.is_empty() { + if array1.is_empty() { + array2 } else { - array.extend(y); + let mut array = array1; + array.extend(array2); + array } + } else { + array1 } - array } pub fn insert(array: &mut Array, position: INT, item: Dynamic) { if array.is_empty() { @@ -76,25 +80,45 @@ mod array_functions { array: &mut Array, len: INT, item: Dynamic, - ) -> Result<(), Box> { - if len <= 0 { + ) -> RhaiResultOf<()> { + if len <= 0 || (len as usize) <= array.len() { return Ok(()); } let _ctx = ctx; + let len = len as usize; // Check if array will be over max size limit #[cfg(not(feature = "unchecked"))] - if _ctx.engine().max_array_size() > 0 && (len as usize) > _ctx.engine().max_array_size() { - return Err(EvalAltResult::ErrorDataTooLarge( - "Size of array".to_string(), - Position::NONE, - ) - .into()); + if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() { + return Err(ERR::ErrorDataTooLarge("Size of array".to_string(), Position::NONE).into()); } - if len as usize > array.len() { - array.resize(len as usize, item); + #[cfg(not(feature = "unchecked"))] + let check_sizes = match item.0 { + crate::types::dynamic::Union::Array(_, _, _) + | crate::types::dynamic::Union::Str(_, _, _) => true, + #[cfg(not(feature = "no_object"))] + crate::types::dynamic::Union::Map(_, _, _) => true, + _ => false, + }; + #[cfg(feature = "unchecked")] + let check_sizes = false; + + if check_sizes { + let arr = mem::take(array); + let mut arr = Dynamic::from_array(arr); + + while array.len() < len { + arr.write_lock::().unwrap().push(item.clone()); + + #[cfg(not(feature = "unchecked"))] + _ctx.engine().ensure_data_size_within_limits(&arr)?; + } + + *array = arr.into_array().unwrap(); + } else { + array.resize(len, item); } Ok(()) @@ -170,9 +194,9 @@ mod array_functions { let arr_len = array.len(); start .checked_abs() - .map_or(0, |n| arr_len - (n as usize).min(arr_len)) + .map_or(0, |n| arr_len - usize::min(n as usize, arr_len)) } else if start as usize >= array.len() { - array.extend(replace.into_iter()); + array.extend(replace); return; } else { start as usize @@ -209,7 +233,7 @@ mod array_functions { let arr_len = array.len(); start .checked_abs() - .map_or(0, |n| arr_len - (n as usize).min(arr_len)) + .map_or(0, |n| arr_len - usize::min(n as usize, arr_len)) } else if start as usize >= array.len() { return Array::new(); } else { @@ -259,11 +283,7 @@ mod array_functions { } } #[rhai_fn(return_raw, pure)] - pub fn map( - ctx: NativeCallContext, - array: &mut Array, - mapper: FnPtr, - ) -> Result> { + pub fn map(ctx: NativeCallContext, array: &mut Array, mapper: FnPtr) -> RhaiResultOf { if array.is_empty() { return Ok(array.clone()); } @@ -275,7 +295,7 @@ mod array_functions { mapper .call_raw(&ctx, None, [item.clone()]) .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + ERR::ErrorFunctionNotFound(fn_sig, _) if fn_sig.starts_with(mapper.fn_name()) => { mapper.call_raw(&ctx, None, [item.clone(), (i as INT).into()]) @@ -283,7 +303,7 @@ mod array_functions { _ => Err(err), }) .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( + Box::new(ERR::ErrorInFunctionCall( "map".to_string(), ctx.source().unwrap_or("").to_string(), err, @@ -300,16 +320,12 @@ mod array_functions { ctx: NativeCallContext, array: &mut Array, mapper: &str, - ) -> Result> { + ) -> RhaiResultOf { map(ctx, array, FnPtr::new(mapper)?) } #[rhai_fn(return_raw, pure)] - pub fn filter( - ctx: NativeCallContext, - array: &mut Array, - filter: FnPtr, - ) -> Result> { + pub fn filter(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { if array.is_empty() { return Ok(array.clone()); } @@ -320,7 +336,7 @@ mod array_functions { if filter .call_raw(&ctx, None, [item.clone()]) .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + ERR::ErrorFunctionNotFound(fn_sig, _) if fn_sig.starts_with(filter.fn_name()) => { filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()]) @@ -328,7 +344,7 @@ mod array_functions { _ => Err(err), }) .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( + Box::new(ERR::ErrorInFunctionCall( "filter".to_string(), ctx.source().unwrap_or("").to_string(), err, @@ -349,7 +365,7 @@ mod array_functions { ctx: NativeCallContext, array: &mut Array, filter_func: &str, - ) -> Result> { + ) -> RhaiResultOf { filter(ctx, array, FnPtr::new(filter_func)?) } #[rhai_fn(return_raw, pure)] @@ -357,7 +373,7 @@ mod array_functions { ctx: NativeCallContext, array: &mut Array, value: Dynamic, - ) -> Result> { + ) -> RhaiResultOf { if array.is_empty() { return Ok(false); } @@ -366,9 +382,7 @@ mod array_functions { if ctx .call_fn_raw(OP_EQUALS, true, false, &mut [item, &mut value.clone()]) .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _) - if fn_sig.starts_with(OP_EQUALS) => - { + ERR::ErrorFunctionNotFound(ref fn_sig, _) if fn_sig.starts_with(OP_EQUALS) => { if item.type_id() == value.type_id() { // No default when comparing same type Err(err) @@ -392,7 +406,7 @@ mod array_functions { ctx: NativeCallContext, array: &mut Array, value: Dynamic, - ) -> Result> { + ) -> RhaiResultOf { if array.is_empty() { Ok(-1) } else { @@ -405,7 +419,7 @@ mod array_functions { array: &mut Array, value: Dynamic, start: INT, - ) -> Result> { + ) -> RhaiResultOf { if array.is_empty() { return Ok(-1); } @@ -414,7 +428,7 @@ mod array_functions { let arr_len = array.len(); start .checked_abs() - .map_or(0, |n| arr_len - (n as usize).min(arr_len)) + .map_or(0, |n| arr_len - usize::min(n as usize, arr_len)) } else if start as usize >= array.len() { return Ok(-1); } else { @@ -425,9 +439,7 @@ mod array_functions { if ctx .call_fn_raw(OP_EQUALS, true, false, &mut [item, &mut value.clone()]) .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _) - if fn_sig.starts_with(OP_EQUALS) => - { + ERR::ErrorFunctionNotFound(ref fn_sig, _) if fn_sig.starts_with(OP_EQUALS) => { if item.type_id() == value.type_id() { // No default when comparing same type Err(err) @@ -451,7 +463,7 @@ mod array_functions { ctx: NativeCallContext, array: &mut Array, filter: &str, - ) -> Result> { + ) -> RhaiResultOf { index_of_filter(ctx, array, FnPtr::new(filter)?) } #[rhai_fn(name = "index_of", return_raw, pure)] @@ -459,7 +471,7 @@ mod array_functions { ctx: NativeCallContext, array: &mut Array, filter: FnPtr, - ) -> Result> { + ) -> RhaiResultOf { if array.is_empty() { Ok(-1) } else { @@ -472,7 +484,7 @@ mod array_functions { array: &mut Array, filter: FnPtr, start: INT, - ) -> Result> { + ) -> RhaiResultOf { if array.is_empty() { return Ok(-1); } @@ -481,7 +493,7 @@ mod array_functions { let arr_len = array.len(); start .checked_abs() - .map_or(0, |n| arr_len - (n as usize).min(arr_len)) + .map_or(0, |n| arr_len - usize::min(n as usize, arr_len)) } else if start as usize >= array.len() { return Ok(-1); } else { @@ -492,7 +504,7 @@ mod array_functions { if filter .call_raw(&ctx, None, [item.clone()]) .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + ERR::ErrorFunctionNotFound(fn_sig, _) if fn_sig.starts_with(filter.fn_name()) => { filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()]) @@ -500,7 +512,7 @@ mod array_functions { _ => Err(err), }) .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( + Box::new(ERR::ErrorInFunctionCall( "index_of".to_string(), ctx.source().unwrap_or("").to_string(), err, @@ -522,15 +534,11 @@ mod array_functions { array: &mut Array, filter: &str, start: INT, - ) -> Result> { + ) -> RhaiResultOf { index_of_filter_starting_from(ctx, array, FnPtr::new(filter)?, start) } #[rhai_fn(return_raw, pure)] - pub fn some( - ctx: NativeCallContext, - array: &mut Array, - filter: FnPtr, - ) -> Result> { + pub fn some(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { if array.is_empty() { return Ok(false); } @@ -539,7 +547,7 @@ mod array_functions { if filter .call_raw(&ctx, None, [item.clone()]) .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + ERR::ErrorFunctionNotFound(fn_sig, _) if fn_sig.starts_with(filter.fn_name()) => { filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()]) @@ -547,7 +555,7 @@ mod array_functions { _ => Err(err), }) .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( + Box::new(ERR::ErrorInFunctionCall( "some".to_string(), ctx.source().unwrap_or("").to_string(), err, @@ -568,15 +576,11 @@ mod array_functions { ctx: NativeCallContext, array: &mut Array, filter: &str, - ) -> Result> { + ) -> RhaiResultOf { some(ctx, array, FnPtr::new(filter)?) } #[rhai_fn(return_raw, pure)] - pub fn all( - ctx: NativeCallContext, - array: &mut Array, - filter: FnPtr, - ) -> Result> { + pub fn all(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { if array.is_empty() { return Ok(true); } @@ -585,7 +589,7 @@ mod array_functions { if !filter .call_raw(&ctx, None, [item.clone()]) .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + ERR::ErrorFunctionNotFound(fn_sig, _) if fn_sig.starts_with(filter.fn_name()) => { filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()]) @@ -593,7 +597,7 @@ mod array_functions { _ => Err(err), }) .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( + Box::new(ERR::ErrorInFunctionCall( "all".to_string(), ctx.source().unwrap_or("").to_string(), err, @@ -614,11 +618,11 @@ mod array_functions { ctx: NativeCallContext, array: &mut Array, filter: &str, - ) -> Result> { + ) -> RhaiResultOf { all(ctx, array, FnPtr::new(filter)?) } #[rhai_fn(return_raw)] - pub fn dedup(ctx: NativeCallContext, array: &mut Array) -> Result<(), Box> { + pub fn dedup(ctx: NativeCallContext, array: &mut Array) -> RhaiResultOf<()> { dedup_with_fn_name(ctx, array, OP_EQUALS) } #[rhai_fn(name = "dedup", return_raw)] @@ -626,7 +630,7 @@ mod array_functions { ctx: NativeCallContext, array: &mut Array, comparer: FnPtr, - ) -> Result<(), Box> { + ) -> RhaiResultOf<()> { if array.is_empty() { return Ok(()); } @@ -646,15 +650,11 @@ mod array_functions { ctx: NativeCallContext, array: &mut Array, comparer: &str, - ) -> Result<(), Box> { + ) -> RhaiResultOf<()> { dedup_by_comparer(ctx, array, FnPtr::new(comparer)?) } #[rhai_fn(return_raw, pure)] - pub fn reduce( - ctx: NativeCallContext, - array: &mut Array, - reducer: FnPtr, - ) -> Result> { + pub fn reduce(ctx: NativeCallContext, array: &mut Array, reducer: FnPtr) -> RhaiResult { reduce_with_initial(ctx, array, reducer, Dynamic::UNIT) } #[rhai_fn(name = "reduce", return_raw, pure)] @@ -662,7 +662,7 @@ mod array_functions { ctx: NativeCallContext, array: &mut Array, reducer: &str, - ) -> Result> { + ) -> RhaiResult { reduce(ctx, array, FnPtr::new(reducer)?) } #[rhai_fn(name = "reduce", return_raw, pure)] @@ -671,7 +671,7 @@ mod array_functions { array: &mut Array, reducer: FnPtr, initial: Dynamic, - ) -> Result> { + ) -> RhaiResult { if array.is_empty() { return Ok(initial); } @@ -684,7 +684,7 @@ mod array_functions { result = reducer .call_raw(&ctx, None, [result.clone(), item.clone()]) .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + ERR::ErrorFunctionNotFound(fn_sig, _) if fn_sig.starts_with(reducer.fn_name()) => { reducer.call_raw(&ctx, None, [result, item, (i as INT).into()]) @@ -692,7 +692,7 @@ mod array_functions { _ => Err(err), }) .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( + Box::new(ERR::ErrorInFunctionCall( "reduce".to_string(), ctx.source().unwrap_or("").to_string(), err, @@ -709,15 +709,11 @@ mod array_functions { array: &mut Array, reducer: &str, initial: Dynamic, - ) -> Result> { + ) -> RhaiResult { reduce_with_initial(ctx, array, FnPtr::new(reducer)?, initial) } #[rhai_fn(return_raw, pure)] - pub fn reduce_rev( - ctx: NativeCallContext, - array: &mut Array, - reducer: FnPtr, - ) -> Result> { + pub fn reduce_rev(ctx: NativeCallContext, array: &mut Array, reducer: FnPtr) -> RhaiResult { reduce_rev_with_initial(ctx, array, reducer, Dynamic::UNIT) } #[rhai_fn(name = "reduce_rev", return_raw, pure)] @@ -725,7 +721,7 @@ mod array_functions { ctx: NativeCallContext, array: &mut Array, reducer: &str, - ) -> Result> { + ) -> RhaiResult { reduce_rev(ctx, array, FnPtr::new(reducer)?) } #[rhai_fn(name = "reduce_rev", return_raw, pure)] @@ -734,7 +730,7 @@ mod array_functions { array: &mut Array, reducer: FnPtr, initial: Dynamic, - ) -> Result> { + ) -> RhaiResult { if array.is_empty() { return Ok(initial); } @@ -748,7 +744,7 @@ mod array_functions { result = reducer .call_raw(&ctx, None, [result.clone(), item.clone()]) .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + ERR::ErrorFunctionNotFound(fn_sig, _) if fn_sig.starts_with(reducer.fn_name()) => { reducer.call_raw(&ctx, None, [result, item, ((len - 1 - i) as INT).into()]) @@ -756,7 +752,7 @@ mod array_functions { _ => Err(err), }) .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( + Box::new(ERR::ErrorInFunctionCall( "reduce_rev".to_string(), ctx.source().unwrap_or("").to_string(), err, @@ -773,7 +769,7 @@ mod array_functions { array: &mut Array, reducer: &str, initial: Dynamic, - ) -> Result> { + ) -> RhaiResult { reduce_rev_with_initial(ctx, array, FnPtr::new(reducer)?, initial) } #[rhai_fn(name = "sort", return_raw)] @@ -781,15 +777,11 @@ mod array_functions { ctx: NativeCallContext, array: &mut Array, comparer: &str, - ) -> Result<(), Box> { + ) -> RhaiResultOf<()> { sort(ctx, array, FnPtr::new(comparer)?) } #[rhai_fn(return_raw)] - pub fn sort( - ctx: NativeCallContext, - array: &mut Array, - comparer: FnPtr, - ) -> Result<(), Box> { + pub fn sort(ctx: NativeCallContext, array: &mut Array, comparer: FnPtr) -> RhaiResultOf<()> { if array.len() <= 1 { return Ok(()); } @@ -803,7 +795,7 @@ mod array_functions { v if v > 0 => Ordering::Greater, v if v < 0 => Ordering::Less, 0 => Ordering::Equal, - _ => unreachable!(), + _ => unreachable!("v is {}", v), }) .unwrap_or_else(|| x.type_id().cmp(&y.type_id())) }); @@ -811,7 +803,7 @@ mod array_functions { Ok(()) } #[rhai_fn(name = "sort", return_raw)] - pub fn sort_with_builtin(array: &mut Array) -> Result<(), Box> { + pub fn sort_with_builtin(array: &mut Array) -> RhaiResultOf<()> { if array.len() <= 1 { return Ok(()); } @@ -819,7 +811,7 @@ mod array_functions { let type_id = array[0].type_id(); if array.iter().any(|a| a.type_id() != type_id) { - return Err(EvalAltResult::ErrorFunctionNotFound( + return Err(ERR::ErrorFunctionNotFound( "sort() cannot be called with elements of different types".into(), Position::NONE, ) @@ -883,11 +875,7 @@ mod array_functions { Ok(()) } #[rhai_fn(return_raw)] - pub fn drain( - ctx: NativeCallContext, - array: &mut Array, - filter: FnPtr, - ) -> Result> { + pub fn drain(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { if array.is_empty() { return Ok(Array::new()); } @@ -901,7 +889,7 @@ mod array_functions { if filter .call_raw(&ctx, None, [array[x].clone()]) .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + ERR::ErrorFunctionNotFound(fn_sig, _) if fn_sig.starts_with(filter.fn_name()) => { filter.call_raw(&ctx, None, [array[x].clone(), (i as INT).into()]) @@ -909,7 +897,7 @@ mod array_functions { _ => Err(err), }) .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( + Box::new(ERR::ErrorInFunctionCall( "drain".to_string(), ctx.source().unwrap_or("").to_string(), err, @@ -934,7 +922,7 @@ mod array_functions { ctx: NativeCallContext, array: &mut Array, filter: &str, - ) -> Result> { + ) -> RhaiResultOf { drain(ctx, array, FnPtr::new(filter)?) } #[rhai_fn(name = "drain")] @@ -959,7 +947,7 @@ mod array_functions { let arr_len = array.len(); start .checked_abs() - .map_or(0, |n| arr_len - (n as usize).min(arr_len)) + .map_or(0, |n| arr_len - usize::min(n as usize, arr_len)) } else if start as usize >= array.len() { return Array::new(); } else { @@ -977,11 +965,7 @@ mod array_functions { array.drain(start..start + len).collect() } #[rhai_fn(return_raw)] - pub fn retain( - ctx: NativeCallContext, - array: &mut Array, - filter: FnPtr, - ) -> Result> { + pub fn retain(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { if array.is_empty() { return Ok(Array::new()); } @@ -995,7 +979,7 @@ mod array_functions { if !filter .call_raw(&ctx, None, [array[x].clone()]) .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + ERR::ErrorFunctionNotFound(fn_sig, _) if fn_sig.starts_with(filter.fn_name()) => { filter.call_raw(&ctx, None, [array[x].clone(), (i as INT).into()]) @@ -1003,7 +987,7 @@ mod array_functions { _ => Err(err), }) .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( + Box::new(ERR::ErrorInFunctionCall( "retain".to_string(), ctx.source().unwrap_or("").to_string(), err, @@ -1028,7 +1012,7 @@ mod array_functions { ctx: NativeCallContext, array: &mut Array, filter: &str, - ) -> Result> { + ) -> RhaiResultOf { retain(ctx, array, FnPtr::new(filter)?) } #[rhai_fn(name = "retain")] @@ -1053,7 +1037,7 @@ mod array_functions { let arr_len = array.len(); start .checked_abs() - .map_or(0, |n| arr_len - (n as usize).min(arr_len)) + .map_or(0, |n| arr_len - usize::min(n as usize, arr_len)) } else if start as usize >= array.len() { return mem::take(array); } else { @@ -1074,11 +1058,7 @@ mod array_functions { drained } #[rhai_fn(name = "==", return_raw, pure)] - pub fn equals( - ctx: NativeCallContext, - array1: &mut Array, - array2: Array, - ) -> Result> { + pub fn equals(ctx: NativeCallContext, array1: &mut Array, array2: Array) -> RhaiResultOf { if array1.len() != array2.len() { return Ok(false); } @@ -1092,9 +1072,7 @@ mod array_functions { if !ctx .call_fn_raw(OP_EQUALS, true, false, &mut [a1, a2]) .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _) - if fn_sig.starts_with(OP_EQUALS) => - { + ERR::ErrorFunctionNotFound(ref fn_sig, _) if fn_sig.starts_with(OP_EQUALS) => { if a1.type_id() == a2.type_id() { // No default when comparing same type Err(err) @@ -1118,7 +1096,7 @@ mod array_functions { ctx: NativeCallContext, array1: &mut Array, array2: Array, - ) -> Result> { + ) -> RhaiResultOf { equals(ctx, array1, array2).map(|r| !r) } } diff --git a/src/packages/bit_field.rs b/src/packages/bit_field.rs new file mode 100644 index 00000000..57155f68 --- /dev/null +++ b/src/packages/bit_field.rs @@ -0,0 +1,198 @@ +#![allow(non_snake_case)] + +use crate::plugin::*; +use crate::{def_package, ExclusiveRange, InclusiveRange, Position, RhaiResultOf, ERR, INT}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +def_package! { + /// Package of basic bit-field utilities. + crate::BitFieldPackage => |lib| { + lib.standard = true; + + combine_with_exported_module!(lib, "bit_field", bit_field_functions); + } +} + +#[export_module] +mod bit_field_functions { + const BITS: usize = std::mem::size_of::() * 8; + + #[rhai_fn(return_raw)] + pub fn get_bit(value: INT, index: INT) -> RhaiResultOf { + if index >= 0 { + let offset = index as usize; + + if offset >= BITS { + Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) + } else { + Ok((value & (1 << offset)) != 0) + } + } else if let Some(abs_index) = index.checked_abs() { + let offset = abs_index as usize; + + // Count from end if negative + if offset > BITS { + Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) + } else { + Ok((value & (1 << (BITS - offset))) != 0) + } + } else { + Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) + } + } + #[rhai_fn(return_raw)] + pub fn set_bit(value: &mut INT, index: INT, new_value: bool) -> RhaiResultOf<()> { + if index >= 0 { + let offset = index as usize; + + if offset >= BITS { + Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) + } else { + let mask = 1 << offset; + if new_value { + *value |= mask; + } else { + *value &= !mask; + } + Ok(()) + } + } else if let Some(abs_index) = index.checked_abs() { + let offset = abs_index as usize; + + // Count from end if negative + if offset > BITS { + Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) + } else { + let mask = 1 << offset; + if new_value { + *value |= mask; + } else { + *value &= !mask; + } + Ok(()) + } + } else { + Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) + } + } + #[rhai_fn(name = "get_bits", return_raw)] + pub fn get_bits_range(value: INT, range: ExclusiveRange) -> RhaiResultOf { + let from = INT::max(range.start, 0); + let to = INT::max(range.end, from); + get_bits(value, from, to - from) + } + #[rhai_fn(name = "get_bits", return_raw)] + pub fn get_bits_range_inclusive(value: INT, range: InclusiveRange) -> RhaiResultOf { + let from = INT::max(*range.start(), 0); + let to = INT::max(*range.end(), from - 1); + get_bits(value, from, to - from + 1) + } + #[rhai_fn(return_raw)] + pub fn get_bits(value: INT, index: INT, bits: INT) -> RhaiResultOf { + if bits < 1 { + return Ok(0); + } + + let offset = if index >= 0 { + let offset = index as usize; + + if offset >= BITS { + return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); + } + + offset + } else if let Some(abs_index) = index.checked_abs() { + let offset = abs_index as usize; + + // Count from end if negative + if offset > BITS { + return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); + } + BITS - offset + } else { + return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); + }; + + let bits = if offset + bits as usize > BITS { + BITS - offset + } else { + bits as usize + }; + + let mut base = 1; + let mut mask = 0; + + for _ in 0..bits { + mask |= base; + base <<= 1; + } + + Ok(((value & (mask << index)) >> index) & mask) + } + #[rhai_fn(name = "set_bits", return_raw)] + pub fn set_bits_range( + value: &mut INT, + range: ExclusiveRange, + new_value: INT, + ) -> RhaiResultOf<()> { + let from = INT::max(range.start, 0); + let to = INT::max(range.end, from); + set_bits(value, from, to - from, new_value) + } + #[rhai_fn(name = "set_bits", return_raw)] + pub fn set_bits_range_inclusive( + value: &mut INT, + range: InclusiveRange, + new_value: INT, + ) -> RhaiResultOf<()> { + let from = INT::max(*range.start(), 0); + let to = INT::max(*range.end(), from - 1); + set_bits(value, from, to - from + 1, new_value) + } + #[rhai_fn(return_raw)] + pub fn set_bits(value: &mut INT, index: INT, bits: INT, new_value: INT) -> RhaiResultOf<()> { + if bits < 1 { + return Ok(()); + } + + let offset = if index >= 0 { + let offset = index as usize; + + if offset >= BITS { + return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); + } + + offset + } else if let Some(abs_index) = index.checked_abs() { + let offset = abs_index as usize; + + // Count from end if negative + if offset > BITS { + return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); + } + BITS - offset + } else { + return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); + }; + + let bits = if offset + bits as usize > BITS { + BITS - offset + } else { + bits as usize + }; + + let mut base = 1; + let mut mask = 0; + + for _ in 0..bits { + mask |= base; + base <<= 1; + } + + *value &= !(mask << index); + *value |= (new_value & mask) << index; + + Ok(()) + } +} diff --git a/src/packages/blob_basic.rs b/src/packages/blob_basic.rs index aea9851c..06e8ada8 100644 --- a/src/packages/blob_basic.rs +++ b/src/packages/blob_basic.rs @@ -3,8 +3,8 @@ use crate::plugin::*; use crate::{ - def_package, Blob, Dynamic, EvalAltResult, ExclusiveRange, InclusiveRange, NativeCallContext, - Position, INT, + def_package, Blob, Dynamic, ExclusiveRange, InclusiveRange, NativeCallContext, Position, + RhaiResultOf, INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -13,25 +13,25 @@ use std::{any::TypeId, mem}; #[cfg(not(feature = "no_float"))] use crate::FLOAT; -def_package!(crate:BasicBlobPackage:"Basic BLOB utilities.", lib, { - lib.standard = true; +def_package! { + /// Package of basic BLOB utilities. + crate::BasicBlobPackage => |lib| { + lib.standard = true; - combine_with_exported_module!(lib, "blob", blob_functions); + combine_with_exported_module!(lib, "blob", blob_functions); - // Register blob iterator - lib.set_iterable::(); -}); + // Register blob iterator + lib.set_iterable::(); + } +} #[export_module] -mod blob_functions { - pub fn blob() -> Blob { +pub mod blob_functions { + pub const fn blob() -> Blob { Blob::new() } #[rhai_fn(name = "blob", return_raw)] - pub fn blob_with_capacity( - ctx: NativeCallContext, - len: INT, - ) -> Result> { + pub fn blob_with_capacity(ctx: NativeCallContext, len: INT) -> RhaiResultOf { blob_with_capacity_and_value(ctx, len, 0) } #[rhai_fn(name = "blob", return_raw)] @@ -39,34 +39,30 @@ mod blob_functions { ctx: NativeCallContext, len: INT, value: INT, - ) -> Result> { + ) -> RhaiResultOf { let len = if len < 0 { 0 } else { len as usize }; let _ctx = ctx; // Check if blob will be over max size limit #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() { - return Err(EvalAltResult::ErrorDataTooLarge( - "Size of BLOB".to_string(), - Position::NONE, - ) - .into()); + return Err( + crate::ERR::ErrorDataTooLarge("Size of BLOB".to_string(), Position::NONE).into(), + ); } let mut blob = Blob::new(); - blob.resize(len, (value & 0x00ff) as u8); + blob.resize(len, (value & 0x000000ff) as u8); Ok(blob) } #[rhai_fn(name = "len", get = "len", pure)] pub fn len(blob: &mut Blob) -> INT { blob.len() as INT } - #[rhai_fn(name = "push", name = "+=")] pub fn push(blob: &mut Blob, item: INT) { - let item = (item & 0x00ff) as u8; + let item = (item & 0x000000ff) as u8; blob.push(item); } - #[rhai_fn(name = "append", name = "+=")] pub fn append(blob: &mut Blob, y: Blob) { if !y.is_empty() { if blob.is_empty() { @@ -77,18 +73,21 @@ mod blob_functions { } } #[rhai_fn(name = "+")] - pub fn concat(mut blob: Blob, y: Blob) -> Blob { - if !y.is_empty() { - if blob.is_empty() { - blob = y; + pub fn concat(blob1: Blob, blob2: Blob) -> Blob { + if !blob2.is_empty() { + if blob1.is_empty() { + blob2 } else { - blob.extend(y); + let mut blob = blob1; + blob.extend(blob2); + blob } + } else { + blob1 } - blob } pub fn insert(blob: &mut Blob, position: INT, item: INT) { - let item = (item & 0x00ff) as u8; + let item = (item & 0x000000ff) as u8; if blob.is_empty() { blob.push(item); @@ -109,27 +108,20 @@ mod blob_functions { } } #[rhai_fn(return_raw)] - pub fn pad( - ctx: NativeCallContext, - blob: &mut Blob, - len: INT, - item: INT, - ) -> Result<(), Box> { + pub fn pad(ctx: NativeCallContext, blob: &mut Blob, len: INT, item: INT) -> RhaiResultOf<()> { if len <= 0 { return Ok(()); } - let item = (item & 0x00ff) as u8; + let item = (item & 0x000000ff) as u8; let _ctx = ctx; // Check if blob will be over max size limit #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_array_size() > 0 && (len as usize) > _ctx.engine().max_array_size() { - return Err(EvalAltResult::ErrorDataTooLarge( - "Size of BLOB".to_string(), - Position::NONE, - ) - .into()); + return Err( + crate::ERR::ErrorDataTooLarge("Size of BLOB".to_string(), Position::NONE).into(), + ); } if len as usize > blob.len() { @@ -209,9 +201,9 @@ mod blob_functions { let start = if start < 0 { start .checked_abs() - .map_or(0, |n| blob_len - (n as usize).min(blob_len)) + .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) } else if start as usize >= blob_len { - blob.extend(replace.into_iter()); + blob.extend(replace); return; } else { start as usize @@ -248,7 +240,7 @@ mod blob_functions { let start = if start < 0 { start .checked_abs() - .map_or(0, |n| blob_len - (n as usize).min(blob_len)) + .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) } else if start as usize >= blob_len { return Blob::new(); } else { @@ -312,7 +304,7 @@ mod blob_functions { let start = if start < 0 { start .checked_abs() - .map_or(0, |n| blob_len - (n as usize).min(blob_len)) + .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) } else if start as usize >= blob_len { return Blob::new(); } else { @@ -348,7 +340,7 @@ mod blob_functions { let start = if start < 0 { start .checked_abs() - .map_or(0, |n| blob_len - (n as usize).min(blob_len)) + .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) } else if start as usize >= blob_len { return mem::take(blob); } else { @@ -366,26 +358,6 @@ mod blob_functions { drained } - pub fn contains(blob: &mut Blob, value: Dynamic) -> bool { - if blob.is_empty() { - return false; - } - - let value = match value.as_int() { - Ok(value) => value as u8, - _ => return false, - }; - - blob.contains(&value) - } - #[rhai_fn(name = "==", pure)] - pub fn equals(blob1: &mut Blob, blob2: Blob) -> bool { - &*blob1 == &blob2 - } - #[rhai_fn(name = "!=", pure)] - pub fn not_equals(blob1: &mut Blob, blob2: Blob) -> bool { - &*blob1 != &blob2 - } #[inline] fn parse_int(blob: &mut Blob, start: INT, len: INT, is_le: bool) -> INT { @@ -397,7 +369,7 @@ mod blob_functions { let start = if start < 0 { start .checked_abs() - .map_or(0, |n| blob_len - (n as usize).min(blob_len)) + .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) } else if start as usize >= blob_len { return 0; } else { @@ -466,7 +438,7 @@ mod blob_functions { let start = if start < 0 { start .checked_abs() - .map_or(0, |n| blob_len - (n as usize).min(blob_len)) + .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) } else if start as usize >= blob_len { return; } else { @@ -483,13 +455,11 @@ mod blob_functions { let len = usize::min(len, INT_BYTES); - let mut buf = [0_u8; INT_BYTES]; - - buf.copy_from_slice(&if is_le { + let buf = if is_le { value.to_le_bytes() } else { value.to_be_bytes() - }); + }; blob[start..][..len].copy_from_slice(&buf[..len]); } @@ -537,7 +507,7 @@ mod blob_functions { let start = if start < 0 { start .checked_abs() - .map_or(0, |n| blob_len - (n as usize).min(blob_len)) + .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) } else if start as usize >= blob_len { return 0.0; } else { @@ -613,7 +583,7 @@ mod blob_functions { let start = if start < 0 { start .checked_abs() - .map_or(0, |n| blob_len - (n as usize).min(blob_len)) + .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) } else if start as usize >= blob_len { return; } else { @@ -629,14 +599,11 @@ mod blob_functions { const FLOAT_BYTES: usize = mem::size_of::(); let len = usize::min(len, FLOAT_BYTES); - - let mut buf = [0_u8; FLOAT_BYTES]; - - buf.copy_from_slice(&if is_le { + let buf = if is_le { value.to_le_bytes() } else { value.to_be_bytes() - }); + }; blob[start..][..len].copy_from_slice(&buf[..len]); } @@ -678,4 +645,77 @@ mod blob_functions { pub fn write_be_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) { write_float(blob, start, len, value, false) } + #[inline] + fn write_string(blob: &mut Blob, start: INT, len: INT, string: &str, ascii_only: bool) { + if len <= 0 || blob.is_empty() || string.is_empty() { + return; + } + let blob_len = blob.len(); + + let start = if start < 0 { + start + .checked_abs() + .map_or(0, |n| blob_len - usize::min(n as usize, blob_len)) + } else if start as usize >= blob_len { + return; + } else { + start as usize + }; + + let len = if len as usize > blob_len - start { + blob_len - start + } else { + len as usize + }; + + let len = usize::min(len, string.len()); + + if ascii_only { + string + .chars() + .filter(char::is_ascii) + .take(len) + .map(|ch| ch as u8) + .enumerate() + .for_each(|(i, x)| blob[start + i] = x); + } else { + blob[start..][..len].copy_from_slice(&string.as_bytes()[..len]); + } + } + #[rhai_fn(name = "write_utf8")] + pub fn write_utf8_string(blob: &mut Blob, start: INT, len: INT, string: &str) { + write_string(blob, start, len, string, false) + } + #[rhai_fn(name = "write_utf8")] + pub fn write_utf8_string_range(blob: &mut Blob, range: ExclusiveRange, string: &str) { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + write_string(blob, start, end - start, string, false) + } + #[rhai_fn(name = "write_utf8")] + pub fn write_utf8_string_range_inclusive(blob: &mut Blob, range: InclusiveRange, string: &str) { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + write_string(blob, start, end - start + 1, string, false) + } + #[rhai_fn(name = "write_ascii")] + pub fn write_ascii_string(blob: &mut Blob, start: INT, len: INT, string: &str) { + write_string(blob, start, len, string, true) + } + #[rhai_fn(name = "write_ascii")] + pub fn write_ascii_string_range(blob: &mut Blob, range: ExclusiveRange, string: &str) { + let start = INT::max(range.start, 0); + let end = INT::max(range.end, start); + write_string(blob, start, end - start, string, true) + } + #[rhai_fn(name = "write_ascii")] + pub fn write_ascii_string_range_inclusive( + blob: &mut Blob, + range: InclusiveRange, + string: &str, + ) { + let start = INT::max(*range.start(), 0); + let end = INT::max(*range.end(), start); + write_string(blob, start, end - start + 1, string, true) + } } diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 37bb6eea..66be2772 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -3,11 +3,14 @@ use crate::{def_package, FnPtr, ImmutableString, NativeCallContext}; #[cfg(feature = "no_std")] use std::prelude::v1::*; -def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, { - lib.standard = true; +def_package! { + /// Package of basic function pointer utilities. + crate::BasicFnPackage => |lib| { + lib.standard = true; - combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions); -}); + combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions); + } +} #[export_module] mod fn_ptr_functions { @@ -41,7 +44,7 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { fn make_metadata( dict: &BTreeSet, namespace: Option, - f: &ScriptFnDef, + func: &ScriptFnDef, ) -> Map { const DICT: &str = "key exists"; @@ -50,10 +53,13 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { if let Some(ns) = namespace { map.insert(dict.get("namespace").expect(DICT).clone(), ns.into()); } - map.insert(dict.get("name").expect(DICT).clone(), f.name.clone().into()); + map.insert( + dict.get("name").expect(DICT).clone(), + func.name.clone().into(), + ); map.insert( dict.get("access").expect(DICT).clone(), - match f.access { + match func.access { FnAccess::Public => dict.get("public").expect(DICT).clone(), FnAccess::Private => dict.get("private").expect(DICT).clone(), } @@ -61,14 +67,14 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { ); map.insert( dict.get("is_anonymous").expect(DICT).clone(), - f.name.starts_with(crate::engine::FN_ANONYMOUS).into(), + func.name.starts_with(crate::engine::FN_ANONYMOUS).into(), ); map.insert( dict.get("params").expect(DICT).clone(), - f.params + func.params .iter() .cloned() - .map(Into::::into) + .map(Into::into) .collect::() .into(), ); diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index 1b4e1e97..6dbd8eaf 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -1,10 +1,12 @@ use crate::plugin::*; use crate::types::dynamic::Variant; -use crate::{def_package, EvalAltResult, ExclusiveRange, InclusiveRange, INT}; -use std::iter::{ExactSizeIterator, FusedIterator}; -use std::ops::{Range, RangeInclusive}; +use crate::{def_package, ExclusiveRange, InclusiveRange, RhaiResultOf, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; +use std::{ + iter::{ExactSizeIterator, FusedIterator}, + ops::{Range, RangeInclusive}, +}; #[cfg(not(feature = "unchecked"))] use num_traits::{CheckedAdd as Add, CheckedSub as Sub}; @@ -22,19 +24,19 @@ impl StepRange where T: Variant + Copy + PartialOrd + Add + Sub, { - pub fn new(from: T, to: T, step: T) -> Result> { + pub fn new(from: T, to: T, step: T) -> RhaiResultOf { #[cfg(not(feature = "unchecked"))] if let Some(r) = from.checked_add(&step) { if r == from { - return Err(EvalAltResult::ErrorInFunctionCall( + return Err(crate::ERR::ErrorInFunctionCall( "range".to_string(), String::new(), - EvalAltResult::ErrorArithmetic( + crate::ERR::ErrorArithmetic( "step value cannot be zero".to_string(), - crate::Position::NONE, + Position::NONE, ) .into(), - crate::Position::NONE, + Position::NONE, ) .into()); } @@ -117,33 +119,24 @@ struct BitRange(INT, INT, usize); const BITS: usize = std::mem::size_of::() * 8; impl BitRange { - pub fn new(value: INT, from: INT, len: INT) -> Result> { + pub fn new(value: INT, from: INT, len: INT) -> RhaiResultOf { let from = if from >= 0 { let offset = from as usize; #[cfg(not(feature = "unchecked"))] if offset >= BITS { - return Err( - EvalAltResult::ErrorBitFieldBounds(BITS, from, crate::Position::NONE).into(), - ); + return Err(crate::ERR::ErrorBitFieldBounds(BITS, from, Position::NONE).into()); } offset } else { #[cfg(not(feature = "unchecked"))] if let Some(abs_from) = from.checked_abs() { if (abs_from as usize) > BITS { - return Err(EvalAltResult::ErrorBitFieldBounds( - BITS, - from, - crate::Position::NONE, - ) - .into()); + return Err(crate::ERR::ErrorBitFieldBounds(BITS, from, Position::NONE).into()); } BITS - (abs_from as usize) } else { - return Err( - EvalAltResult::ErrorBitFieldBounds(BITS, from, crate::Position::NONE).into(), - ); + return Err(crate::ERR::ErrorBitFieldBounds(BITS, from, Position::NONE).into()); } #[cfg(feature = "unchecked")] @@ -168,12 +161,10 @@ impl Iterator for BitRange { type Item = bool; fn next(&mut self) -> Option { - let Self(value, mask, len) = *self; - - if len == 0 { + if self.2 == 0 { None } else { - let r = (value & mask) != 0; + let r = (self.0 & self.1) != 0; self.1 <<= 1; self.2 -= 1; Some(r) @@ -272,7 +263,6 @@ macro_rules! reg_range { ($lib:ident | $x:expr => $( $y:ty ),*) => { $( $lib.set_iterator::>(); - $lib.set_iterator::>(); let _hash = $lib.set_native_fn($x, |from: $y, to: $y| Ok(from..to)); #[cfg(feature = "metadata")] @@ -281,6 +271,8 @@ macro_rules! reg_range { concat!("to: ", stringify!($y)), concat!("Iterator") ]); + + $lib.set_iterator::>(); )* }; ($lib:ident | step $x:expr => $( $y:ty ),*) => { @@ -299,268 +291,275 @@ macro_rules! reg_range { }; } -def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { - lib.standard = true; +def_package! { + /// Package of basic range iterators + crate::BasicIteratorPackage => |lib| { + lib.standard = true; - reg_range!(lib | "range" => INT); + reg_range!(lib | "range" => INT); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - reg_range!(lib | "range" => i8, u8, i16, u16, i32, u32, i64, u64); + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_range!(lib | "range" => i8, u8, i16, u16, i32, u32, i64, u64); - #[cfg(not(target_arch = "wasm32"))] - reg_range!(lib | "range" => i128, u128); - } - - reg_range!(lib | step "range" => INT); - - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - reg_range!(lib | step "range" => i8, u8, i16, u16, i32, u32, i64, u64); - - #[cfg(not(target_arch = "wasm32"))] - reg_range!(lib | step "range" => i128, u128); - } - - #[cfg(not(feature = "no_float"))] - { - use crate::FLOAT; - - #[derive(Debug, Clone, Copy, PartialEq)] - struct StepFloatRange(FLOAT, FLOAT, FLOAT); - - impl StepFloatRange { - pub fn new(from: FLOAT, to: FLOAT, step: FLOAT) -> Result> { - #[cfg(not(feature = "unchecked"))] - if step == 0.0 { - return Err(EvalAltResult::ErrorInFunctionCall("range".to_string(), "".to_string(), - EvalAltResult::ErrorArithmetic("step value cannot be zero".to_string(), crate::Position::NONE).into(), - crate::Position::NONE, - ).into()); - } - - Ok(Self(from, to, step)) - } + #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_arch = "wasm64"))] + reg_range!(lib | "range" => i128, u128); } - impl Iterator for StepFloatRange { - type Item = FLOAT; + reg_range!(lib | step "range" => INT); - fn next(&mut self) -> Option { - if self.0 == self.1 { - None - } else if self.0 < self.1 { - #[cfg(not(feature = "unchecked"))] - if self.2 < 0.0 { - return None; - } + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_range!(lib | step "range" => i8, u8, i16, u16, i32, u32, i64, u64); - let v = self.0; - let n = self.0 + self.2; - - self.0 = if n >= self.1 { self.1 } else { n }; - Some(v) - } else { - #[cfg(not(feature = "unchecked"))] - if self.2 > 0.0 { - return None; - } - - let v = self.0; - let n = self.0 + self.2; - - self.0 = if n <= self.1 { self.1 } else { n }; - Some(v) - } - } + #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_arch = "wasm64"))] + reg_range!(lib | step "range" => i128, u128); } - impl FusedIterator for StepFloatRange {} + #[cfg(not(feature = "no_float"))] + { + use crate::FLOAT; - lib.set_iterator::(); + #[derive(Debug, Clone, Copy, PartialEq)] + struct StepFloatRange(FLOAT, FLOAT, FLOAT); - let _hash = lib.set_native_fn("range", StepFloatRange::new); - #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["from: FLOAT", "to: FLOAT", "step: FLOAT", "Iterator"]); - } - - #[cfg(feature = "decimal")] - { - use rust_decimal::Decimal; - - #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] - struct StepDecimalRange(Decimal, Decimal, Decimal); - - impl StepDecimalRange { - pub fn new(from: Decimal, to: Decimal, step: Decimal) -> Result> { - #[cfg(not(feature = "unchecked"))] - if step.is_zero() { - return Err(EvalAltResult::ErrorInFunctionCall("range".to_string(), "".to_string(), - EvalAltResult::ErrorArithmetic("step value cannot be zero".to_string(), crate::Position::NONE).into(), - crate::Position::NONE, - ).into()); - } - - Ok(Self(from, to, step)) - } - } - - impl Iterator for StepDecimalRange { - type Item = Decimal; - - fn next(&mut self) -> Option { - if self.0 == self.1 { - None - } else if self.0 < self.1 { + impl StepFloatRange { + pub fn new(from: FLOAT, to: FLOAT, step: FLOAT) -> RhaiResultOf { #[cfg(not(feature = "unchecked"))] - if self.2.is_sign_negative() { - return None; + if step == 0.0 { + return Err(crate::ERR::ErrorInFunctionCall("range".to_string(), "".to_string(), + crate::ERR::ErrorArithmetic("step value cannot be zero".to_string(), Position::NONE).into(), + Position::NONE, + ).into()); } - let v = self.0; - let n = self.0 + self.2; - - self.0 = if n >= self.1 { self.1 } else { n }; - Some(v) - } else { - #[cfg(not(feature = "unchecked"))] - if self.2.is_sign_positive() { - return None; - } - - let v = self.0; - let n = self.0 + self.2; - - self.0 = if n <= self.1 { self.1 } else { n }; - Some(v) + Ok(Self(from, to, step)) } } + + impl Iterator for StepFloatRange { + type Item = FLOAT; + + fn next(&mut self) -> Option { + if self.0 == self.1 { + None + } else if self.0 < self.1 { + #[cfg(not(feature = "unchecked"))] + if self.2 < 0.0 { + return None; + } + + let v = self.0; + let n = self.0 + self.2; + + self.0 = if n >= self.1 { self.1 } else { n }; + Some(v) + } else { + #[cfg(not(feature = "unchecked"))] + if self.2 > 0.0 { + return None; + } + + let v = self.0; + let n = self.0 + self.2; + + self.0 = if n <= self.1 { self.1 } else { n }; + Some(v) + } + } + } + + impl FusedIterator for StepFloatRange {} + + lib.set_iterator::(); + + let _hash = lib.set_native_fn("range", StepFloatRange::new); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["from: FLOAT", "to: FLOAT", "step: FLOAT", "Iterator"]); } - impl FusedIterator for StepDecimalRange {} + #[cfg(feature = "decimal")] + { + use rust_decimal::Decimal; - lib.set_iterator::(); + #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] + struct StepDecimalRange(Decimal, Decimal, Decimal); - let _hash = lib.set_native_fn("range", StepDecimalRange::new); + impl StepDecimalRange { + pub fn new(from: Decimal, to: Decimal, step: Decimal) -> RhaiResultOf { + #[cfg(not(feature = "unchecked"))] + if step.is_zero() { + return Err(crate::ERR::ErrorInFunctionCall("range".to_string(), "".to_string(), + crate::ERR::ErrorArithmetic("step value cannot be zero".to_string(), Position::NONE).into(), + Position::NONE, + ).into()); + } + + Ok(Self(from, to, step)) + } + } + + impl Iterator for StepDecimalRange { + type Item = Decimal; + + fn next(&mut self) -> Option { + if self.0 == self.1 { + None + } else if self.0 < self.1 { + #[cfg(not(feature = "unchecked"))] + if self.2.is_sign_negative() { + return None; + } + + let v = self.0; + let n = self.0 + self.2; + + self.0 = if n >= self.1 { self.1 } else { n }; + Some(v) + } else { + #[cfg(not(feature = "unchecked"))] + if self.2.is_sign_positive() { + return None; + } + + let v = self.0; + let n = self.0 + self.2; + + self.0 = if n <= self.1 { self.1 } else { n }; + Some(v) + } + } + } + + impl FusedIterator for StepDecimalRange {} + + lib.set_iterator::(); + + let _hash = lib.set_native_fn("range", StepDecimalRange::new); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["from: Decimal", "to: Decimal", "step: Decimal", "Iterator"]); + } + + // Register string iterator + lib.set_iterator::(); + + let _hash = lib.set_native_fn("chars", |string, range: ExclusiveRange| { + let from = INT::max(range.start, 0); + let to = INT::max(range.end, from); + Ok(CharsStream::new(string, from, to - from)) + }); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["from: Decimal", "to: Decimal", "step: Decimal", "Iterator"]); - } + lib.update_fn_metadata(_hash, &["string: &str", "range: Range", "Iterator"]); - // Register string iterator - lib.set_iterator::(); - - let _hash = lib.set_native_fn("chars", |string, range: ExclusiveRange| { - let from = INT::max(range.start, 0); - let to = INT::max(range.end, from); - Ok(CharsStream::new(string, from, to - from)) - }); - #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &str", "range: Range", "Iterator"]); - - let _hash = lib.set_native_fn("chars", |string, range: InclusiveRange| { - let from = INT::max(*range.start(), 0); - let to = INT::max(*range.end(), from - 1); - Ok(CharsStream::new(string, from, to-from + 1)) - }); - #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &str", "range: RangeInclusive", "Iterator"]); - - let _hash = lib.set_native_fn("chars", |string, from, len| Ok(CharsStream::new(string, from, len))); - #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "len: INT", "Iterator"]); - - let _hash = lib.set_native_fn("chars", |string, from| Ok(CharsStream::new(string, from, INT::MAX))); - #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "Iterator"]); - - let _hash = lib.set_native_fn("chars", |string| Ok(CharsStream::new(string, 0, INT::MAX))); - #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &str", "Iterator"]); - - #[cfg(not(feature = "no_object"))] - { - let _hash = lib.set_getter_fn("chars", |string: &mut ImmutableString| Ok(CharsStream::new(string, 0, INT::MAX))); + let _hash = lib.set_native_fn("chars", |string, range: InclusiveRange| { + let from = INT::max(*range.start(), 0); + let to = INT::max(*range.end(), from - 1); + Ok(CharsStream::new(string, from, to-from + 1)) + }); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["string: &mut ImmutableString", "Iterator"]); - } + lib.update_fn_metadata(_hash, &["string: &str", "range: RangeInclusive", "Iterator"]); - // Register bit-field iterator - lib.set_iterator::(); - - let _hash = lib.set_native_fn("bits", |value, range: ExclusiveRange| { - let from = INT::max(range.start, 0); - let to = INT::max(range.end, from); - BitRange::new(value, from, to - from) - }); - #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: INT", "range: Range", "Iterator"]); - - let _hash = lib.set_native_fn("bits", |value, range: InclusiveRange| { - let from = INT::max(*range.start(), 0); - let to = INT::max(*range.end(), from - 1); - BitRange::new(value, from, to - from + 1) - }); - #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: INT", "range: RangeInclusive", "Iterator"]); - - let _hash = lib.set_native_fn("bits", BitRange::new); - #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "len: INT", "Iterator"]); - - let _hash = lib.set_native_fn("bits", |value, from| BitRange::new(value, from, INT::MAX)); - #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "Iterator"]); - - let _hash = lib.set_native_fn("bits", |value| BitRange::new(value, 0, INT::MAX) ); - #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: INT", "Iterator"]); - - #[cfg(not(feature = "no_object"))] - { - let _hash = lib.set_getter_fn("bits", |value: &mut INT| BitRange::new(*value, 0, INT::MAX) ); + let _hash = lib.set_native_fn("chars", |string, from, len| Ok(CharsStream::new(string, from, len))); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: &mut INT", "range: Range", "Iterator"]); - } + lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "len: INT", "Iterator"]); - combine_with_exported_module!(lib, "range", range_functions); -}); + let _hash = lib.set_native_fn("chars", |string, from| Ok(CharsStream::new(string, from, INT::MAX))); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "Iterator"]); + + let _hash = lib.set_native_fn("chars", |string| Ok(CharsStream::new(string, 0, INT::MAX))); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["string: &str", "Iterator"]); + + #[cfg(not(feature = "no_object"))] + { + let _hash = lib.set_getter_fn("chars", |string: &mut ImmutableString| Ok(CharsStream::new(string, 0, INT::MAX))); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["string: &mut ImmutableString", "Iterator"]); + } + + // Register bit-field iterator + lib.set_iterator::(); + + let _hash = lib.set_native_fn("bits", |value, range: ExclusiveRange| { + let from = INT::max(range.start, 0); + let to = INT::max(range.end, from); + BitRange::new(value, from, to - from) + }); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["value: INT", "range: Range", "Iterator"]); + + let _hash = lib.set_native_fn("bits", |value, range: InclusiveRange| { + let from = INT::max(*range.start(), 0); + let to = INT::max(*range.end(), from - 1); + BitRange::new(value, from, to - from + 1) + }); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["value: INT", "range: RangeInclusive", "Iterator"]); + + let _hash = lib.set_native_fn("bits", BitRange::new); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "len: INT", "Iterator"]); + + let _hash = lib.set_native_fn("bits", |value, from| BitRange::new(value, from, INT::MAX)); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "Iterator"]); + + let _hash = lib.set_native_fn("bits", |value| BitRange::new(value, 0, INT::MAX) ); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["value: INT", "Iterator"]); + + #[cfg(not(feature = "no_object"))] + { + let _hash = lib.set_getter_fn("bits", |value: &mut INT| BitRange::new(*value, 0, INT::MAX) ); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["value: &mut INT", "range: Range", "Iterator"]); + } + + combine_with_exported_module!(lib, "range", range_functions); + } +} #[export_module] mod range_functions { #[rhai_fn(get = "start", name = "start", pure)] - pub fn range_start(range: &mut ExclusiveRange) -> INT { + pub fn start(range: &mut ExclusiveRange) -> INT { range.start } #[rhai_fn(get = "end", name = "end", pure)] - pub fn range_end(range: &mut ExclusiveRange) -> INT { + pub fn end(range: &mut ExclusiveRange) -> INT { range.end } - #[rhai_fn(name = "contains", pure)] - pub fn range_contains(range: &mut ExclusiveRange, value: INT) -> bool { - range.contains(&value) - } #[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)] - pub fn range_is_inclusive(range: &mut ExclusiveRange) -> bool { + pub fn is_inclusive(range: &mut ExclusiveRange) -> bool { let _range = range; false } - #[rhai_fn(get = "start", name = "start", pure)] - pub fn range_inclusive_start(range: &mut InclusiveRange) -> INT { - *range.start() - } - #[rhai_fn(get = "end", name = "end", pure)] - pub fn range_inclusive_end(range: &mut InclusiveRange) -> INT { - *range.end() - } - #[rhai_fn(name = "contains", pure)] - pub fn range_inclusive_contains(range: &mut InclusiveRange, value: INT) -> bool { - range.contains(&value) - } - #[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)] - pub fn range_inclusive_is_inclusive(range: &mut InclusiveRange) -> bool { + #[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)] + pub fn is_exclusive(range: &mut ExclusiveRange) -> bool { let _range = range; true } + #[rhai_fn(get = "start", name = "start", pure)] + pub fn start_inclusive(range: &mut InclusiveRange) -> INT { + *range.start() + } + #[rhai_fn(get = "end", name = "end", pure)] + pub fn end_inclusive(range: &mut InclusiveRange) -> INT { + *range.end() + } + #[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)] + pub fn is_inclusive_inclusive(range: &mut InclusiveRange) -> bool { + let _range = range; + true + } + #[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)] + pub fn is_exclusive_inclusive(range: &mut InclusiveRange) -> bool { + let _range = range; + false + } } diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index cab9ebc3..a84b6628 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -1,20 +1,33 @@ use crate::def_package; use crate::plugin::*; use crate::types::dynamic::Tag; -use crate::{Dynamic, EvalAltResult, INT}; +use crate::{Dynamic, RhaiResultOf, ERR, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; +def_package! { + /// Package of core language features. + crate::LanguageCorePackage => |lib| { + lib.standard = true; + + combine_with_exported_module!(lib, "language_core", core_functions); + } +} + #[export_module] mod core_functions { + #[rhai_fn(name = "!")] + pub fn not(x: bool) -> bool { + !x + } #[rhai_fn(name = "tag", get = "tag", pure)] pub fn get_tag(value: &mut Dynamic) -> INT { value.tag() as INT } #[rhai_fn(name = "set_tag", set = "tag", return_raw)] - pub fn set_tag(value: &mut Dynamic, tag: INT) -> Result<(), Box> { + pub fn set_tag(value: &mut Dynamic, tag: INT) -> RhaiResultOf<()> { if tag < Tag::MIN as INT { - Err(EvalAltResult::ErrorArithmetic( + Err(ERR::ErrorArithmetic( format!( "{} is too small to fit into a tag (must be between {} and {})", tag, @@ -25,7 +38,7 @@ mod core_functions { ) .into()) } else if tag > Tag::MAX as INT { - Err(EvalAltResult::ErrorArithmetic( + Err(ERR::ErrorArithmetic( format!( "{} is too large to fit into a tag (must be between {} and {})", tag, @@ -41,9 +54,3 @@ mod core_functions { } } } - -def_package!(crate:LanguageCorePackage:"Language core functions.", lib, { - lib.standard = true; - - combine_with_exported_module!(lib, "language_core", core_functions); -}); diff --git a/src/packages/logic.rs b/src/packages/logic.rs index d6c5523b..9d51db0e 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] use crate::plugin::*; -use crate::{def_package, EvalAltResult, ExclusiveRange, InclusiveRange, INT}; +use crate::{def_package, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -37,38 +37,32 @@ macro_rules! reg_functions { )* } } -def_package!(crate:LogicPackage:"Logical operators.", lib, { - lib.standard = true; +def_package! { + /// Package of basic logic operators. + crate::LogicPackage => |lib| { + lib.standard = true; - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - reg_functions!(lib += numbers; i8, u8, i16, u16, i32, u32, u64); + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_functions!(lib += numbers; i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - reg_functions!(lib += num_128; i128, u128); + #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_arch = "wasm64"))] + reg_functions!(lib += num_128; i128, u128); + } + + #[cfg(not(feature = "no_float"))] + { + #[cfg(not(feature = "f32_float"))] + reg_functions!(lib += float; f32); + combine_with_exported_module!(lib, "f32", f32_functions); + + #[cfg(feature = "f32_float")] + reg_functions!(lib += float; f64); + combine_with_exported_module!(lib, "f64", f64_functions); + } } - - #[cfg(not(feature = "no_float"))] - { - #[cfg(not(feature = "f32_float"))] - reg_functions!(lib += float; f32); - combine_with_exported_module!(lib, "f32", f32_functions); - - #[cfg(feature = "f32_float")] - reg_functions!(lib += float; f64); - combine_with_exported_module!(lib, "f64", f64_functions); - } - - set_exported_fn!(lib, "!", not); - - combine_with_exported_module!(lib, "bit_field", bit_field_functions); -}); - -// Logic operators -#[export_fn] -fn not(x: bool) -> bool { - !x } #[cfg(not(feature = "only_i32"))] @@ -77,7 +71,8 @@ gen_cmp_functions!(numbers => i8, u8, i16, u16, i32, u32, u64); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] -#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_arch = "wasm64"))] gen_cmp_functions!(num_128 => i128, u128); #[cfg(not(feature = "no_float"))] @@ -193,194 +188,3 @@ mod f64_functions { (x as f64) <= (y as f64) } } - -#[export_module] -mod bit_field_functions { - const BITS: usize = std::mem::size_of::() * 8; - - #[rhai_fn(return_raw)] - pub fn get_bit(value: INT, index: INT) -> Result> { - if index >= 0 { - let offset = index as usize; - - if offset >= BITS { - Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) - } else { - Ok((value & (1 << offset)) != 0) - } - } else if let Some(abs_index) = index.checked_abs() { - let offset = abs_index as usize; - - // Count from end if negative - if offset > BITS { - Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) - } else { - Ok((value & (1 << (BITS - offset))) != 0) - } - } else { - Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) - } - } - #[rhai_fn(return_raw)] - pub fn set_bit(value: &mut INT, index: INT, new_value: bool) -> Result<(), Box> { - if index >= 0 { - let offset = index as usize; - - if offset >= BITS { - Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) - } else { - let mask = 1 << offset; - if new_value { - *value |= mask; - } else { - *value &= !mask; - } - Ok(()) - } - } else if let Some(abs_index) = index.checked_abs() { - let offset = abs_index as usize; - - // Count from end if negative - if offset > BITS { - Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) - } else { - let mask = 1 << offset; - if new_value { - *value |= mask; - } else { - *value &= !mask; - } - Ok(()) - } - } else { - Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into()) - } - } - #[rhai_fn(name = "get_bits", return_raw)] - pub fn get_bits_range(value: INT, range: ExclusiveRange) -> Result> { - let from = INT::max(range.start, 0); - let to = INT::max(range.end, from); - get_bits(value, from, to - from) - } - #[rhai_fn(name = "get_bits", return_raw)] - pub fn get_bits_range_inclusive( - value: INT, - range: InclusiveRange, - ) -> Result> { - let from = INT::max(*range.start(), 0); - let to = INT::max(*range.end(), from - 1); - get_bits(value, from, to - from + 1) - } - #[rhai_fn(return_raw)] - pub fn get_bits(value: INT, index: INT, bits: INT) -> Result> { - if bits < 1 { - return Ok(0); - } - - let offset = if index >= 0 { - let offset = index as usize; - - if offset >= BITS { - return Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); - } - - offset - } else if let Some(abs_index) = index.checked_abs() { - let offset = abs_index as usize; - - // Count from end if negative - if offset > BITS { - return Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); - } - BITS - offset - } else { - return Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); - }; - - let bits = if offset + bits as usize > BITS { - BITS - offset - } else { - bits as usize - }; - - let mut base = 1; - let mut mask = 0; - - for _ in 0..bits { - mask |= base; - base <<= 1; - } - - Ok(((value & (mask << index)) >> index) & mask) - } - #[rhai_fn(name = "set_bits", return_raw)] - pub fn set_bits_range( - value: &mut INT, - range: ExclusiveRange, - new_value: INT, - ) -> Result<(), Box> { - let from = INT::max(range.start, 0); - let to = INT::max(range.end, from); - set_bits(value, from, to - from, new_value) - } - #[rhai_fn(name = "set_bits", return_raw)] - pub fn set_bits_range_inclusive( - value: &mut INT, - range: InclusiveRange, - new_value: INT, - ) -> Result<(), Box> { - let from = INT::max(*range.start(), 0); - let to = INT::max(*range.end(), from - 1); - set_bits(value, from, to - from + 1, new_value) - } - #[rhai_fn(return_raw)] - pub fn set_bits( - value: &mut INT, - index: INT, - bits: INT, - new_value: INT, - ) -> Result<(), Box> { - if bits < 1 { - return Ok(()); - } - - let offset = if index >= 0 { - let offset = index as usize; - - if offset >= BITS { - return Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); - } - - offset - } else if let Some(abs_index) = index.checked_abs() { - let offset = abs_index as usize; - - // Count from end if negative - if offset > BITS { - return Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); - } - BITS - offset - } else { - return Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into()); - }; - - let bits = if offset + bits as usize > BITS { - BITS - offset - } else { - bits as usize - }; - - let mut base = 1; - let mut mask = 0; - - for _ in 0..bits { - mask |= base; - base <<= 1; - } - - *value &= !(mask << index); - *value |= (new_value & mask) << index; - - Ok(()) - } -} diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index f8338043..525ce5c0 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -2,29 +2,24 @@ use crate::engine::OP_EQUALS; use crate::plugin::*; -use crate::{def_package, Dynamic, ImmutableString, Map, INT}; +use crate::{def_package, Dynamic, ImmutableString, Map, RhaiResultOf, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(not(feature = "no_index"))] use crate::Array; -def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { - lib.standard = true; +def_package! { + /// Package of basic object map utilities. + crate::BasicMapPackage => |lib| { + lib.standard = true; - combine_with_exported_module!(lib, "map", map_functions); -}); + combine_with_exported_module!(lib, "map", map_functions); + } +} #[export_module] mod map_functions { - #[rhai_fn(name = "has", pure)] - pub fn contains(map: &mut Map, prop: ImmutableString) -> bool { - if map.is_empty() { - false - } else { - map.contains_key(prop.as_str()) - } - } #[rhai_fn(pure)] pub fn len(map: &mut Map) -> INT { map.len() as INT @@ -71,11 +66,7 @@ mod map_functions { } } #[rhai_fn(name = "==", return_raw, pure)] - pub fn equals( - ctx: NativeCallContext, - map1: &mut Map, - map2: Map, - ) -> Result> { + pub fn equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf { if map1.len() != map2.len() { return Ok(false); } @@ -101,11 +92,7 @@ mod map_functions { Ok(true) } #[rhai_fn(name = "!=", return_raw, pure)] - pub fn not_equals( - ctx: NativeCallContext, - map1: &mut Map, - map2: Map, - ) -> Result> { + pub fn not_equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf { equals(ctx, map1, map2).map(|r| !r) } @@ -115,7 +102,7 @@ mod map_functions { if map.is_empty() { Array::new() } else { - map.keys().cloned().map(Into::::into).collect() + map.keys().cloned().map(Into::into).collect() } } #[cfg(not(feature = "no_index"))] diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 0479b937..a6dfa939 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] use crate::plugin::*; -use crate::{def_package, Position, INT}; +use crate::{def_package, Position, RhaiResultOf, ERR, INT, UNSIGNED_INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -18,13 +18,6 @@ use rust_decimal::Decimal; #[cfg(feature = "decimal")] use super::arithmetic::make_err; -#[allow(dead_code)] -#[cfg(feature = "only_i32")] -pub const MAX_INT: INT = i32::MAX; -#[allow(dead_code)] -#[cfg(not(feature = "only_i32"))] -pub const MAX_INT: INT = i64::MAX; - macro_rules! gen_conversion_as_functions { ($root:ident => $func_name:ident ( $($arg_type:ident),+ ) -> $result_type:ty) => { pub mod $root { $(pub mod $arg_type { @@ -58,78 +51,85 @@ macro_rules! reg_functions { )* } } -def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { - lib.standard = true; +def_package! { + /// Basic mathematical package. + crate::BasicMathPackage => |lib| { + lib.standard = true; - // Integer functions - combine_with_exported_module!(lib, "int", int_functions); + // Integer functions + combine_with_exported_module!(lib, "int", int_functions); - reg_functions!(lib += basic_to_int::to_int(char)); - - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - reg_functions!(lib += numbers_to_int::to_int(i8, u8, i16, u16, i32, u32, i64, u64)); - - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - reg_functions!(lib += num_128_to_int::to_int(i128, u128)); - } - - #[cfg(not(feature = "no_float"))] - { - // Floating point functions - combine_with_exported_module!(lib, "float", float_functions); - - // Trig functions - combine_with_exported_module!(lib, "trig", trig_functions); - - reg_functions!(lib += basic_to_float::to_float(INT)); + reg_functions!(lib += basic_to_int::to_int(char)); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_functions!(lib += numbers_to_float::to_float(i8, u8, i16, u16, i32, u32, i64, u32)); + reg_functions!(lib += numbers_to_int::to_int(i8, u8, i16, u16, i32, u32, i64, u64)); - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - reg_functions!(lib += num_128_to_float::to_float(i128, u128)); + #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_arch = "wasm64"))] + reg_functions!(lib += num_128_to_int::to_int(i128, u128)); + } + + #[cfg(not(feature = "no_float"))] + { + // Floating point functions + combine_with_exported_module!(lib, "float", float_functions); + + // Trig functions + combine_with_exported_module!(lib, "trig", trig_functions); + + reg_functions!(lib += basic_to_float::to_float(INT)); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_functions!(lib += numbers_to_float::to_float(i8, u8, i16, u16, i32, u32, i64, u32)); + + #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_arch = "wasm64"))] + reg_functions!(lib += num_128_to_float::to_float(i128, u128)); + } + } + + // Decimal functions + #[cfg(feature = "decimal")] + { + combine_with_exported_module!(lib, "decimal", decimal_functions); + + reg_functions!(lib += basic_to_decimal::to_decimal(INT)); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + reg_functions!(lib += numbers_to_decimal::to_decimal(i8, u8, i16, u16, i32, u32, i64, u64)); } } - - // Decimal functions - #[cfg(feature = "decimal")] - { - combine_with_exported_module!(lib, "decimal", decimal_functions); - - reg_functions!(lib += basic_to_decimal::to_decimal(INT)); - - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - reg_functions!(lib += numbers_to_decimal::to_decimal(i8, u8, i16, u16, i32, u32, i64, u64)); - } -}); +} #[export_module] mod int_functions { #[rhai_fn(name = "parse_int", return_raw)] - pub fn parse_int_radix(string: &str, radix: INT) -> Result> { + pub fn parse_int_radix(string: &str, radix: INT) -> RhaiResultOf { if !(2..=36).contains(&radix) { - return Err(EvalAltResult::ErrorArithmetic( + return Err(ERR::ErrorArithmetic( format!("Invalid radix: '{}'", radix), Position::NONE, ) .into()); } - INT::from_str_radix(string.trim(), radix as u32).map_err(|err| { - EvalAltResult::ErrorArithmetic( - format!("Error parsing integer number '{}': {}", string, err), - Position::NONE, - ) - .into() - }) + UNSIGNED_INT::from_str_radix(string.trim(), radix as u32) + .map(|v| v as INT) + .map_err(|err| { + ERR::ErrorArithmetic( + format!("Error parsing integer number '{}': {}", string, err), + Position::NONE, + ) + .into() + }) } #[rhai_fn(name = "parse_int", return_raw)] - pub fn parse_int(string: &str) -> Result> { + pub fn parse_int(string: &str) -> RhaiResultOf { parse_int_radix(string, 10) } } @@ -258,33 +258,31 @@ mod float_functions { x.is_infinite() } #[rhai_fn(name = "to_int", return_raw)] - pub fn f32_to_int(x: f32) -> Result> { - if cfg!(not(feature = "unchecked")) && x > (MAX_INT as f32) { - Err(EvalAltResult::ErrorArithmetic( - format!("Integer overflow: to_int({})", x), - Position::NONE, + pub fn f32_to_int(x: f32) -> RhaiResultOf { + if cfg!(not(feature = "unchecked")) && x > (INT::MAX as f32) { + Err( + ERR::ErrorArithmetic(format!("Integer overflow: to_int({})", x), Position::NONE) + .into(), ) - .into()) } else { Ok(x.trunc() as INT) } } #[rhai_fn(name = "to_int", return_raw)] - pub fn f64_to_int(x: f64) -> Result> { - if cfg!(not(feature = "unchecked")) && x > (MAX_INT as f64) { - Err(EvalAltResult::ErrorArithmetic( - format!("Integer overflow: to_int({})", x), - Position::NONE, + pub fn f64_to_int(x: f64) -> RhaiResultOf { + if cfg!(not(feature = "unchecked")) && x > (INT::MAX as f64) { + Err( + ERR::ErrorArithmetic(format!("Integer overflow: to_int({})", x), Position::NONE) + .into(), ) - .into()) } else { Ok(x.trunc() as INT) } } #[rhai_fn(return_raw)] - pub fn parse_float(string: &str) -> Result> { + pub fn parse_float(string: &str) -> RhaiResultOf { string.trim().parse::().map_err(|err| { - EvalAltResult::ErrorArithmetic( + ERR::ErrorArithmetic( format!("Error parsing floating-point number '{}': {}", string, err), Position::NONE, ) @@ -320,7 +318,7 @@ mod decimal_functions { } #[cfg(feature = "no_float")] #[rhai_fn(return_raw)] - pub fn parse_float(s: &str) -> Result> { + pub fn parse_float(s: &str) -> RhaiResultOf { parse_decimal(s) } @@ -334,12 +332,12 @@ mod decimal_functions { x.tan() } #[rhai_fn(return_raw)] - pub fn sqrt(x: Decimal) -> Result> { + pub fn sqrt(x: Decimal) -> RhaiResultOf { x.sqrt() .ok_or_else(|| make_err(format!("Error taking the square root of {}", x,))) } #[rhai_fn(return_raw)] - pub fn exp(x: Decimal) -> Result> { + pub fn exp(x: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_exp() .ok_or_else(|| make_err(format!("Exponential overflow: e ** {}", x,))) @@ -348,7 +346,7 @@ mod decimal_functions { } } #[rhai_fn(return_raw)] - pub fn ln(x: Decimal) -> Result> { + pub fn ln(x: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_ln() .ok_or_else(|| make_err(format!("Error taking the natural log of {}", x))) @@ -357,7 +355,7 @@ mod decimal_functions { } } #[rhai_fn(name = "log", return_raw)] - pub fn log10(x: Decimal) -> Result> { + pub fn log10(x: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_log10() .ok_or_else(|| make_err(format!("Error taking the log of {}", x))) @@ -378,7 +376,7 @@ mod decimal_functions { x.round() } #[rhai_fn(name = "round", return_raw)] - pub fn round_dp(x: Decimal, dp: INT) -> Result> { + pub fn round_dp(x: Decimal, dp: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { if dp < 0 { return Err(make_err(format!( @@ -394,7 +392,7 @@ mod decimal_functions { Ok(x.round_dp(dp as u32)) } #[rhai_fn(return_raw)] - pub fn round_up(x: Decimal, dp: INT) -> Result> { + pub fn round_up(x: Decimal, dp: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { if dp < 0 { return Err(make_err(format!( @@ -410,7 +408,7 @@ mod decimal_functions { Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::AwayFromZero)) } #[rhai_fn(return_raw)] - pub fn round_down(x: Decimal, dp: INT) -> Result> { + pub fn round_down(x: Decimal, dp: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { if dp < 0 { return Err(make_err(format!( @@ -426,7 +424,7 @@ mod decimal_functions { Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::ToZero)) } #[rhai_fn(return_raw)] - pub fn round_half_up(x: Decimal, dp: INT) -> Result> { + pub fn round_half_up(x: Decimal, dp: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { if dp < 0 { return Err(make_err(format!( @@ -442,7 +440,7 @@ mod decimal_functions { Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::MidpointAwayFromZero)) } #[rhai_fn(return_raw)] - pub fn round_half_down(x: Decimal, dp: INT) -> Result> { + pub fn round_half_down(x: Decimal, dp: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { if dp < 0 { return Err(make_err(format!( @@ -466,11 +464,11 @@ mod decimal_functions { x.fract() } #[rhai_fn(return_raw)] - pub fn parse_decimal(string: &str) -> Result> { + pub fn parse_decimal(string: &str) -> RhaiResultOf { Decimal::from_str(string) .or_else(|_| Decimal::from_scientific(string)) .map_err(|err| { - EvalAltResult::ErrorArithmetic( + ERR::ErrorArithmetic( format!("Error parsing decimal number '{}': {}", string, err), Position::NONE, ) @@ -480,9 +478,9 @@ mod decimal_functions { #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "to_decimal", return_raw)] - pub fn f32_to_decimal(x: f32) -> Result> { + pub fn f32_to_decimal(x: f32) -> RhaiResultOf { Decimal::try_from(x).map_err(|_| { - EvalAltResult::ErrorArithmetic( + ERR::ErrorArithmetic( format!("Cannot convert to Decimal: to_decimal({})", x), Position::NONE, ) @@ -491,9 +489,9 @@ mod decimal_functions { } #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "to_decimal", return_raw)] - pub fn f64_to_decimal(x: f64) -> Result> { + pub fn f64_to_decimal(x: f64) -> RhaiResultOf { Decimal::try_from(x).map_err(|_| { - EvalAltResult::ErrorArithmetic( + ERR::ErrorArithmetic( format!("Cannot convert to Decimal: to_decimal({})", x), Position::NONE, ) @@ -502,9 +500,9 @@ mod decimal_functions { } #[cfg(not(feature = "no_float"))] #[rhai_fn(return_raw)] - pub fn to_float(x: Decimal) -> Result> { + pub fn to_float(x: Decimal) -> RhaiResultOf { FLOAT::try_from(x).map_err(|_| { - EvalAltResult::ErrorArithmetic( + ERR::ErrorArithmetic( format!("Cannot convert to floating-point: to_float({})", x), Position::NONE, ) @@ -524,7 +522,8 @@ gen_conversion_as_functions!(numbers_to_float => to_float (i8, u8, i16, u16, i32 #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] -#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_arch = "wasm64"))] gen_conversion_as_functions!(num_128_to_float => to_float (i128, u128) -> FLOAT); gen_conversion_as_functions!(basic_to_int => to_int (char) -> INT); @@ -535,7 +534,8 @@ gen_conversion_as_functions!(numbers_to_int => to_int (i8, u8, i16, u16, i32, u3 #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] -#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_arch = "wasm64"))] gen_conversion_as_functions!(num_128_to_int => to_int (i128, u128) -> INT); #[cfg(feature = "decimal")] diff --git a/src/packages/mod.rs b/src/packages/mod.rs index a18a11cf..0b8bc6f3 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -3,8 +3,9 @@ use crate::{Module, Shared}; pub(crate) mod arithmetic; -mod array_basic; -mod blob_basic; +pub(crate) mod array_basic; +mod bit_field; +pub(crate) mod blob_basic; mod fn_basic; mod iter_basic; mod lang_core; @@ -20,10 +21,12 @@ mod time_basic; pub use arithmetic::ArithmeticPackage; #[cfg(not(feature = "no_index"))] pub use array_basic::BasicArrayPackage; +pub use bit_field::BitFieldPackage; #[cfg(not(feature = "no_index"))] pub use blob_basic::BasicBlobPackage; pub use fn_basic::BasicFnPackage; pub use iter_basic::BasicIteratorPackage; +pub use lang_core::LanguageCorePackage; pub use logic::LogicPackage; #[cfg(not(feature = "no_object"))] pub use map_basic::BasicMapPackage; @@ -69,8 +72,8 @@ pub trait Package { /// ``` #[macro_export] macro_rules! def_package { - ($root:ident : $package:ident : $comment:expr , $lib:ident , $block:stmt) => { - #[doc=$comment] + ($($(#[$outer:meta])* $root:ident :: $package:ident => | $lib:ident | $block:block)+) => { $( + $(#[$outer])* pub struct $package($root::Shared<$root::Module>); impl $root::packages::Package for $package { @@ -88,6 +91,42 @@ macro_rules! def_package { } } + impl $package { + pub fn new() -> Self { + let mut module = $root::Module::new(); + ::init(&mut module); + module.build_index(); + Self(module.into()) + } + } + )* }; + ($root:ident : $package:ident : $comment:expr , $lib:ident , $block:stmt) => { + #[deprecated(since = "1.4.0", note = "this is an old syntax of `def_package!` and is deprecated; use the new syntax of `def_package!` instead")] + #[doc=$comment] + /// + /// # Deprecated + /// + /// This old syntax of `def_package!` is deprecated. Use the new syntax instead. + /// + /// This syntax will be removed in the next major version. + pub struct $package($root::Shared<$root::Module>); + + impl $root::packages::Package for $package { + fn as_shared_module(&self) -> $root::Shared<$root::Module> { + #[allow(deprecated)] + self.0.clone() + } + fn init($lib: &mut $root::Module) { + $block + } + } + + impl Default for $package { + fn default() -> Self { + Self::new() + } + } + impl $package { pub fn new() -> Self { let mut module = $root::Module::new(); diff --git a/src/packages/pkg_core.rs b/src/packages/pkg_core.rs index e82e7fc2..c006b0a1 100644 --- a/src/packages/pkg_core.rs +++ b/src/packages/pkg_core.rs @@ -1,21 +1,25 @@ -use super::arithmetic::ArithmeticPackage; -use super::fn_basic::BasicFnPackage; -use super::iter_basic::BasicIteratorPackage; -use super::lang_core::LanguageCorePackage; -use super::logic::LogicPackage; -use super::string_basic::BasicStringPackage; #[cfg(feature = "no_std")] use std::prelude::v1::*; use crate::def_package; -def_package!(crate:CorePackage:"_Core_ package containing basic facilities.", lib, { - lib.standard = true; +def_package! { + /// Core package containing basic facilities. + /// + /// # Contents + /// + /// * [`LanguageCorePackage`][super::LanguageCorePackage] + /// * [`ArithmeticPackage`][super::ArithmeticPackage] + /// * [`BasicStringPackage`][super::BasicStringPackage] + /// * [`BasicIteratorPackage`][super::BasicIteratorPackage] + /// * [`BasicFnPackage`][super::BasicFnPackage] + crate::CorePackage => |lib| { + lib.standard = true; - LanguageCorePackage::init(lib); - ArithmeticPackage::init(lib); - LogicPackage::init(lib); - BasicStringPackage::init(lib); - BasicIteratorPackage::init(lib); - BasicFnPackage::init(lib); -}); + super::LanguageCorePackage::init(lib); + super::ArithmeticPackage::init(lib); + super::BasicStringPackage::init(lib); + super::BasicIteratorPackage::init(lib); + super::BasicFnPackage::init(lib); + } +} diff --git a/src/packages/pkg_std.rs b/src/packages/pkg_std.rs index 3c881232..b388b379 100644 --- a/src/packages/pkg_std.rs +++ b/src/packages/pkg_std.rs @@ -1,32 +1,37 @@ -#[cfg(not(feature = "no_index"))] -use super::array_basic::BasicArrayPackage; -#[cfg(not(feature = "no_index"))] -use super::blob_basic::BasicBlobPackage; -#[cfg(not(feature = "no_object"))] -use super::map_basic::BasicMapPackage; -use super::math_basic::BasicMathPackage; -use super::pkg_core::CorePackage; -use super::string_more::MoreStringPackage; -#[cfg(not(feature = "no_std"))] -use super::time_basic::BasicTimePackage; #[cfg(feature = "no_std")] use std::prelude::v1::*; use crate::def_package; -def_package!(crate:StandardPackage:"_Standard_ package containing all built-in features.", lib, { - lib.standard = true; +def_package! { + /// Standard package containing all built-in features. + /// + /// # Contents + /// + /// * [`CorePackage`][super::CorePackage] + /// * [`BitFieldPackage`][super::BitFieldPackage] + /// * [`LogicPackage`][super::LogicPackage] + /// * [`BasicMathPackage`][super::BasicMathPackage] + /// * [`BasicArrayPackage`][super::BasicArrayPackage] + /// * [`BasicBlobPackage`][super::BasicBlobPackage] + /// * [`BasicMapPackage`][super::BasicMapPackage] + /// * [`BasicTimePackage`][super::BasicTimePackage] + /// * [`MoreStringPackage`][super::MoreStringPackage] + crate::StandardPackage => |lib| { + lib.standard = true; - CorePackage::init(lib); - BasicMathPackage::init(lib); - #[cfg(not(feature = "no_index"))] - { - BasicArrayPackage::init(lib); - BasicBlobPackage::init(lib); + super::CorePackage::init(lib); + super::BitFieldPackage::init(lib); + super::LogicPackage::init(lib); + super::BasicMathPackage::init(lib); + #[cfg(not(feature = "no_index"))] + super::BasicArrayPackage::init(lib); + #[cfg(not(feature = "no_index"))] + super::BasicBlobPackage::init(lib); + #[cfg(not(feature = "no_object"))] + super::BasicMapPackage::init(lib); + #[cfg(not(feature = "no_std"))] + super::BasicTimePackage::init(lib); + super::MoreStringPackage::init(lib); } - #[cfg(not(feature = "no_object"))] - BasicMapPackage::init(lib); - #[cfg(not(feature = "no_std"))] - BasicTimePackage::init(lib); - MoreStringPackage::init(lib); -}); +} diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index facc8360..736fdd95 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -15,12 +15,15 @@ use crate::Map; pub const FUNC_TO_STRING: &str = "to_string"; pub const FUNC_TO_DEBUG: &str = "to_debug"; -def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, { - lib.standard = true; +def_package! { + /// Package of basic string utilities (e.g. printing) + crate::BasicStringPackage => |lib| { + lib.standard = true; - combine_with_exported_module!(lib, "print_debug", print_debug_functions); - combine_with_exported_module!(lib, "number_formatting", number_formatting); -}); + combine_with_exported_module!(lib, "print_debug", print_debug_functions); + combine_with_exported_module!(lib, "number_formatting", number_formatting); + } +} // Register print and debug @@ -271,7 +274,8 @@ mod number_formatting { to_binary(value) } - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_arch = "wasm64"))] pub mod num_128 { #[rhai_fn(name = "to_hex")] pub fn u128_to_hex(value: u128) -> ImmutableString { diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index e95c15e3..515dda99 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,18 +1,21 @@ #![allow(non_snake_case)] use crate::plugin::*; -use crate::{def_package, Dynamic, ExclusiveRange, InclusiveRange, StaticVec, INT}; +use crate::{def_package, Dynamic, ExclusiveRange, InclusiveRange, RhaiResultOf, StaticVec, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{any::TypeId, mem}; use super::string_basic::{print_with_func, FUNC_TO_STRING}; -def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, { - lib.standard = true; +def_package! { + /// Package of additional string utilities over [`BasicStringPackage`][super::BasicStringPackage] + crate::MoreStringPackage => |lib| { + lib.standard = true; - combine_with_exported_module!(lib, "string", string_functions); -}); + combine_with_exported_module!(lib, "string", string_functions); + } +} #[export_module] mod string_functions { @@ -175,7 +178,7 @@ mod string_functions { #[rhai_fn(name = "to_upper")] pub fn to_upper_char(character: char) -> char { let mut stream = character.to_uppercase(); - let ch = stream.next().expect("not empty"); + let ch = stream.next().unwrap(); if stream.next().is_some() { character } else { @@ -189,7 +192,7 @@ mod string_functions { #[rhai_fn(name = "to_lower")] pub fn to_lower_char(character: char) -> char { let mut stream = character.to_lowercase(); - let ch = stream.next().expect("not empty"); + let ch = stream.next().unwrap(); if stream.next().is_some() { character } else { @@ -497,7 +500,7 @@ mod string_functions { string: &mut ImmutableString, len: INT, character: char, - ) -> Result<(), Box> { + ) -> RhaiResultOf<()> { if len <= 0 { return Ok(()); } @@ -506,7 +509,7 @@ mod string_functions { // Check if string will be over max size limit #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_string_size() > 0 && len as usize > _ctx.engine().max_string_size() { - return Err(crate::EvalAltResult::ErrorDataTooLarge( + return Err(crate::ERR::ErrorDataTooLarge( "Length of string".to_string(), crate::Position::NONE, ) @@ -525,7 +528,7 @@ mod string_functions { #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size() { - return Err(crate::EvalAltResult::ErrorDataTooLarge( + return Err(crate::ERR::ErrorDataTooLarge( "Length of string".to_string(), crate::Position::NONE, ) @@ -541,7 +544,7 @@ mod string_functions { string: &mut ImmutableString, len: INT, padding: &str, - ) -> Result<(), Box> { + ) -> RhaiResultOf<()> { if len <= 0 { return Ok(()); } @@ -550,7 +553,7 @@ mod string_functions { // Check if string will be over max size limit #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_string_size() > 0 && len as usize > _ctx.engine().max_string_size() { - return Err(crate::EvalAltResult::ErrorDataTooLarge( + return Err(crate::ERR::ErrorDataTooLarge( "Length of string".to_string(), crate::Position::NONE, ) @@ -576,7 +579,7 @@ mod string_functions { #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size() { - return Err(crate::EvalAltResult::ErrorDataTooLarge( + return Err(crate::ERR::ErrorDataTooLarge( "Length of string".to_string(), crate::Position::NONE, ) @@ -596,7 +599,7 @@ mod string_functions { if string.is_empty() { Array::new() } else { - string.chars().map(Into::::into).collect() + string.chars().map(Into::into).collect() } } #[rhai_fn(name = "split")] @@ -621,57 +624,39 @@ mod string_functions { } } pub fn split(string: &str, delimiter: &str) -> Array { - string.split(delimiter).map(Into::::into).collect() + string.split(delimiter).map(Into::into).collect() } #[rhai_fn(name = "split")] pub fn splitn(string: &str, delimiter: &str, segments: INT) -> Array { let pieces: usize = if segments < 1 { 1 } else { segments as usize }; - string - .splitn(pieces, delimiter) - .map(Into::::into) - .collect() + string.splitn(pieces, delimiter).map(Into::into).collect() } #[rhai_fn(name = "split")] pub fn split_char(string: &str, delimiter: char) -> Array { - string.split(delimiter).map(Into::::into).collect() + string.split(delimiter).map(Into::into).collect() } #[rhai_fn(name = "split")] pub fn splitn_char(string: &str, delimiter: char, segments: INT) -> Array { let pieces: usize = if segments < 1 { 1 } else { segments as usize }; - string - .splitn(pieces, delimiter) - .map(Into::::into) - .collect() + string.splitn(pieces, delimiter).map(Into::into).collect() } #[rhai_fn(name = "split_rev")] pub fn rsplit(string: &str, delimiter: &str) -> Array { - string - .rsplit(delimiter) - .map(Into::::into) - .collect() + string.rsplit(delimiter).map(Into::into).collect() } #[rhai_fn(name = "split_rev")] pub fn rsplitn(string: &str, delimiter: &str, segments: INT) -> Array { let pieces: usize = if segments < 1 { 1 } else { segments as usize }; - string - .rsplitn(pieces, delimiter) - .map(Into::::into) - .collect() + string.rsplitn(pieces, delimiter).map(Into::into).collect() } #[rhai_fn(name = "split_rev")] pub fn rsplit_char(string: &str, delimiter: char) -> Array { - string - .rsplit(delimiter) - .map(Into::::into) - .collect() + string.rsplit(delimiter).map(Into::into).collect() } #[rhai_fn(name = "split_rev")] pub fn rsplitn_char(string: &str, delimiter: char, segments: INT) -> Array { let pieces: usize = if segments < 1 { 1 } else { segments as usize }; - string - .rsplitn(pieces, delimiter) - .map(Into::::into) - .collect() + string.rsplitn(pieces, delimiter).map(Into::into).collect() } } } diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index c51e340b..5f6721bf 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -1,24 +1,28 @@ #![cfg(not(feature = "no_std"))] -use super::{arithmetic::make_err as make_arithmetic_err, math_basic::MAX_INT}; +use super::arithmetic::make_err as make_arithmetic_err; use crate::plugin::*; -use crate::{def_package, Dynamic, EvalAltResult, INT}; +use crate::{def_package, Dynamic, EvalAltResult, RhaiResult, RhaiResultOf, INT}; #[cfg(not(feature = "no_float"))] use crate::FLOAT; -#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_arch = "wasm64"))] use std::time::{Duration, Instant}; #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] use instant::{Duration, Instant}; -def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { - lib.standard = true; +def_package! { + /// Package of basic timing utilities. + crate::BasicTimePackage => |lib| { + lib.standard = true; - // Register date/time functions - combine_with_exported_module!(lib, "time", time_functions); -}); + // Register date/time functions + combine_with_exported_module!(lib, "time", time_functions); + } +} #[export_module] mod time_functions { @@ -27,7 +31,7 @@ mod time_functions { } #[rhai_fn(name = "elapsed", get = "elapsed", return_raw)] - pub fn elapsed(timestamp: Instant) -> Result> { + pub fn elapsed(timestamp: Instant) -> RhaiResult { #[cfg(not(feature = "no_float"))] if timestamp > Instant::now() { Err(make_arithmetic_err("Time-stamp is later than now")) @@ -39,7 +43,7 @@ mod time_functions { { let seconds = timestamp.elapsed().as_secs(); - if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) { + if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) { Err(make_arithmetic_err(format!( "Integer overflow for timestamp.elapsed: {}", seconds @@ -53,10 +57,7 @@ mod time_functions { } #[rhai_fn(return_raw, name = "-")] - pub fn time_diff( - timestamp1: Instant, - timestamp2: Instant, - ) -> Result> { + pub fn time_diff(timestamp1: Instant, timestamp2: Instant) -> RhaiResult { #[cfg(not(feature = "no_float"))] return Ok(if timestamp2 > timestamp1 { -(timestamp2 - timestamp1).as_secs_f64() as FLOAT @@ -69,7 +70,7 @@ mod time_functions { if timestamp2 > timestamp1 { let seconds = (timestamp2 - timestamp1).as_secs(); - if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) { + if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) { Err(make_arithmetic_err(format!( "Integer overflow for timestamp duration: -{}", seconds @@ -80,7 +81,7 @@ mod time_functions { } else { let seconds = (timestamp1 - timestamp2).as_secs(); - if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) { + if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) { Err(make_arithmetic_err(format!( "Integer overflow for timestamp duration: {}", seconds @@ -93,11 +94,11 @@ mod time_functions { #[cfg(not(feature = "no_float"))] pub mod float_functions { - fn add_impl(timestamp: Instant, seconds: FLOAT) -> Result> { + fn add_impl(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf { if seconds < 0.0 { subtract_impl(timestamp, -seconds) } else if cfg!(not(feature = "unchecked")) { - if seconds > (MAX_INT as FLOAT) { + if seconds > (INT::MAX as FLOAT) { Err(make_arithmetic_err(format!( "Integer overflow for timestamp add: {}", seconds @@ -116,14 +117,11 @@ mod time_functions { Ok(timestamp + Duration::from_millis((seconds * 1000.0) as u64)) } } - fn subtract_impl( - timestamp: Instant, - seconds: FLOAT, - ) -> Result> { + fn subtract_impl(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf { if seconds < 0.0 { add_impl(timestamp, -seconds) } else if cfg!(not(feature = "unchecked")) { - if seconds > (MAX_INT as FLOAT) { + if seconds > (INT::MAX as FLOAT) { Err(make_arithmetic_err(format!( "Integer overflow for timestamp add: {}", seconds @@ -144,32 +142,26 @@ mod time_functions { } #[rhai_fn(return_raw, name = "+")] - pub fn add(timestamp: Instant, seconds: FLOAT) -> Result> { + pub fn add(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf { add_impl(timestamp, seconds) } #[rhai_fn(return_raw, name = "+=")] - pub fn add_assign( - timestamp: &mut Instant, - seconds: FLOAT, - ) -> Result<(), Box> { + pub fn add_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> { *timestamp = add_impl(*timestamp, seconds)?; Ok(()) } #[rhai_fn(return_raw, name = "-")] - pub fn subtract(timestamp: Instant, seconds: FLOAT) -> Result> { + pub fn subtract(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf { subtract_impl(timestamp, seconds) } #[rhai_fn(return_raw, name = "-=")] - pub fn subtract_assign( - timestamp: &mut Instant, - seconds: FLOAT, - ) -> Result<(), Box> { + pub fn subtract_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> { *timestamp = subtract_impl(*timestamp, seconds)?; Ok(()) } } - fn add_impl(timestamp: Instant, seconds: INT) -> Result> { + fn add_impl(timestamp: Instant, seconds: INT) -> RhaiResultOf { if seconds < 0 { subtract_impl(timestamp, -seconds) } else if cfg!(not(feature = "unchecked")) { @@ -185,7 +177,7 @@ mod time_functions { Ok(timestamp + Duration::from_secs(seconds as u64)) } } - fn subtract_impl(timestamp: Instant, seconds: INT) -> Result> { + fn subtract_impl(timestamp: Instant, seconds: INT) -> RhaiResultOf { if seconds < 0 { add_impl(timestamp, -seconds) } else if cfg!(not(feature = "unchecked")) { @@ -203,23 +195,20 @@ mod time_functions { } #[rhai_fn(return_raw, name = "+")] - pub fn add(timestamp: Instant, seconds: INT) -> Result> { + pub fn add(timestamp: Instant, seconds: INT) -> RhaiResultOf { add_impl(timestamp, seconds) } #[rhai_fn(return_raw, name = "+=")] - pub fn add_assign(timestamp: &mut Instant, seconds: INT) -> Result<(), Box> { + pub fn add_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> { *timestamp = add_impl(*timestamp, seconds)?; Ok(()) } #[rhai_fn(return_raw, name = "-")] - pub fn subtract(timestamp: Instant, seconds: INT) -> Result> { + pub fn subtract(timestamp: Instant, seconds: INT) -> RhaiResultOf { subtract_impl(timestamp, seconds) } #[rhai_fn(return_raw, name = "-=")] - pub fn subtract_assign( - timestamp: &mut Instant, - seconds: INT, - ) -> Result<(), Box> { + pub fn subtract_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> { *timestamp = subtract_impl(*timestamp, seconds)?; Ok(()) } diff --git a/src/parser.rs b/src/parser.rs index 1698aa25..c4c5311f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,23 +1,24 @@ //! Main module defining the lexer and parser. +use crate::api::custom_syntax::{markers::*, CustomSyntax}; use crate::api::options::LanguageOptions; use crate::ast::{ BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*, }; -use crate::custom_syntax::{markers::*, CustomSyntax}; use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; use crate::func::hashing::get_hasher; -use crate::module::NamespaceRef; +use crate::module::Namespace; use crate::tokenizer::{ is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream, TokenizerControl, }; use crate::types::dynamic::AccessMode; +use crate::types::StringsInterner; use crate::{ - calc_fn_hash, calc_qualified_fn_hash, calc_qualified_var_hash, Engine, ExclusiveRange, - Identifier, ImmutableString, InclusiveRange, LexError, ParseError, ParseErrorType, Position, - Scope, Shared, StaticVec, AST, INT, + calc_fn_hash, calc_qualified_fn_hash, calc_qualified_var_hash, Dynamic, Engine, ExclusiveRange, + Identifier, ImmutableString, InclusiveRange, LexError, ParseError, Position, Scope, Shared, + StaticVec, AST, INT, PERR, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -25,12 +26,11 @@ use std::{ collections::BTreeMap, hash::{Hash, Hasher}, num::{NonZeroU8, NonZeroUsize}, - ops::AddAssign, }; -type PERR = ParseErrorType; +pub type ParseResult = Result; -type FunctionsLib = BTreeMap>; +type FnLib = BTreeMap>; /// Invalid variable name that acts as a search barrier in a [`Scope`]. const SCOPE_SEARCH_BARRIER_MARKER: &str = "$BARRIER$"; @@ -38,41 +38,6 @@ const SCOPE_SEARCH_BARRIER_MARKER: &str = "$BARRIER$"; /// The message: `TokenStream` never ends const NEVER_ENDS: &str = "`TokenStream` never ends"; -/// _(internals)_ A factory of identifiers from text strings. -/// Exported under the `internals` feature only. -/// -/// When [`SmartString`](https://crates.io/crates/smartstring) is used as [`Identifier`], -/// this just returns a copy because most identifiers in Rhai are short and ASCII-based. -/// -/// When [`ImmutableString`] is used as [`Identifier`], this type acts as an interner which keeps a -/// collection of strings and returns shared instances, only creating a new string when it is not -/// yet interned. -#[derive(Debug, Clone, Hash)] -pub struct IdentifierBuilder(); - -impl IdentifierBuilder { - /// Create a new [`IdentifierBuilder`]. - #[inline] - #[must_use] - pub const fn new() -> Self { - Self() - } - /// Get an identifier from a text string. - #[inline] - #[must_use] - pub fn get(&mut self, text: impl AsRef + Into) -> Identifier { - text.into() - } - /// Merge another [`IdentifierBuilder`] into this. - #[inline(always)] - pub fn merge(&mut self, _other: &Self) {} -} - -impl AddAssign for IdentifierBuilder { - #[inline(always)] - fn add_assign(&mut self, _rhs: Self) {} -} - /// _(internals)_ A type that encapsulates the current state of the parser. /// Exported under the `internals` feature only. #[derive(Debug)] @@ -82,7 +47,7 @@ pub struct ParseState<'e> { /// Input stream buffer containing the next character to read. pub tokenizer_control: TokenizerControl, /// Interned strings. - pub interned_strings: IdentifierBuilder, + pub interned_strings: StringsInterner, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. pub stack: StaticVec<(Identifier, AccessMode)>, /// Size of the local variables stack upon entry of the current block scope. @@ -125,7 +90,7 @@ impl<'e> ParseState<'e> { external_vars: BTreeMap::new(), #[cfg(not(feature = "no_closure"))] allow_capture: true, - interned_strings: IdentifierBuilder::new(), + interned_strings: StringsInterner::new(), stack: StaticVec::new_const(), entry_stack_len: 0, #[cfg(not(feature = "no_module"))] @@ -143,8 +108,7 @@ impl<'e> ParseState<'e> { /// Return `None` when the variable name is not found in the `stack`. #[inline] #[must_use] - pub fn access_var(&mut self, name: impl AsRef, pos: Position) -> Option { - let name = name.as_ref(); + pub fn access_var(&mut self, name: &str, pos: Position) -> Option { let mut barrier = false; let _pos = pos; @@ -193,9 +157,7 @@ impl<'e> ParseState<'e> { #[cfg(not(feature = "no_module"))] #[inline] #[must_use] - pub fn find_module(&self, name: impl AsRef) -> Option { - let name = name.as_ref(); - + pub fn find_module(&self, name: &str) -> Option { self.modules .iter() .rev() @@ -204,11 +166,23 @@ impl<'e> ParseState<'e> { .and_then(|(i, _)| NonZeroUsize::new(i + 1)) } - /// Get an interned string, creating one if it is not yet interned. + /// Get an interned identifier, creating one if it is not yet interned. #[inline(always)] #[must_use] - pub fn get_identifier(&mut self, text: impl AsRef + Into) -> Identifier { - self.interned_strings.get(text) + pub fn get_identifier(&mut self, prefix: impl AsRef, text: impl AsRef) -> Identifier { + self.interned_strings.get(prefix, text).into() + } + + /// Get an interned string, creating one if it is not yet interned. + #[inline(always)] + #[allow(dead_code)] + #[must_use] + pub fn get_interned_string( + &mut self, + prefix: impl AsRef, + text: impl AsRef, + ) -> ImmutableString { + self.interned_strings.get(prefix, text) } } @@ -258,10 +232,7 @@ impl ParseSettings { /// Make sure that the current level of expression nesting is within the maximum limit. #[cfg(not(feature = "unchecked"))] #[inline] - pub fn ensure_level_within_max_limit( - &self, - limit: Option, - ) -> Result<(), ParseError> { + pub fn ensure_level_within_max_limit(&self, limit: Option) -> ParseResult<()> { if let Some(limit) = limit { if self.level > limit.get() { return Err(PERR::ExprTooDeep.into_err(self.pos)); @@ -281,22 +252,22 @@ impl Expr { match self { Self::Variable(_, pos, x) if x.1.is_none() => { let ident = x.2; - let getter = state.get_identifier(crate::engine::make_getter(&ident)); + let getter = state.get_identifier(crate::engine::FN_GET, &ident); let hash_get = calc_fn_hash(&getter, 1); - let setter = state.get_identifier(crate::engine::make_setter(&ident)); + let setter = state.get_identifier(crate::engine::FN_SET, &ident); let hash_set = calc_fn_hash(&setter, 2); Self::Property(Box::new(( (getter, hash_get), (setter, hash_set), - (state.get_identifier(ident).into(), pos), + (state.get_interned_string("", &ident), pos), ))) } _ => self, } } /// Raise an error if the expression can never yield a boolean value. - fn ensure_bool_expr(self) -> Result { + fn ensure_bool_expr(self) -> ParseResult { let type_name = match self { Expr::Unit(_) => "()", Expr::DynamicConstant(ref v, _) if !v.is::() => v.type_name(), @@ -317,7 +288,7 @@ impl Expr { ) } /// Raise an error if the expression can never yield an iterable value. - fn ensure_iterable(self) -> Result { + fn ensure_iterable(self) -> ParseResult { let type_name = match self { Expr::Unit(_) => "()", Expr::BoolConstant(_, _) => "a boolean", @@ -340,10 +311,7 @@ impl Expr { /// Make sure that the next expression is not a statement expression (i.e. wrapped in `{}`). #[inline] -fn ensure_not_statement_expr( - input: &mut TokenStream, - type_name: impl ToString, -) -> Result<(), ParseError> { +fn ensure_not_statement_expr(input: &mut TokenStream, type_name: impl ToString) -> ParseResult<()> { match input.peek().expect(NEVER_ENDS) { (Token::LeftBrace, pos) => Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos)), _ => Ok(()), @@ -352,7 +320,7 @@ fn ensure_not_statement_expr( /// Make sure that the next expression is not a mis-typed assignment (i.e. `a = b` instead of `a == b`). #[inline] -fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> { +fn ensure_not_assignment(input: &mut TokenStream) -> ParseResult<()> { match input.peek().expect(NEVER_ENDS) { (Token::Equals, pos) => Err(LexError::ImproperSymbol( "=".to_string(), @@ -374,7 +342,7 @@ fn eat_token(input: &mut TokenStream, expected_token: Token) -> Position { if t != expected_token { unreachable!( - "expecting {} (found {}) at {}", + "{} expected but gets {} at {}", expected_token.syntax(), t.syntax(), pos @@ -395,7 +363,7 @@ fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) { } /// Parse a variable name. -fn parse_var_name(input: &mut TokenStream) -> Result<(Box, Position), ParseError> { +fn parse_var_name(input: &mut TokenStream) -> ParseResult<(Box, Position)> { match input.next().expect(NEVER_ENDS) { // Variable name (Token::Identifier(s), pos) => Ok((s, pos)), @@ -411,7 +379,7 @@ fn parse_var_name(input: &mut TokenStream) -> Result<(Box, Position), Parse } /// Parse a symbol. -fn parse_symbol(input: &mut TokenStream) -> Result<(Box, Position), ParseError> { +fn parse_symbol(input: &mut TokenStream) -> ParseResult<(Box, Position)> { match input.next().expect(NEVER_ENDS) { // Symbol (token, pos) if token.is_standard_symbol() => Ok((token.literal_syntax().into(), pos)), @@ -428,9 +396,9 @@ fn parse_symbol(input: &mut TokenStream) -> Result<(Box, Position), ParseEr fn parse_paren_expr( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -462,12 +430,12 @@ fn parse_paren_expr( fn parse_fn_call( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, id: Identifier, capture_parent_scope: bool, - namespace: Option, + namespace: Option, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -502,7 +470,7 @@ fn parse_fn_call( let relax = false; if !relax && settings.strict_var && index.is_none() { - return Err(ParseErrorType::ModuleUndefined(modules[0].name.to_string()) + return Err(PERR::ModuleUndefined(modules[0].name.to_string()) .into_err(modules[0].pos)); } @@ -523,7 +491,7 @@ fn parse_fn_call( args.shrink_to_fit(); return Ok(FnCallExpr { - name: state.get_identifier(id), + name: state.get_identifier("", id), capture_parent_scope, namespace, hashes, @@ -561,10 +529,8 @@ fn parse_fn_call( let relax = false; if !relax && settings.strict_var && index.is_none() { - return Err(ParseErrorType::ModuleUndefined( - modules[0].name.to_string(), - ) - .into_err(modules[0].pos)); + return Err(PERR::ModuleUndefined(modules[0].name.to_string()) + .into_err(modules[0].pos)); } modules.set_index(index); @@ -584,7 +550,7 @@ fn parse_fn_call( args.shrink_to_fit(); return Ok(FnCallExpr { - name: state.get_identifier(id), + name: state.get_identifier("", id), capture_parent_scope, namespace, hashes, @@ -625,10 +591,10 @@ fn parse_fn_call( fn parse_index_chain( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, lhs: Expr, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -789,9 +755,9 @@ fn parse_index_chain( fn parse_array_literal( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -862,9 +828,9 @@ fn parse_array_literal( fn parse_map_literal( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -944,7 +910,7 @@ fn parse_map_literal( } let expr = parse_expr(input, state, lib, settings.level_up())?; - let name = state.get_identifier(name); + let name = state.get_identifier("", name); template.insert(name.clone(), crate::Dynamic::UNIT); map.push((Ident { name, pos }, expr)); @@ -979,9 +945,9 @@ fn parse_map_literal( fn parse_switch( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -1095,7 +1061,29 @@ fn parse_switch( def_stmt = match (hash, range) { (None, Some(range)) => { - ranges.push((range.0, range.1, range.2, condition, stmt.into())); + let is_empty = if range.2 { + (range.0..=range.1).is_empty() + } else { + (range.0..range.1).is_empty() + }; + + if !is_empty { + match (range.1.checked_sub(range.0), range.2) { + // Unroll single range + (Some(1), false) | (Some(0), true) => { + let value = Dynamic::from_int(range.0); + let hasher = &mut get_hasher(); + value.hash(hasher); + let hash = hasher.finish(); + + table + .entry(hash) + .or_insert_with(|| (condition.clone(), stmt.into()).into()); + } + // Other range + _ => ranges.push((range.0, range.1, range.2, condition, stmt.into())), + } + } None } (Some(hash), None) => { @@ -1103,9 +1091,7 @@ fn parse_switch( None } (None, None) => Some(stmt.into()), - (Some(_), Some(_)) => { - unreachable!("cannot have both a hash and a range in a `switch` statement") - } + _ => unreachable!("both hash and range in switch statement case"), }; match input.peek().expect(NEVER_ENDS) { @@ -1144,9 +1130,9 @@ fn parse_switch( fn parse_primary( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -1155,7 +1141,7 @@ fn parse_primary( let mut settings = settings; settings.pos = *token_pos; - let mut root_expr = match token { + let root_expr = match token { Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)), Token::IntegerConstant(_) @@ -1166,11 +1152,11 @@ fn parse_primary( Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos), Token::CharConstant(c) => Expr::CharConstant(c, settings.pos), Token::StringConstant(s) => { - Expr::StringConstant(state.get_identifier(s).into(), settings.pos) + Expr::StringConstant(state.get_interned_string("", s), settings.pos) } Token::True => Expr::BoolConstant(true, settings.pos), Token::False => Expr::BoolConstant(false, settings.pos), - _ => unreachable!(), + token => unreachable!("token is {:?}", token), }, #[cfg(not(feature = "no_float"))] Token::FloatConstant(x) => { @@ -1189,7 +1175,7 @@ fn parse_primary( Token::LeftBrace if settings.allow_stmt_expr => { match parse_block(input, state, lib, settings.level_up())? { block @ Stmt::Block(_, _) => Expr::Stmt(Box::new(block.into())), - stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt), + stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt), } } // ( - grouped expression @@ -1238,13 +1224,12 @@ fn parse_primary( #[cfg(not(feature = "no_closure"))] new_state.external_vars.iter().try_for_each( - |(captured_var, &pos)| -> Result<_, ParseError> { + |(captured_var, &pos)| -> ParseResult<_> { let index = state.access_var(captured_var, pos); if !settings.is_closure && settings.strict_var && index.is_none() { // If the parent scope is not inside another capturing closure - Err(ParseErrorType::VariableUndefined(captured_var.to_string()) - .into_err(pos)) + Err(PERR::VariableUndefined(captured_var.to_string()).into_err(pos)) } else { Ok(()) } @@ -1261,16 +1246,17 @@ fn parse_primary( Token::InterpolatedString(_) => { let mut segments = StaticVec::::new(); - if let (Token::InterpolatedString(s), pos) = input.next().expect(NEVER_ENDS) { - segments.push(Expr::StringConstant(s.into(), pos)); - } else { - unreachable!(); + match input.next().expect(NEVER_ENDS) { + (Token::InterpolatedString(s), pos) => { + segments.push(Expr::StringConstant(s.into(), pos)); + } + token => unreachable!("Token::InterpolatedString expected but gets {:?}", token), } loop { let expr = match parse_block(input, state, lib, settings.level_up())? { block @ Stmt::Block(_, _) => Expr::Stmt(Box::new(block.into())), - stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt), + stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt), }; segments.push(expr); @@ -1296,7 +1282,7 @@ fn parse_primary( return Err(err.into_err(pos)) } (token, _) => unreachable!( - "expected a string within an interpolated string literal, but gets {:?}", + "string within an interpolated string literal expected but gets {:?}", token ), } @@ -1314,11 +1300,21 @@ fn parse_primary( #[cfg(not(feature = "no_object"))] Token::MapStart => parse_map_literal(input, state, lib, settings.level_up())?, + // Custom syntax. + Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key) + if state.engine.custom_syntax.contains_key(&**key) => + { + let (key, syntax) = state.engine.custom_syntax.get_key_value(&**key).unwrap(); + let (_, pos) = input.next().expect(NEVER_ENDS); + let settings2 = settings.level_up(); + parse_custom_syntax(input, state, lib, settings2, key, syntax, pos)? + } + // Identifier Token::Identifier(_) => { let s = match input.next().expect(NEVER_ENDS) { (Token::Identifier(s), _) => s, - _ => unreachable!(), + token => unreachable!("Token::Identifier expected but gets {:?}", token), }; match input.peek().expect(NEVER_ENDS).0 { @@ -1332,7 +1328,7 @@ fn parse_primary( Expr::Variable( None, settings.pos, - (None, None, state.get_identifier(s)).into(), + (None, None, state.get_identifier("", s)).into(), ) } // Namespace qualification @@ -1346,7 +1342,7 @@ fn parse_primary( Expr::Variable( None, settings.pos, - (None, None, state.get_identifier(s)).into(), + (None, None, state.get_identifier("", s)).into(), ) } // Normal variable access @@ -1354,9 +1350,7 @@ fn parse_primary( let index = state.access_var(&s, settings.pos); if settings.strict_var && index.is_none() { - return Err( - ParseErrorType::VariableUndefined(s.to_string()).into_err(settings.pos) - ); + return Err(PERR::VariableUndefined(s.to_string()).into_err(settings.pos)); } let short_index = index.and_then(|x| { @@ -1369,7 +1363,7 @@ fn parse_primary( Expr::Variable( short_index, settings.pos, - (index, None, state.get_identifier(s)).into(), + (index, None, state.get_identifier("", s)).into(), ) } } @@ -1379,7 +1373,7 @@ fn parse_primary( Token::Reserved(_) => { let s = match input.next().expect(NEVER_ENDS) { (Token::Reserved(s), _) => s, - _ => unreachable!(), + token => unreachable!("Token::Reserved expected but gets {:?}", token), }; match input.peek().expect(NEVER_ENDS).0 { @@ -1387,30 +1381,27 @@ fn parse_primary( Token::LeftParen | Token::Bang if is_keyword_function(&s) => Expr::Variable( None, settings.pos, - (None, None, state.get_identifier(s)).into(), + (None, None, state.get_identifier("", s)).into(), ), // Access to `this` as a variable is OK within a function scope #[cfg(not(feature = "no_function"))] _ if &*s == KEYWORD_THIS && settings.is_function_scope => Expr::Variable( None, settings.pos, - (None, None, state.get_identifier(s)).into(), + (None, None, state.get_identifier("", s)).into(), ), // Cannot access to `this` as a variable not in a function scope _ if &*s == KEYWORD_THIS => { let msg = format!("'{}' can only be used in functions", s); return Err(LexError::ImproperSymbol(s.to_string(), msg).into_err(settings.pos)); } - _ if is_valid_identifier(s.chars()) => { - return Err(PERR::Reserved(s.to_string()).into_err(settings.pos)) - } - _ => return Err(LexError::UnexpectedInput(s.to_string()).into_err(settings.pos)), + _ => return Err(PERR::Reserved(s.to_string()).into_err(settings.pos)), } } Token::LexError(_) => match input.next().expect(NEVER_ENDS) { (Token::LexError(err), _) => return Err(err.into_err(settings.pos)), - _ => unreachable!(), + token => unreachable!("Token::LexError expected but gets {:?}", token), }, _ => { @@ -1418,18 +1409,31 @@ fn parse_primary( } }; + parse_postfix(input, state, lib, root_expr, settings) +} + +/// Tail processing of all possible postfix operators of a primary expression. +fn parse_postfix( + input: &mut TokenStream, + state: &mut ParseState, + lib: &mut FnLib, + mut lhs: Expr, + settings: ParseSettings, +) -> ParseResult { + let mut settings = settings; + // Tail processing all possible postfix operators loop { let (tail_token, _) = input.peek().expect(NEVER_ENDS); - if !root_expr.is_valid_postfix(tail_token) { + if !lhs.is_valid_postfix(tail_token) { break; } let (tail_token, tail_pos) = input.next().expect(NEVER_ENDS); settings.pos = tail_pos; - root_expr = match (root_expr, tail_token) { + lhs = match (lhs, tail_token) { // Qualified function call with ! (Expr::Variable(_, _, x), Token::Bang) if x.1.is_some() => { return if !match_token(input, Token::LeftParen).0 { @@ -1478,7 +1482,7 @@ fn parse_primary( if let Some((ref mut namespace, _)) = namespace { namespace.push(var_name_def); } else { - let mut ns = NamespaceRef::new(); + let mut ns = Namespace::new(); ns.push(var_name_def); namespace = Some((ns, 42)); } @@ -1486,7 +1490,7 @@ fn parse_primary( Expr::Variable( None, pos2, - (None, namespace, state.get_identifier(id2)).into(), + (None, namespace, state.get_identifier("", id2)).into(), ) } // Indexing @@ -1523,7 +1527,7 @@ fn parse_primary( } // Cache the hash key for namespace-qualified variables - let namespaced_variable = match root_expr { + let namespaced_variable = match lhs { Expr::Variable(_, _, ref mut x) if x.1.is_some() => Some(x.as_mut()), Expr::Index(ref mut x, _, _) | Expr::Dot(ref mut x, _, _) => match x.lhs { Expr::Variable(_, _, ref mut x) if x.1.is_some() => Some(x.as_mut()), @@ -1546,8 +1550,7 @@ fn parse_primary( if !relax && settings.strict_var && index.is_none() { return Err( - ParseErrorType::ModuleUndefined(namespace[0].name.to_string()) - .into_err(namespace[0].pos), + PERR::ModuleUndefined(namespace[0].name.to_string()).into_err(namespace[0].pos) ); } @@ -1556,16 +1559,16 @@ fn parse_primary( } // Make sure identifiers are valid - Ok(root_expr) + Ok(lhs) } /// Parse a potential unary operator. fn parse_unary( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -1604,7 +1607,7 @@ fn parse_unary( args.shrink_to_fit(); Ok(FnCallExpr { - name: state.get_identifier("-"), + name: state.get_identifier("", "-"), hashes: FnCallHashes::from_native(calc_fn_hash("-", 1)), args, ..Default::default() @@ -1630,7 +1633,7 @@ fn parse_unary( args.shrink_to_fit(); Ok(FnCallExpr { - name: state.get_identifier("+"), + name: state.get_identifier("", "+"), hashes: FnCallHashes::from_native(calc_fn_hash("+", 1)), args, ..Default::default() @@ -1647,7 +1650,7 @@ fn parse_unary( args.shrink_to_fit(); Ok(FnCallExpr { - name: state.get_identifier("!"), + name: state.get_identifier("", "!"), hashes: FnCallHashes::from_native(calc_fn_hash("!", 1)), args, ..Default::default() @@ -1668,7 +1671,7 @@ fn make_assignment_stmt( lhs: Expr, rhs: Expr, op_pos: Position, -) -> Result { +) -> ParseResult { #[must_use] fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option { match expr { @@ -1692,7 +1695,7 @@ fn make_assignment_stmt( } } - let op_info = op.map(OpAssignment::new); + let op_info = op.map(OpAssignment::new_from_token); match lhs { // const_expr = rhs @@ -1707,11 +1710,7 @@ fn make_assignment_stmt( Expr::Variable(i, var_pos, ref x) => { let (index, _, name) = x.as_ref(); let index = i.map_or_else( - || { - index - .expect("the long index is `Some` when the short index is `None`") - .get() - }, + || index.expect("either long or short index is `None`").get(), |n| n.get() as usize, ); match state.stack[state.stack.len() - index].1 { @@ -1764,10 +1763,10 @@ fn make_assignment_stmt( fn parse_op_assignment_stmt( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, lhs: Expr, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -1798,7 +1797,7 @@ fn make_dot_expr( terminate_chaining: bool, rhs: Expr, op_pos: Position, -) -> Result { +) -> ParseResult { match (lhs, rhs) { // lhs[idx_expr].rhs (Expr::Index(mut x, term, pos), rhs) => { @@ -1825,7 +1824,7 @@ fn make_dot_expr( let (x, term, pos, is_dot) = match rhs { Expr::Dot(x, term, pos) => (x, term, pos, true), Expr::Index(x, term, pos) => (x, term, pos, false), - _ => unreachable!(), + expr => unreachable!("Expr::Dot or Expr::Index expected but gets {:?}", expr), }; match x.lhs { @@ -1864,7 +1863,7 @@ fn make_dot_expr( }; Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos)) } - _ => unreachable!("invalid dot expression: {:?}", x.lhs), + expr => unreachable!("invalid dot expression: {:?}", expr), } } // lhs.nnn::func(...) @@ -1912,11 +1911,11 @@ fn make_dot_expr( fn parse_binary_op( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, parent_precedence: Option, lhs: Expr, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -1984,7 +1983,7 @@ fn parse_binary_op( let hash = calc_fn_hash(&op, 2); let op_base = FnCallExpr { - name: state.get_identifier(op.as_ref()), + name: state.get_identifier("", op), hashes: FnCallHashes::from_native(hash), ..Default::default() }; @@ -2006,8 +2005,8 @@ fn parse_binary_op( | Token::GreaterThanEqualsTo => FnCallExpr { args, ..op_base }.into_fn_call_expr(pos), Token::Or => { - let rhs = args.pop().expect("two arguments"); - let current_lhs = args.pop().expect("two arguments"); + let rhs = args.pop().unwrap(); + let current_lhs = args.pop().unwrap(); Expr::Or( BinaryExpr { lhs: current_lhs.ensure_bool_expr()?, @@ -2018,8 +2017,8 @@ fn parse_binary_op( ) } Token::And => { - let rhs = args.pop().expect("two arguments"); - let current_lhs = args.pop().expect("two arguments"); + let rhs = args.pop().unwrap(); + let current_lhs = args.pop().unwrap(); Expr::And( BinaryExpr { lhs: current_lhs.ensure_bool_expr()?, @@ -2039,7 +2038,7 @@ fn parse_binary_op( FnCallExpr { hashes: calc_fn_hash(OP_CONTAINS, 2).into(), args, - name: state.get_identifier(OP_CONTAINS), + name: state.get_identifier("", OP_CONTAINS), ..op_base } .into_fn_call_expr(pos) @@ -2075,12 +2074,12 @@ fn parse_binary_op( fn parse_custom_syntax( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, key: impl Into, syntax: &CustomSyntax, pos: Position, -) -> Result { +) -> ParseResult { let mut settings = settings; let mut inputs = StaticVec::::new(); let mut segments = StaticVec::new_const(); @@ -2090,7 +2089,7 @@ fn parse_custom_syntax( if syntax.scope_may_be_changed { // Add a barrier variable to the stack so earlier variables will not be matched. // Variable searches stop at the first barrier. - let marker = state.get_identifier(SCOPE_SEARCH_BARRIER_MARKER); + let marker = state.get_identifier("", SCOPE_SEARCH_BARRIER_MARKER); state.stack.push((marker, AccessMode::ReadWrite)); } @@ -2110,7 +2109,10 @@ fn parse_custom_syntax( if seg.starts_with(CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT) && seg.len() > CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT.len() => { - inputs.push(Expr::StringConstant(state.get_identifier(seg).into(), pos)); + inputs.push(Expr::StringConstant( + state.get_interned_string("", seg), + pos, + )); break; } Ok(Some(seg)) => seg, @@ -2121,38 +2123,38 @@ fn parse_custom_syntax( match required_token.as_str() { CUSTOM_SYNTAX_MARKER_IDENT => { let (name, pos) = parse_var_name(input)?; - let name = state.get_identifier(name); + let name = state.get_identifier("", name); segments.push(name.clone().into()); - tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_IDENT)); + tokens.push(state.get_identifier("", CUSTOM_SYNTAX_MARKER_IDENT)); inputs.push(Expr::Variable(None, pos, (None, None, name).into())); } CUSTOM_SYNTAX_MARKER_SYMBOL => { let (symbol, pos) = parse_symbol(input)?; - let symbol: ImmutableString = state.get_identifier(symbol).into(); + let symbol = state.get_interned_string("", symbol); segments.push(symbol.clone()); - tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_SYMBOL)); + tokens.push(state.get_identifier("", CUSTOM_SYNTAX_MARKER_SYMBOL)); inputs.push(Expr::StringConstant(symbol, pos)); } CUSTOM_SYNTAX_MARKER_EXPR => { inputs.push(parse_expr(input, state, lib, settings)?); - let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_EXPR); + let keyword = state.get_identifier("", CUSTOM_SYNTAX_MARKER_EXPR); segments.push(keyword.clone().into()); tokens.push(keyword); } CUSTOM_SYNTAX_MARKER_BLOCK => match parse_block(input, state, lib, settings)? { block @ Stmt::Block(_, _) => { inputs.push(Expr::Stmt(Box::new(block.into()))); - let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_BLOCK); + let keyword = state.get_identifier("", CUSTOM_SYNTAX_MARKER_BLOCK); segments.push(keyword.clone().into()); tokens.push(keyword); } - stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt), + stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt), }, CUSTOM_SYNTAX_MARKER_BOOL => match input.next().expect(NEVER_ENDS) { (b @ Token::True, pos) | (b @ Token::False, pos) => { inputs.push(Expr::BoolConstant(b == Token::True, pos)); - segments.push(state.get_identifier(b.literal_syntax()).into()); - tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_BOOL)); + segments.push(state.get_interned_string("", b.literal_syntax())); + tokens.push(state.get_identifier("", CUSTOM_SYNTAX_MARKER_BOOL)); } (_, pos) => { return Err( @@ -2165,7 +2167,7 @@ fn parse_custom_syntax( (Token::IntegerConstant(i), pos) => { inputs.push(Expr::IntegerConstant(i, pos)); segments.push(i.to_string().into()); - tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_INT)); + tokens.push(state.get_identifier("", CUSTOM_SYNTAX_MARKER_INT)); } (_, pos) => { return Err( @@ -2175,29 +2177,25 @@ fn parse_custom_syntax( } }, #[cfg(not(feature = "no_float"))] - crate::custom_syntax::markers::CUSTOM_SYNTAX_MARKER_FLOAT => { - match input.next().expect(NEVER_ENDS) { - (Token::FloatConstant(f), pos) => { - inputs.push(Expr::FloatConstant(f, pos)); - segments.push(f.to_string().into()); - tokens.push(state.get_identifier( - crate::custom_syntax::markers::CUSTOM_SYNTAX_MARKER_FLOAT, - )); - } - (_, pos) => { - return Err(PERR::MissingSymbol( - "Expecting a floating-point number".to_string(), - ) - .into_err(pos)) - } + CUSTOM_SYNTAX_MARKER_FLOAT => match input.next().expect(NEVER_ENDS) { + (Token::FloatConstant(f), pos) => { + inputs.push(Expr::FloatConstant(f, pos)); + segments.push(f.to_string().into()); + tokens.push(state.get_identifier("", CUSTOM_SYNTAX_MARKER_FLOAT)); } - } + (_, pos) => { + return Err(PERR::MissingSymbol( + "Expecting a floating-point number".to_string(), + ) + .into_err(pos)) + } + }, CUSTOM_SYNTAX_MARKER_STRING => match input.next().expect(NEVER_ENDS) { (Token::StringConstant(s), pos) => { - let s: ImmutableString = state.get_identifier(s).into(); + let s = state.get_interned_string("", s); inputs.push(Expr::StringConstant(s.clone(), pos)); segments.push(s); - tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_STRING)); + tokens.push(state.get_identifier("", CUSTOM_SYNTAX_MARKER_STRING)); } (_, pos) => { return Err(PERR::MissingSymbol("Expecting a string".to_string()).into_err(pos)) @@ -2250,53 +2248,28 @@ fn parse_custom_syntax( fn parse_expr( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut settings = settings; settings.pos = input.peek().expect(NEVER_ENDS).1; - // Check if it is a custom syntax. - if !state.engine.custom_syntax.is_empty() { - let (token, pos) = input.peek().expect(NEVER_ENDS); - let token_pos = *pos; - - match token { - Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key) => { - if let Some((key, syntax)) = state.engine.custom_syntax.get_key_value(key.as_ref()) - { - input.next().expect(NEVER_ENDS); - return parse_custom_syntax( - input, state, lib, settings, key, syntax, token_pos, - ); - } - } - _ => (), - } - } - // Parse expression normally. + let precedence = Precedence::new(1); let lhs = parse_unary(input, state, lib, settings.level_up())?; - parse_binary_op( - input, - state, - lib, - Precedence::new(1), - lhs, - settings.level_up(), - ) + parse_binary_op(input, state, lib, precedence, lhs, settings.level_up()) } /// Parse an if statement. fn parse_if( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2334,9 +2307,9 @@ fn parse_if( fn parse_while_loop( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2351,7 +2324,7 @@ fn parse_while_loop( (expr, pos) } (Token::Loop, pos) => (Expr::Unit(Position::NONE), pos), - _ => unreachable!(), + token => unreachable!("Token::While or Token::Loop expected but gets {:?}", token), }; settings.pos = token_pos; settings.is_breakable = true; @@ -2365,9 +2338,9 @@ fn parse_while_loop( fn parse_do( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2408,9 +2381,9 @@ fn parse_do( fn parse_for( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2470,13 +2443,13 @@ fn parse_for( let prev_stack_len = state.stack.len(); let counter_var = counter_name.map(|name| { - let name = state.get_identifier(name); + let name = state.get_identifier("", name); let pos = counter_pos.expect("`Some`"); state.stack.push((name.clone(), AccessMode::ReadWrite)); Ident { name, pos } }); - let loop_var = state.get_identifier(name); + let loop_var = state.get_identifier("", name); state.stack.push((loop_var.clone(), AccessMode::ReadWrite)); let loop_var = Ident { name: loop_var, @@ -2499,11 +2472,11 @@ fn parse_for( fn parse_let( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, var_type: AccessMode, is_export: bool, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2514,7 +2487,7 @@ fn parse_let( // let name ... let (name, pos) = parse_var_name(input)?; - let name = state.get_identifier(name); + let name = state.get_identifier("", name); let var_def = Ident { name: name.clone(), pos, @@ -2554,9 +2527,9 @@ fn parse_let( fn parse_import( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2574,7 +2547,7 @@ fn parse_import( // import expr as name ... let (name, pos) = parse_var_name(input)?; - let name = state.get_identifier(name); + let name = state.get_identifier("", name); state.modules.push(name.clone()); Ok(Stmt::Import( @@ -2589,9 +2562,9 @@ fn parse_import( fn parse_export( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2631,11 +2604,11 @@ fn parse_export( exports.push(( Ident { - name: state.get_identifier(id), + name: state.get_identifier("", id), pos: id_pos, }, Ident { - name: state.get_identifier(rename.as_ref().map_or("", |s| s.as_ref())), + name: state.get_identifier("", rename.as_ref().map_or("", <_>::as_ref)), pos: rename_pos, }, )); @@ -2662,9 +2635,9 @@ fn parse_export( fn parse_block( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2764,9 +2737,9 @@ fn parse_block( fn parse_expr_stmt( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2782,9 +2755,9 @@ fn parse_expr_stmt( fn parse_stmt( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result { +) -> ParseResult { use AccessMode::{ReadOnly, ReadWrite}; let mut settings = settings; @@ -2802,7 +2775,7 @@ fn parse_stmt( } if !crate::tokenizer::is_doc_comment(comment) { - unreachable!("expecting doc-comment, but gets {:?}", comment); + unreachable!("doc-comment expected but gets {:?}", comment); } if !settings.is_global { @@ -2819,7 +2792,7 @@ fn parse_stmt( _ => return Err(PERR::WrongDocComment.into_err(comments_pos)), } } - _ => unreachable!(), + token => unreachable!("Token::Comment expected but gets {:?}", token), } } @@ -2946,14 +2919,15 @@ fn parse_stmt( let (return_type, token_pos) = input .next() .map(|(token, pos)| { - ( - match token { - Token::Return => AST_OPTION_NONE, - Token::Throw => AST_OPTION_BREAK_OUT, - _ => unreachable!(), - }, - pos, - ) + let flags = match token { + Token::Return => AST_OPTION_NONE, + Token::Throw => AST_OPTION_BREAK_OUT, + token => unreachable!( + "Token::Return or Token::Throw expected but gets {:?}", + token + ), + }; + (flags, pos) }) .expect(NEVER_ENDS); @@ -2996,9 +2970,9 @@ fn parse_stmt( fn parse_try_catch( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -3032,7 +3006,7 @@ fn parse_try_catch( .into_err(err_pos)); } - let name = state.get_identifier(name); + let name = state.get_identifier("", name); state.stack.push((name.clone(), AccessMode::ReadWrite)); Some(Ident { name, pos }) } else { @@ -3044,7 +3018,7 @@ fn parse_try_catch( if err_var.is_some() { // Remove the error variable from the stack - state.stack.pop().expect("not empty"); + state.stack.pop().unwrap(); } Ok(Stmt::TryCatch( @@ -3058,13 +3032,13 @@ fn parse_try_catch( fn parse_fn( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, access: crate::FnAccess, settings: ParseSettings, #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] comments: Vec>, -) -> Result { +) -> ParseResult { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -3097,7 +3071,7 @@ fn parse_fn( PERR::FnDuplicatedParam(name.to_string(), s.to_string()).into_err(pos) ); } - let s = state.get_identifier(s); + let s = state.get_identifier("", s); state.stack.push((s.clone(), AccessMode::ReadWrite)); params.push((s, pos)) } @@ -3136,13 +3110,13 @@ fn parse_fn( params.shrink_to_fit(); Ok(ScriptFnDef { - name: state.get_identifier(name), + name: state.get_identifier("", name), access, params, body, lib: None, #[cfg(not(feature = "no_module"))] - mods: crate::engine::Imports::new(), + global: None, #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] comments: if comments.is_empty() { @@ -3180,7 +3154,7 @@ fn make_curry_from_externals( ); let expr = FnCallExpr { - name: state.get_identifier(crate::engine::KEYWORD_FN_PTR_CURRY), + name: state.get_identifier("", crate::engine::KEYWORD_FN_PTR_CURRY), hashes: FnCallHashes::from_native(calc_fn_hash( crate::engine::KEYWORD_FN_PTR_CURRY, num_externals + 1, @@ -3203,9 +3177,9 @@ fn make_curry_from_externals( fn parse_anon_fn( input: &mut TokenStream, state: &mut ParseState, - lib: &mut FunctionsLib, + lib: &mut FnLib, settings: ParseSettings, -) -> Result<(Expr, ScriptFnDef), ParseError> { +) -> ParseResult<(Expr, ScriptFnDef)> { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -3222,7 +3196,7 @@ fn parse_anon_fn( PERR::FnDuplicatedParam("".to_string(), s.to_string()).into_err(pos) ); } - let s = state.get_identifier(s); + let s = state.get_identifier("", s); state.stack.push((s.clone(), AccessMode::ReadWrite)); params_list.push(s) } @@ -3279,8 +3253,7 @@ fn parse_anon_fn( params.iter().for_each(|p| p.hash(hasher)); body.hash(hasher); let hash = hasher.finish(); - - let fn_name = state.get_identifier(&(format!("{}{:016x}", crate::engine::FN_ANONYMOUS, hash))); + let fn_name = state.get_identifier("", format!("{}{:016x}", crate::engine::FN_ANONYMOUS, hash)); // Define the function let script = ScriptFnDef { @@ -3290,7 +3263,7 @@ fn parse_anon_fn( body: body.into(), lib: None, #[cfg(not(feature = "no_module"))] - mods: crate::engine::Imports::new(), + global: None, #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] comments: None, @@ -3313,7 +3286,7 @@ impl Engine { state: &mut ParseState, scope: &Scope, #[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel, - ) -> Result { + ) -> ParseResult { let _scope = scope; let mut functions = BTreeMap::new(); @@ -3373,7 +3346,7 @@ impl Engine { &self, input: &mut TokenStream, state: &mut ParseState, - ) -> Result<(StaticVec, StaticVec>), ParseError> { + ) -> ParseResult<(StaticVec, StaticVec>)> { let mut statements = StaticVec::new_const(); let mut functions = BTreeMap::new(); @@ -3443,7 +3416,7 @@ impl Engine { state: &mut ParseState, scope: &Scope, #[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel, - ) -> Result { + ) -> ParseResult { let _scope = scope; let (statements, _lib) = self.parse_global_level(input, state)?; diff --git a/src/serde/de.rs b/src/serde/de.rs index bd055ce0..eb158707 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -1,8 +1,8 @@ //! Implement deserialization support of [`Dynamic`][crate::Dynamic] for [`serde`]. use crate::types::dynamic::Union; -use crate::{Dynamic, EvalAltResult, ImmutableString, LexError, Position}; -use serde::de::{DeserializeSeed, Error, IntoDeserializer, Visitor}; +use crate::{Dynamic, ImmutableString, LexError, Position, RhaiError, RhaiResultOf, ERR}; +use serde::de::{Error, IntoDeserializer, Visitor}; use serde::{Deserialize, Deserializer}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -26,12 +26,12 @@ impl<'de> DynamicDeserializer<'de> { Self { value } } /// Shortcut for a type conversion error. - fn type_error(&self) -> Result> { + fn type_error(&self) -> RhaiResultOf { self.type_error_str(type_name::()) } /// Shortcut for a type conversion error. - fn type_error_str(&self, error: &str) -> Result> { - Err(EvalAltResult::ErrorMismatchOutputType( + fn type_error_str(&self, error: &str) -> RhaiResultOf { + Err(ERR::ErrorMismatchOutputType( error.into(), self.value.type_name().into(), Position::NONE, @@ -42,7 +42,7 @@ impl<'de> DynamicDeserializer<'de> { &mut self, v: crate::INT, visitor: V, - ) -> Result> { + ) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return visitor.visit_i64(v); #[cfg(feature = "only_i32")] @@ -101,13 +101,11 @@ impl<'de> DynamicDeserializer<'de> { /// # Ok(()) /// # } /// ``` -pub fn from_dynamic<'de, T: Deserialize<'de>>( - value: &'de Dynamic, -) -> Result> { +pub fn from_dynamic<'de, T: Deserialize<'de>>(value: &'de Dynamic) -> RhaiResultOf { T::deserialize(&mut DynamicDeserializer::from_dynamic(value)) } -impl Error for Box { +impl Error for RhaiError { fn custom(err: T) -> Self { LexError::ImproperSymbol(String::new(), err.to_string()) .into_err(Position::NONE) @@ -116,9 +114,9 @@ impl Error for Box { } impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { - type Error = Box; + type Error = RhaiError; - fn deserialize_any>(self, visitor: V) -> Result> { + fn deserialize_any>(self, visitor: V) -> RhaiResultOf { match self.value.0 { Union::Unit(_, _, _) => self.deserialize_unit(visitor), Union::Bool(_, _, _) => self.deserialize_bool(visitor), @@ -172,11 +170,11 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } - fn deserialize_bool>(self, visitor: V) -> Result> { + fn deserialize_bool>(self, visitor: V) -> RhaiResultOf { visitor.visit_bool(self.value.as_bool().or_else(|_| self.type_error())?) } - fn deserialize_i8>(self, visitor: V) -> Result> { + fn deserialize_i8>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.value.as_int() { self.deserialize_int(v, visitor) } else { @@ -186,7 +184,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } - fn deserialize_i16>(self, visitor: V) -> Result> { + fn deserialize_i16>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.value.as_int() { self.deserialize_int(v, visitor) } else { @@ -196,7 +194,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } - fn deserialize_i32>(self, visitor: V) -> Result> { + fn deserialize_i32>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.value.as_int() { self.deserialize_int(v, visitor) } else if cfg!(feature = "only_i32") { @@ -208,7 +206,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } - fn deserialize_i64>(self, visitor: V) -> Result> { + fn deserialize_i64>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.value.as_int() { self.deserialize_int(v, visitor) } else if cfg!(not(feature = "only_i32")) { @@ -220,7 +218,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } - fn deserialize_i128>(self, visitor: V) -> Result> { + fn deserialize_i128>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.value.as_int() { self.deserialize_int(v, visitor) } else if cfg!(not(feature = "only_i32")) { @@ -232,7 +230,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } - fn deserialize_u8>(self, visitor: V) -> Result> { + fn deserialize_u8>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.value.as_int() { self.deserialize_int(v, visitor) } else { @@ -242,7 +240,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } - fn deserialize_u16>(self, visitor: V) -> Result> { + fn deserialize_u16>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.value.as_int() { self.deserialize_int(v, visitor) } else { @@ -252,7 +250,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } - fn deserialize_u32>(self, visitor: V) -> Result> { + fn deserialize_u32>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.value.as_int() { self.deserialize_int(v, visitor) } else { @@ -262,7 +260,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } - fn deserialize_u64>(self, visitor: V) -> Result> { + fn deserialize_u64>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.value.as_int() { self.deserialize_int(v, visitor) } else { @@ -272,7 +270,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } - fn deserialize_u128>(self, visitor: V) -> Result> { + fn deserialize_u128>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.value.as_int() { self.deserialize_int(v, visitor) } else { @@ -282,7 +280,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } - fn deserialize_f32>(self, _visitor: V) -> Result> { + fn deserialize_f32>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_float"))] return self .value @@ -306,7 +304,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { return self.type_error_str("f32"); } - fn deserialize_f64>(self, _visitor: V) -> Result> { + fn deserialize_f64>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_float"))] return self .value @@ -330,30 +328,24 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { return self.type_error_str("f64"); } - fn deserialize_char>(self, visitor: V) -> Result> { + fn deserialize_char>(self, visitor: V) -> RhaiResultOf { self.value .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_char(x)) } - fn deserialize_str>(self, visitor: V) -> Result> { + fn deserialize_str>(self, visitor: V) -> RhaiResultOf { self.value.downcast_ref::().map_or_else( || self.type_error(), |x| visitor.visit_borrowed_str(x.as_str()), ) } - fn deserialize_string>( - self, - visitor: V, - ) -> Result> { + fn deserialize_string>(self, visitor: V) -> RhaiResultOf { self.deserialize_str(visitor) } - fn deserialize_bytes>( - self, - _visitor: V, - ) -> Result> { + fn deserialize_bytes>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return self .value @@ -364,17 +356,11 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { return self.type_error(); } - fn deserialize_byte_buf>( - self, - visitor: V, - ) -> Result> { + fn deserialize_byte_buf>(self, visitor: V) -> RhaiResultOf { self.deserialize_bytes(visitor) } - fn deserialize_option>( - self, - visitor: V, - ) -> Result> { + fn deserialize_option>(self, visitor: V) -> RhaiResultOf { if self.value.is::<()>() { visitor.visit_none() } else { @@ -382,7 +368,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } - fn deserialize_unit>(self, visitor: V) -> Result> { + fn deserialize_unit>(self, visitor: V) -> RhaiResultOf { self.value .downcast_ref::<()>() .map_or_else(|| self.type_error(), |_| visitor.visit_unit()) @@ -392,7 +378,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { self, _name: &'static str, visitor: V, - ) -> Result> { + ) -> RhaiResultOf { self.deserialize_unit(visitor) } @@ -400,11 +386,11 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { self, _name: &'static str, visitor: V, - ) -> Result> { + ) -> RhaiResultOf { visitor.visit_newtype_struct(self) } - fn deserialize_seq>(self, _visitor: V) -> Result> { + fn deserialize_seq>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return self.value.downcast_ref::().map_or_else( || self.type_error(), @@ -415,11 +401,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { return self.type_error(); } - fn deserialize_tuple>( - self, - _len: usize, - visitor: V, - ) -> Result> { + fn deserialize_tuple>(self, _len: usize, visitor: V) -> RhaiResultOf { self.deserialize_seq(visitor) } @@ -428,11 +410,11 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { _name: &'static str, _len: usize, visitor: V, - ) -> Result> { + ) -> RhaiResultOf { self.deserialize_seq(visitor) } - fn deserialize_map>(self, _visitor: V) -> Result> { + fn deserialize_map>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return self.value.downcast_ref::().map_or_else( || self.type_error(), @@ -453,7 +435,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { _name: &'static str, _fields: &'static [&'static str], visitor: V, - ) -> Result> { + ) -> RhaiResultOf { self.deserialize_map(visitor) } @@ -462,7 +444,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { _name: &'static str, _variants: &'static [&'static str], visitor: V, - ) -> Result> { + ) -> RhaiResultOf { if let Some(s) = self.value.read_lock::() { visitor.visit_enum(s.as_str().into_deserializer()) } else { @@ -487,17 +469,11 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } - fn deserialize_identifier>( - self, - visitor: V, - ) -> Result> { + fn deserialize_identifier>(self, visitor: V) -> RhaiResultOf { self.deserialize_str(visitor) } - fn deserialize_ignored_any>( - self, - visitor: V, - ) -> Result> { + fn deserialize_ignored_any>(self, visitor: V) -> RhaiResultOf { self.deserialize_any(visitor) } } @@ -521,12 +497,12 @@ impl<'a, ITER: Iterator> IterateDynamicArray<'a, ITER> { impl<'a: 'de, 'de, ITER: Iterator> serde::de::SeqAccess<'de> for IterateDynamicArray<'a, ITER> { - type Error = Box; + type Error = RhaiError; - fn next_element_seed>( + fn next_element_seed>( &mut self, seed: T, - ) -> Result, Box> { + ) -> RhaiResultOf> { // Deserialize each item coming out of the iterator. match self.iter.next() { None => Ok(None), @@ -568,12 +544,12 @@ where KEYS: Iterator, VALUES: Iterator, { - type Error = Box; + type Error = RhaiError; - fn next_key_seed>( + fn next_key_seed>( &mut self, seed: K, - ) -> Result, Box> { + ) -> RhaiResultOf> { // Deserialize each `Identifier` key coming out of the keys iterator. match self.keys.next() { None => Ok(None), @@ -583,13 +559,13 @@ where } } - fn next_value_seed>( + fn next_value_seed>( &mut self, seed: V, - ) -> Result> { + ) -> RhaiResultOf { // Deserialize each value item coming out of the iterator. seed.deserialize(&mut DynamicDeserializer::from_dynamic( - self.values.next().expect("exists"), + self.values.next().unwrap(), )) } } @@ -602,13 +578,13 @@ struct EnumDeserializer<'t, 'de: 't> { #[cfg(not(feature = "no_object"))] impl<'t, 'de> serde::de::EnumAccess<'de> for EnumDeserializer<'t, 'de> { - type Error = Box; + type Error = RhaiError; type Variant = Self; - fn variant_seed>( + fn variant_seed>( self, seed: V, - ) -> Result<(V::Value, Self::Variant), Self::Error> { + ) -> RhaiResultOf<(V::Value, Self::Variant)> { seed.deserialize(self.tag.into_deserializer()) .map(|v| (v, self)) } @@ -616,24 +592,20 @@ impl<'t, 'de> serde::de::EnumAccess<'de> for EnumDeserializer<'t, 'de> { #[cfg(not(feature = "no_object"))] impl<'t, 'de> serde::de::VariantAccess<'de> for EnumDeserializer<'t, 'de> { - type Error = Box; + type Error = RhaiError; - fn unit_variant(mut self) -> Result<(), Self::Error> { + fn unit_variant(mut self) -> RhaiResultOf<()> { Deserialize::deserialize(&mut self.content) } - fn newtype_variant_seed>( + fn newtype_variant_seed>( mut self, seed: T, - ) -> Result { + ) -> RhaiResultOf { seed.deserialize(&mut self.content) } - fn tuple_variant>( - mut self, - len: usize, - visitor: V, - ) -> Result { + fn tuple_variant>(mut self, len: usize, visitor: V) -> RhaiResultOf { self.content.deserialize_tuple(len, visitor) } @@ -641,7 +613,7 @@ impl<'t, 'de> serde::de::VariantAccess<'de> for EnumDeserializer<'t, 'de> { mut self, fields: &'static [&'static str], visitor: V, - ) -> Result { + ) -> RhaiResultOf { self.content.deserialize_struct("", fields, visitor) } } diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index 6470e528..ce5f3f3d 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -1,3 +1,5 @@ +//! Serialization of functions metadata. + #![cfg(feature = "metadata")] use crate::module::calc_native_fn_hash; @@ -48,33 +50,24 @@ impl From for FnAccess { #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -struct FnParam { - pub name: Box, +struct FnParam<'a> { #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - pub typ: Option>, + pub name: Option<&'a str>, + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub typ: Option<&'a str>, } -impl PartialOrd for FnParam { +impl PartialOrd for FnParam<'_> { fn partial_cmp(&self, other: &Self) -> Option { Some(match self.name.partial_cmp(&other.name).expect("succeed") { Ordering::Less => Ordering::Less, Ordering::Greater => Ordering::Greater, - Ordering::Equal => match (self.typ.is_none(), other.typ.is_none()) { - (true, true) => Ordering::Equal, - (true, false) => Ordering::Greater, - (false, true) => Ordering::Less, - (false, false) => self - .typ - .as_ref() - .expect("`Some`") - .partial_cmp(other.typ.as_ref().expect("`Some`")) - .expect("succeed"), - }, + Ordering::Equal => self.typ.partial_cmp(&other.typ).expect("succeed"), }) } } -impl Ord for FnParam { +impl Ord for FnParam<'_> { fn cmp(&self, other: &Self) -> Ordering { match self.name.cmp(&other.name) { Ordering::Equal => self.typ.cmp(&other.typ), @@ -85,7 +78,7 @@ impl Ord for FnParam { #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -struct FnMetadata { +struct FnMetadata<'a> { pub base_hash: u64, pub full_hash: u64, pub namespace: FnNamespace, @@ -95,21 +88,21 @@ struct FnMetadata { pub typ: FnType, pub num_params: usize, #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub params: Vec, + pub params: Vec>, #[serde(default, skip_serializing_if = "Option::is_none")] - pub return_type: Option>, + pub return_type_name: Option<&'a str>, pub signature: String, #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub doc_comments: Vec>, + pub doc_comments: Vec<&'a str>, } -impl PartialOrd for FnMetadata { +impl PartialOrd for FnMetadata<'_> { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for FnMetadata { +impl Ord for FnMetadata<'_> { fn cmp(&self, other: &Self) -> Ordering { match self.name.cmp(&other.name) { Ordering::Equal => match self.num_params.cmp(&other.num_params) { @@ -121,8 +114,8 @@ impl Ord for FnMetadata { } } -impl From<&crate::module::FuncInfo> for FnMetadata { - fn from(info: &crate::module::FuncInfo) -> Self { +impl<'a> From<&'a crate::module::FuncInfo> for FnMetadata<'a> { + fn from(info: &'a crate::module::FuncInfo) -> Self { let base_hash = calc_fn_hash(&info.name, info.params); let (typ, full_hash) = if info.func.is_script() { (FnType::Script, base_hash) @@ -142,41 +135,38 @@ impl From<&crate::module::FuncInfo> for FnMetadata { typ, num_params: info.params, params: info - .param_names + .param_names_and_types .iter() - .take(info.params) .map(|s| { let mut seg = s.splitn(2, ':'); - let name = seg - .next() - .map(|s| s.trim().into()) - .unwrap_or_else(|| "_".into()); - let typ = seg.next().map(|s| s.trim().into()); + let name = match seg.next().map(&str::trim).unwrap_or("_") { + "_" => None, + s => Some(s), + }; + let typ = seg.next().map(&str::trim); FnParam { name, typ } }) .collect(), - return_type: info - .param_names - .last() - .map(|s| s.as_str().into()) - .or_else(|| Some("()".into())), + return_type_name: match info.return_type_name.as_str() { + "" | "()" => None, + ty => Some(ty), + }, signature: info.gen_signature(), doc_comments: if info.func.is_script() { #[cfg(feature = "no_function")] - { - unreachable!("scripted functions should not exist under no_function") - } + unreachable!("script-defined functions should not exist under no_function"); + #[cfg(not(feature = "no_function"))] - { - info.func - .get_script_fn_def() - .expect("scripted function") - .comments - .as_ref() - .map_or_else(|| Vec::new(), |v| v.to_vec()) - } + info.func + .get_script_fn_def() + .expect("script-defined function") + .comments + .as_ref() + .map_or_else(|| Vec::new(), |v| v.iter().map(|s| &**s).collect()) } else { - Vec::new() + info.comments + .as_ref() + .map_or_else(|| Vec::new(), |v| v.iter().map(|s| &**s).collect()) }, } } @@ -184,14 +174,14 @@ impl From<&crate::module::FuncInfo> for FnMetadata { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -struct ModuleMetadata { +struct ModuleMetadata<'a> { #[serde(skip_serializing_if = "BTreeMap::is_empty")] - pub modules: BTreeMap, + pub modules: BTreeMap<&'a str, Self>, #[serde(skip_serializing_if = "Vec::is_empty")] - pub functions: Vec, + pub functions: Vec>, } -impl ModuleMetadata { +impl ModuleMetadata<'_> { #[inline(always)] pub fn new() -> Self { Self { @@ -201,22 +191,21 @@ impl ModuleMetadata { } } -impl From<&crate::Module> for ModuleMetadata { - fn from(module: &crate::Module) -> Self { +impl<'a> From<&'a crate::Module> for ModuleMetadata<'a> { + fn from(module: &'a crate::Module) -> Self { let mut functions: Vec<_> = module.iter_fn().map(|f| f.into()).collect(); functions.sort(); Self { modules: module .iter_sub_modules() - .map(|(name, m)| (name.to_string(), m.as_ref().into())) + .map(|(name, m)| (name, m.as_ref().into())) .collect(), functions, } } } -#[cfg(feature = "metadata")] impl Engine { /// _(metadata)_ Generate a list of all functions (including those defined in an /// [`AST`][crate::AST]) in JSON format. @@ -235,20 +224,14 @@ impl Engine { let _ast = ast; let mut global = ModuleMetadata::new(); - if include_global { - self.global_modules - .iter() - .take(self.global_modules.len() - 1) - .flat_map(|m| m.iter_fn()) - .for_each(|f| global.functions.push(f.into())); - } - self.global_sub_modules.iter().for_each(|(name, m)| { - global.modules.insert(name.to_string(), m.as_ref().into()); + global.modules.insert(name, m.as_ref().into()); }); - self.global_namespace() - .iter_fn() + self.global_modules + .iter() + .take(if include_global { usize::MAX } else { 1 }) + .flat_map(|m| m.iter_fn()) .for_each(|f| global.functions.push(f.into())); #[cfg(not(feature = "no_function"))] diff --git a/src/serde/ser.rs b/src/serde/ser.rs index b415d8fc..d85cea90 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -1,6 +1,6 @@ //! Implement serialization support of [`Dynamic`][crate::Dynamic] for [`serde`]. -use crate::{Dynamic, EvalAltResult, Position, RhaiResult}; +use crate::{Dynamic, Position, RhaiError, RhaiResult, RhaiResultOf, ERR}; use serde::ser::{ Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeTuple, SerializeTupleStruct, }; @@ -81,55 +81,56 @@ pub fn to_dynamic(value: T) -> RhaiResult { value.serialize(&mut s) } -impl Error for Box { +impl Error for RhaiError { fn custom(err: T) -> Self { - EvalAltResult::ErrorRuntime(err.to_string().into(), Position::NONE).into() + ERR::ErrorRuntime(err.to_string().into(), Position::NONE).into() } } impl Serializer for &mut DynamicSerializer { type Ok = Dynamic; - type Error = Box; + type Error = RhaiError; type SerializeSeq = DynamicSerializer; type SerializeTuple = DynamicSerializer; type SerializeTupleStruct = DynamicSerializer; - #[cfg(not(any(feature = "no_object", feature = "no_index")))] + #[cfg(not(feature = "no_object"))] + #[cfg(not(feature = "no_index"))] type SerializeTupleVariant = TupleVariantSerializer; #[cfg(any(feature = "no_object", feature = "no_index"))] - type SerializeTupleVariant = serde::ser::Impossible>; + type SerializeTupleVariant = serde::ser::Impossible; type SerializeMap = DynamicSerializer; type SerializeStruct = DynamicSerializer; #[cfg(not(feature = "no_object"))] type SerializeStructVariant = StructVariantSerializer; #[cfg(feature = "no_object")] - type SerializeStructVariant = serde::ser::Impossible>; + type SerializeStructVariant = serde::ser::Impossible; - fn serialize_bool(self, v: bool) -> Result> { + fn serialize_bool(self, v: bool) -> RhaiResultOf { Ok(v.into()) } - fn serialize_i8(self, v: i8) -> Result> { + fn serialize_i8(self, v: i8) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return self.serialize_i64(i64::from(v)); #[cfg(feature = "only_i32")] return self.serialize_i32(i32::from(v)); } - fn serialize_i16(self, v: i16) -> Result> { + fn serialize_i16(self, v: i16) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return self.serialize_i64(i64::from(v)); #[cfg(feature = "only_i32")] return self.serialize_i32(i32::from(v)); } - fn serialize_i32(self, v: i32) -> Result> { + fn serialize_i32(self, v: i32) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return self.serialize_i64(i64::from(v)); #[cfg(feature = "only_i32")] return Ok(v.into()); } - fn serialize_i64(self, v: i64) -> Result> { + fn serialize_i64(self, v: i64) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] { Ok(v.into()) @@ -142,7 +143,7 @@ impl Serializer for &mut DynamicSerializer { } } - fn serialize_i128(self, v: i128) -> Result> { + fn serialize_i128(self, v: i128) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] if v > i64::MAX as i128 { Ok(Dynamic::from(v)) @@ -157,21 +158,21 @@ impl Serializer for &mut DynamicSerializer { } } - fn serialize_u8(self, v: u8) -> Result> { + fn serialize_u8(self, v: u8) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return self.serialize_i64(i64::from(v)); #[cfg(feature = "only_i32")] return self.serialize_i32(i32::from(v)); } - fn serialize_u16(self, v: u16) -> Result> { + fn serialize_u16(self, v: u16) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return self.serialize_i64(i64::from(v)); #[cfg(feature = "only_i32")] return self.serialize_i32(i32::from(v)); } - fn serialize_u32(self, v: u32) -> Result> { + fn serialize_u32(self, v: u32) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] { self.serialize_i64(i64::from(v)) @@ -184,7 +185,7 @@ impl Serializer for &mut DynamicSerializer { } } - fn serialize_u64(self, v: u64) -> Result> { + fn serialize_u64(self, v: u64) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] if v > i64::MAX as u64 { Ok(Dynamic::from(v)) @@ -199,7 +200,7 @@ impl Serializer for &mut DynamicSerializer { } } - fn serialize_u128(self, v: u128) -> Result> { + fn serialize_u128(self, v: u128) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] if v > i64::MAX as u128 { Ok(Dynamic::from(v)) @@ -214,7 +215,7 @@ impl Serializer for &mut DynamicSerializer { } } - fn serialize_f32(self, v: f32) -> Result> { + fn serialize_f32(self, v: f32) -> RhaiResultOf { #[cfg(any(not(feature = "no_float"), not(feature = "decimal")))] return Ok(Dynamic::from(v)); @@ -230,7 +231,7 @@ impl Serializer for &mut DynamicSerializer { } } - fn serialize_f64(self, v: f64) -> Result> { + fn serialize_f64(self, v: f64) -> RhaiResultOf { #[cfg(any(not(feature = "no_float"), not(feature = "decimal")))] return Ok(Dynamic::from(v)); @@ -246,20 +247,20 @@ impl Serializer for &mut DynamicSerializer { } } - fn serialize_char(self, v: char) -> Result> { + fn serialize_char(self, v: char) -> RhaiResultOf { Ok(v.into()) } - fn serialize_str(self, v: &str) -> Result> { + fn serialize_str(self, v: &str) -> RhaiResultOf { Ok(v.into()) } - fn serialize_bytes(self, _v: &[u8]) -> Result> { + fn serialize_bytes(self, _v: &[u8]) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(Dynamic::from_blob(_v.to_vec())); #[cfg(feature = "no_index")] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "BLOB's are not supported with 'no_index'".into(), Position::NONE, @@ -267,22 +268,19 @@ impl Serializer for &mut DynamicSerializer { .into()); } - fn serialize_none(self) -> Result> { + fn serialize_none(self) -> RhaiResultOf { Ok(Dynamic::UNIT) } - fn serialize_some( - self, - value: &T, - ) -> Result> { + fn serialize_some(self, value: &T) -> RhaiResultOf { value.serialize(&mut *self) } - fn serialize_unit(self) -> Result> { + fn serialize_unit(self) -> RhaiResultOf { Ok(Dynamic::UNIT) } - fn serialize_unit_struct(self, _name: &'static str) -> Result> { + fn serialize_unit_struct(self, _name: &'static str) -> RhaiResultOf { self.serialize_unit() } @@ -291,7 +289,7 @@ impl Serializer for &mut DynamicSerializer { _name: &'static str, _variant_index: u32, variant: &'static str, - ) -> Result> { + ) -> RhaiResultOf { self.serialize_str(variant) } @@ -299,7 +297,7 @@ impl Serializer for &mut DynamicSerializer { self, _name: &'static str, value: &T, - ) -> Result> { + ) -> RhaiResultOf { value.serialize(&mut *self) } @@ -309,14 +307,14 @@ impl Serializer for &mut DynamicSerializer { _variant_index: u32, _variant: &'static str, _value: &T, - ) -> Result> { + ) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] { let content = to_dynamic(_value)?; make_variant(_variant, content) } #[cfg(feature = "no_object")] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported with 'no_object'".into(), Position::NONE, @@ -324,11 +322,11 @@ impl Serializer for &mut DynamicSerializer { .into()); } - fn serialize_seq(self, _len: Option) -> Result> { + fn serialize_seq(self, _len: Option) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(DynamicSerializer::new(crate::Array::new().into())); #[cfg(feature = "no_index")] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "arrays are not supported with 'no_index'".into(), Position::NONE, @@ -336,7 +334,7 @@ impl Serializer for &mut DynamicSerializer { .into()); } - fn serialize_tuple(self, len: usize) -> Result> { + fn serialize_tuple(self, len: usize) -> RhaiResultOf { self.serialize_seq(Some(len)) } @@ -344,7 +342,7 @@ impl Serializer for &mut DynamicSerializer { self, _name: &'static str, len: usize, - ) -> Result> { + ) -> RhaiResultOf { self.serialize_seq(Some(len)) } @@ -354,7 +352,7 @@ impl Serializer for &mut DynamicSerializer { _variant_index: u32, _variant: &'static str, _len: usize, - ) -> Result> { + ) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] return Ok(TupleVariantSerializer { @@ -362,7 +360,7 @@ impl Serializer for &mut DynamicSerializer { array: crate::Array::with_capacity(_len), }); #[cfg(any(feature = "no_object", feature = "no_index"))] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "tuples are not supported with 'no_index' or 'no_object'".into(), Position::NONE, @@ -370,11 +368,11 @@ impl Serializer for &mut DynamicSerializer { .into()); } - fn serialize_map(self, _len: Option) -> Result> { + fn serialize_map(self, _len: Option) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return Ok(DynamicSerializer::new(crate::Map::new().into())); #[cfg(feature = "no_object")] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported with 'no_object'".into(), Position::NONE, @@ -386,7 +384,7 @@ impl Serializer for &mut DynamicSerializer { self, _name: &'static str, len: usize, - ) -> Result> { + ) -> RhaiResultOf { self.serialize_map(Some(len)) } @@ -396,14 +394,14 @@ impl Serializer for &mut DynamicSerializer { _variant_index: u32, _variant: &'static str, _len: usize, - ) -> Result> { + ) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return Ok(StructVariantSerializer { variant: _variant, map: crate::Map::new(), }); #[cfg(feature = "no_object")] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported with 'no_object'".into(), Position::NONE, @@ -414,12 +412,9 @@ impl Serializer for &mut DynamicSerializer { impl SerializeSeq for DynamicSerializer { type Ok = Dynamic; - type Error = Box; + type Error = RhaiError; - fn serialize_element( - &mut self, - _value: &T, - ) -> Result<(), Box> { + fn serialize_element(&mut self, _value: &T) -> RhaiResultOf<()> { #[cfg(not(feature = "no_index"))] { let _value = _value.serialize(&mut *self)?; @@ -428,7 +423,7 @@ impl SerializeSeq for DynamicSerializer { Ok(()) } #[cfg(feature = "no_index")] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "arrays are not supported with 'no_index'".into(), Position::NONE, @@ -437,11 +432,11 @@ impl SerializeSeq for DynamicSerializer { } // Close the sequence. - fn end(self) -> Result> { + fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(self._value); #[cfg(feature = "no_index")] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "arrays are not supported with 'no_index'".into(), Position::NONE, @@ -452,12 +447,9 @@ impl SerializeSeq for DynamicSerializer { impl SerializeTuple for DynamicSerializer { type Ok = Dynamic; - type Error = Box; + type Error = RhaiError; - fn serialize_element( - &mut self, - _value: &T, - ) -> Result<(), Box> { + fn serialize_element(&mut self, _value: &T) -> RhaiResultOf<()> { #[cfg(not(feature = "no_index"))] { let _value = _value.serialize(&mut *self)?; @@ -466,7 +458,7 @@ impl SerializeTuple for DynamicSerializer { Ok(()) } #[cfg(feature = "no_index")] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "tuples are not supported with 'no_index'".into(), Position::NONE, @@ -474,11 +466,11 @@ impl SerializeTuple for DynamicSerializer { .into()); } - fn end(self) -> Result> { + fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(self._value); #[cfg(feature = "no_index")] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "tuples are not supported with 'no_index'".into(), Position::NONE, @@ -489,12 +481,9 @@ impl SerializeTuple for DynamicSerializer { impl SerializeTupleStruct for DynamicSerializer { type Ok = Dynamic; - type Error = Box; + type Error = RhaiError; - fn serialize_field( - &mut self, - _value: &T, - ) -> Result<(), Box> { + fn serialize_field(&mut self, _value: &T) -> RhaiResultOf<()> { #[cfg(not(feature = "no_index"))] { let _value = _value.serialize(&mut *self)?; @@ -503,7 +492,7 @@ impl SerializeTupleStruct for DynamicSerializer { Ok(()) } #[cfg(feature = "no_index")] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "tuples are not supported with 'no_index'".into(), Position::NONE, @@ -511,11 +500,11 @@ impl SerializeTupleStruct for DynamicSerializer { .into()); } - fn end(self) -> Result> { + fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(self._value); #[cfg(feature = "no_index")] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "tuples are not supported with 'no_index'".into(), Position::NONE, @@ -526,16 +515,16 @@ impl SerializeTupleStruct for DynamicSerializer { impl SerializeMap for DynamicSerializer { type Ok = Dynamic; - type Error = Box; + type Error = RhaiError; - fn serialize_key(&mut self, _key: &T) -> Result<(), Box> { + fn serialize_key(&mut self, _key: &T) -> RhaiResultOf<()> { #[cfg(not(feature = "no_object"))] { self._key = _key.serialize(&mut *self)?; Ok(()) } #[cfg(feature = "no_object")] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported with 'no_object'".into(), Position::NONE, @@ -543,20 +532,13 @@ impl SerializeMap for DynamicSerializer { .into()); } - fn serialize_value( - &mut self, - _value: &T, - ) -> Result<(), Box> { + fn serialize_value(&mut self, _value: &T) -> RhaiResultOf<()> { #[cfg(not(feature = "no_object"))] { let key = std::mem::take(&mut self._key) .into_immutable_string() .map_err(|typ| { - EvalAltResult::ErrorMismatchDataType( - "string".into(), - typ.into(), - Position::NONE, - ) + ERR::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE) })?; let _value = _value.serialize(&mut *self)?; let map = self._value.downcast_mut::().unwrap(); @@ -564,7 +546,7 @@ impl SerializeMap for DynamicSerializer { Ok(()) } #[cfg(feature = "no_object")] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported with 'no_object'".into(), Position::NONE, @@ -576,12 +558,12 @@ impl SerializeMap for DynamicSerializer { &mut self, _key: &K, _value: &T, - ) -> Result<(), Box> { + ) -> RhaiResultOf<()> { #[cfg(not(feature = "no_object"))] { let _key: Dynamic = _key.serialize(&mut *self)?; let _key = _key.into_immutable_string().map_err(|typ| { - EvalAltResult::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE) + ERR::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE) })?; let _value = _value.serialize(&mut *self)?; let map = self._value.downcast_mut::().unwrap(); @@ -589,7 +571,7 @@ impl SerializeMap for DynamicSerializer { Ok(()) } #[cfg(feature = "no_object")] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported with 'no_object'".into(), Position::NONE, @@ -597,11 +579,11 @@ impl SerializeMap for DynamicSerializer { .into()); } - fn end(self) -> Result> { + fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return Ok(self._value); #[cfg(feature = "no_object")] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported with 'no_object'".into(), Position::NONE, @@ -612,13 +594,13 @@ impl SerializeMap for DynamicSerializer { impl SerializeStruct for DynamicSerializer { type Ok = Dynamic; - type Error = Box; + type Error = RhaiError; fn serialize_field( &mut self, _key: &'static str, _value: &T, - ) -> Result<(), Box> { + ) -> RhaiResultOf<()> { #[cfg(not(feature = "no_object"))] { let _value = _value.serialize(&mut *self)?; @@ -627,7 +609,7 @@ impl SerializeStruct for DynamicSerializer { Ok(()) } #[cfg(feature = "no_object")] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported with 'no_object'".into(), Position::NONE, @@ -635,11 +617,11 @@ impl SerializeStruct for DynamicSerializer { .into()); } - fn end(self) -> Result> { + fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return Ok(self._value); #[cfg(feature = "no_object")] - return Err(EvalAltResult::ErrorMismatchDataType( + return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported with 'no_object'".into(), Position::NONE, @@ -648,27 +630,26 @@ impl SerializeStruct for DynamicSerializer { } } -#[cfg(not(any(feature = "no_object", feature = "no_index")))] +#[cfg(not(feature = "no_object"))] +#[cfg(not(feature = "no_index"))] struct TupleVariantSerializer { variant: &'static str, array: crate::Array, } -#[cfg(not(any(feature = "no_object", feature = "no_index")))] +#[cfg(not(feature = "no_object"))] +#[cfg(not(feature = "no_index"))] impl serde::ser::SerializeTupleVariant for TupleVariantSerializer { type Ok = Dynamic; - type Error = Box; + type Error = RhaiError; - fn serialize_field( - &mut self, - value: &T, - ) -> Result<(), Box> { + fn serialize_field(&mut self, value: &T) -> RhaiResultOf<()> { let value = to_dynamic(value)?; self.array.push(value); Ok(()) } - fn end(self) -> Result> { + fn end(self) -> RhaiResultOf { make_variant(self.variant, self.array.into()) } } @@ -682,19 +663,19 @@ struct StructVariantSerializer { #[cfg(not(feature = "no_object"))] impl serde::ser::SerializeStructVariant for StructVariantSerializer { type Ok = Dynamic; - type Error = Box; + type Error = RhaiError; fn serialize_field( &mut self, key: &'static str, value: &T, - ) -> Result<(), Box> { + ) -> RhaiResultOf<()> { let value = to_dynamic(value)?; self.map.insert(key.into(), value); Ok(()) } - fn end(self) -> Result> { + fn end(self) -> RhaiResultOf { make_variant(self.variant, self.map.into()) } } diff --git a/src/serde/str.rs b/src/serde/str.rs index 1ad17537..fd19de8c 100644 --- a/src/serde/str.rs +++ b/src/serde/str.rs @@ -1,6 +1,6 @@ //! Implement deserialization support of [`ImmutableString`][crate::ImmutableString] for [`serde`]. -use crate::{EvalAltResult, Position}; +use crate::{Position, RhaiError, RhaiResultOf, ERR}; use serde::de::{Deserializer, Visitor}; use std::any::type_name; #[cfg(feature = "no_std")] @@ -18,102 +18,93 @@ impl<'a> StringSliceDeserializer<'a> { Self { value } } /// Shortcut for a type conversion error. - fn type_error(&self) -> Result> { - Err(EvalAltResult::ErrorMismatchOutputType( - type_name::().into(), - "string".into(), - Position::NONE, + fn type_error(&self) -> RhaiResultOf { + Err( + ERR::ErrorMismatchOutputType(type_name::().into(), "string".into(), Position::NONE) + .into(), ) - .into()) } } impl<'de> Deserializer<'de> for &mut StringSliceDeserializer<'de> { - type Error = Box; + type Error = RhaiError; - fn deserialize_any>(self, v: V) -> Result> { + fn deserialize_any>(self, v: V) -> RhaiResultOf { self.deserialize_str(v) } - fn deserialize_bool>(self, _: V) -> Result> { + fn deserialize_bool>(self, _: V) -> RhaiResultOf { self.type_error() } - fn deserialize_i8>(self, _: V) -> Result> { + fn deserialize_i8>(self, _: V) -> RhaiResultOf { self.type_error() } - fn deserialize_i16>(self, _: V) -> Result> { + fn deserialize_i16>(self, _: V) -> RhaiResultOf { self.type_error() } - fn deserialize_i32>(self, _: V) -> Result> { + fn deserialize_i32>(self, _: V) -> RhaiResultOf { self.type_error() } - fn deserialize_i64>(self, _: V) -> Result> { + fn deserialize_i64>(self, _: V) -> RhaiResultOf { self.type_error() } - fn deserialize_u8>(self, _: V) -> Result> { + fn deserialize_u8>(self, _: V) -> RhaiResultOf { self.type_error() } - fn deserialize_u16>(self, _: V) -> Result> { + fn deserialize_u16>(self, _: V) -> RhaiResultOf { self.type_error() } - fn deserialize_u32>(self, _: V) -> Result> { + fn deserialize_u32>(self, _: V) -> RhaiResultOf { self.type_error() } - fn deserialize_u64>(self, _: V) -> Result> { + fn deserialize_u64>(self, _: V) -> RhaiResultOf { self.type_error() } - fn deserialize_f32>(self, _: V) -> Result> { + fn deserialize_f32>(self, _: V) -> RhaiResultOf { self.type_error() } - fn deserialize_f64>(self, _: V) -> Result> { + fn deserialize_f64>(self, _: V) -> RhaiResultOf { self.type_error() } - fn deserialize_char>(self, _: V) -> Result> { + fn deserialize_char>(self, _: V) -> RhaiResultOf { self.type_error() } - fn deserialize_str>(self, v: V) -> Result> { + fn deserialize_str>(self, v: V) -> RhaiResultOf { // Only allow deserialization into a string. v.visit_borrowed_str(self.value) } - fn deserialize_string>( - self, - visitor: V, - ) -> Result> { + fn deserialize_string>(self, visitor: V) -> RhaiResultOf { self.deserialize_str(visitor) } - fn deserialize_bytes>(self, _: V) -> Result> { + fn deserialize_bytes>(self, _: V) -> RhaiResultOf { self.type_error() } - fn deserialize_byte_buf>(self, _: V) -> Result> { + fn deserialize_byte_buf>(self, _: V) -> RhaiResultOf { self.type_error() } - fn deserialize_option>(self, _: V) -> Result> { + fn deserialize_option>(self, _: V) -> RhaiResultOf { self.type_error() } - fn deserialize_unit>(self, _: V) -> Result> { + fn deserialize_unit>(self, _: V) -> RhaiResultOf { self.type_error() } fn deserialize_unit_struct>( self, _name: &'static str, v: V, - ) -> Result> { + ) -> RhaiResultOf { self.deserialize_unit(v) } fn deserialize_newtype_struct>( self, _name: &'static str, v: V, - ) -> Result> { + ) -> RhaiResultOf { v.visit_newtype_struct(self) } - fn deserialize_seq>(self, _: V) -> Result> { + fn deserialize_seq>(self, _: V) -> RhaiResultOf { self.type_error() } - fn deserialize_tuple>( - self, - _len: usize, - v: V, - ) -> Result> { + fn deserialize_tuple>(self, _len: usize, v: V) -> RhaiResultOf { self.deserialize_seq(v) } fn deserialize_tuple_struct>( @@ -121,10 +112,10 @@ impl<'de> Deserializer<'de> for &mut StringSliceDeserializer<'de> { _name: &'static str, _len: usize, v: V, - ) -> Result> { + ) -> RhaiResultOf { self.deserialize_seq(v) } - fn deserialize_map>(self, _: V) -> Result> { + fn deserialize_map>(self, _: V) -> RhaiResultOf { self.type_error() } fn deserialize_struct>( @@ -132,7 +123,7 @@ impl<'de> Deserializer<'de> for &mut StringSliceDeserializer<'de> { _name: &'static str, _fields: &'static [&'static str], v: V, - ) -> Result> { + ) -> RhaiResultOf { self.deserialize_map(v) } fn deserialize_enum>( @@ -140,16 +131,13 @@ impl<'de> Deserializer<'de> for &mut StringSliceDeserializer<'de> { _name: &'static str, _variants: &'static [&'static str], _: V, - ) -> Result> { + ) -> RhaiResultOf { self.type_error() } - fn deserialize_identifier>(self, v: V) -> Result> { + fn deserialize_identifier>(self, v: V) -> RhaiResultOf { self.deserialize_str(v) } - fn deserialize_ignored_any>( - self, - v: V, - ) -> Result> { + fn deserialize_ignored_any>(self, v: V) -> RhaiResultOf { self.deserialize_any(v) } } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 9e93f582..d4827dfe 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -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, LexError, StaticVec, INT}; +use crate::{Engine, LexError, StaticVec, INT, UNSIGNED_INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ @@ -213,6 +213,16 @@ impl Position { #[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 { @@ -240,7 +250,7 @@ impl fmt::Display for Position { #[cfg(not(feature = "no_position"))] write!(f, "line {}, position {}", self.line, self.pos)?; #[cfg(feature = "no_position")] - unreachable!(); + unreachable!("no position"); } Ok(()) @@ -275,7 +285,7 @@ impl Add for Position { }, }; #[cfg(feature = "no_position")] - unreachable!(); + unreachable!("no position"); } } } @@ -684,11 +694,9 @@ impl Token { /// Reverse lookup a token from a piece of syntax. #[must_use] - pub fn lookup_from_syntax(syntax: impl AsRef) -> Option { + pub fn lookup_from_syntax(syntax: &str) -> Option { use Token::*; - let syntax = syntax.as_ref(); - Some(match syntax { "{" => LeftBrace, "}" => RightBrace, @@ -776,11 +784,16 @@ impl Token { #[cfg(feature = "no_module")] "import" | "export" | "as" => Reserved(syntax.into()), - "===" | "!==" | "->" | "<-" | ":=" | "~" | "::<" | "(*" | "*)" | "#" | "#!" - | "public" | "protected" | "super" | "new" | "use" | "module" | "package" | "var" - | "static" | "shared" | "with" | "goto" | "exit" | "match" | "case" | "default" - | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync" | "async" | "await" - | "yield" => Reserved(syntax.into()), + // List of reserved operators + "===" | "!==" | "->" | "<-" | ":=" | "~" | "::<" | "(*" | "*)" | "#" | "#!" => { + Reserved(syntax.into()) + } + + // List of reserved keywords + "public" | "protected" | "super" | "new" | "use" | "module" | "package" | "var" + | "static" | "shared" | "with" | "is" | "goto" | "exit" | "match" | "case" + | "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync" + | "async" | "await" | "yield" => Reserved(syntax.into()), KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS | KEYWORD_IS_DEF_VAR => { @@ -873,13 +886,6 @@ impl Token { use Token::*; Precedence::new(match self { - // Assignments are not considered expressions - set to zero - Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | PowerOfAssign - | LeftShiftAssign | RightShiftAssign | AndAssign | OrAssign | XOrAssign - | ModuloAssign => 0, - - ExclusiveRange | InclusiveRange => 10, - Or | XOr | Pipe => 30, And | Ampersand => 60, @@ -890,6 +896,8 @@ impl Token { LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 130, + ExclusiveRange | InclusiveRange => 140, + Plus | Minus => 150, Divide | Multiply | Modulo => 180, @@ -898,8 +906,6 @@ impl Token { LeftShift | RightShift => 210, - Period => 240, - _ => 0, }) } @@ -910,14 +916,6 @@ impl Token { use Token::*; match self { - // Assignments bind to the right - Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | PowerOfAssign - | LeftShiftAssign | RightShiftAssign | AndAssign | OrAssign | XOrAssign - | ModuloAssign => true, - - // Property access binds to the right - Period => true, - // Exponentiation binds to the right PowerOf => true, @@ -1026,7 +1024,7 @@ pub trait InputStream { fn peek_next(&mut self) -> Option; } -/// _(internals)_ Parse a string literal ended by `termination_char`. +/// _(internals)_ Parse a string literal ended by a specified termination character. /// Exported under the `internals` feature only. /// /// Returns the parsed string and a boolean indicating whether the string is @@ -1034,7 +1032,7 @@ pub trait InputStream { /// /// # Returns /// -/// |Type |Return Value |`state.is_within_text_terminated_by`| +/// | Type | Return Value |`state.is_within_text_terminated_by`| /// |---------------------------------|:--------------------------:|:----------------------------------:| /// |`"hello"` |`StringConstant("hello")` |`None` | /// |`"hello`_{LF}_ or _{EOF}_ |`LexError` |`None` | @@ -1061,8 +1059,8 @@ pub fn parse_string_literal( state: &mut TokenizeState, pos: &mut Position, termination_char: char, - continuation: bool, verbatim: bool, + allow_line_continuation: bool, allow_interpolation: bool, ) -> Result<(Box, bool), (LexError, Position)> { let mut result = String::with_capacity(12); @@ -1091,7 +1089,7 @@ pub fn parse_string_literal( pos.advance(); break; } - None if continuation && !escape.is_empty() => { + None if allow_line_continuation && !escape.is_empty() => { assert_eq!(escape, "\\", "unexpected escape {} at end of line", escape); pos.advance(); break; @@ -1159,7 +1157,7 @@ pub fn parse_string_literal( 'x' => 2, 'u' => 4, 'U' => 8, - _ => unreachable!(), + c => unreachable!("x or u or U expected but gets '{}'", c), }; for _ in 0..len { @@ -1211,14 +1209,14 @@ pub fn parse_string_literal( } // Line continuation - '\n' if continuation && !escape.is_empty() => { + '\n' if allow_line_continuation && !escape.is_empty() => { assert_eq!(escape, "\\", "unexpected escape {} at end of line", escape); escape.clear(); pos.new_line(); #[cfg(not(feature = "no_position"))] { - let start_position = start.position().expect("start position"); + let start_position = start.position().unwrap(); skip_whitespace_until = start_position + 1; } } @@ -1239,8 +1237,7 @@ pub fn parse_string_literal( // Whitespace to skip #[cfg(not(feature = "no_position"))] - _ if next_char.is_whitespace() - && pos.position().expect("position") < skip_whitespace_until => {} + _ if next_char.is_whitespace() && pos.position().unwrap() < skip_whitespace_until => {} // All other characters _ => { @@ -1319,7 +1316,7 @@ fn scan_block_comment( level } -/// _(internals)_ Get the next token from the `stream`. +/// _(internals)_ Get the next token from the input stream. /// Exported under the `internals` feature only. #[inline] #[must_use] @@ -1355,9 +1352,7 @@ fn is_numeric_digit(c: char) -> bool { #[cfg(feature = "metadata")] #[inline] #[must_use] -pub fn is_doc_comment(comment: impl AsRef) -> bool { - let comment = comment.as_ref(); - +pub fn is_doc_comment(comment: &str) -> bool { (comment.starts_with("///") && !comment.starts_with("////")) || (comment.starts_with("/**") && !comment.starts_with("/***")) } @@ -1400,7 +1395,7 @@ fn get_next_token_inner( if let Some(ch) = state.is_within_text_terminated_by.take() { let start_pos = *pos; - return parse_string_literal(stream, state, pos, ch, false, true, true).map_or_else( + return parse_string_literal(stream, state, pos, ch, true, false, true).map_or_else( |(err, err_pos)| Some((Token::LexError(err), err_pos)), |(result, interpolated)| { if interpolated { @@ -1506,14 +1501,14 @@ fn get_next_token_inner( 'x' | 'X' => is_hex_digit, 'o' | 'O' => is_numeric_digit, 'b' | 'B' => is_numeric_digit, - _ => unreachable!(), + c => unreachable!("x/X or o/O or b/B expected but gets '{}'", c), }; radix_base = Some(match ch { 'x' | 'X' => 16, 'o' | 'O' => 8, 'b' | 'B' => 2, - _ => unreachable!(), + c => unreachable!("x/X or o/O or b/B expected but gets '{}'", c), }); } @@ -1535,7 +1530,8 @@ fn get_next_token_inner( .filter(|&&c| c != NUMBER_SEPARATOR) .collect(); - INT::from_str_radix(&out, radix) + UNSIGNED_INT::from_str_radix(&out, radix) + .map(|v| v as INT) .map(Token::IntegerConstant) .unwrap_or_else(|_| { Token::LexError(LERR::MalformedNumber(result.into_iter().collect())) @@ -1583,7 +1579,7 @@ fn get_next_token_inner( // " - string literal ('"', _) => { - return parse_string_literal(stream, state, pos, c, true, false, false) + return parse_string_literal(stream, state, pos, c, false, true, false) .map_or_else( |(err, err_pos)| Some((Token::LexError(err), err_pos)), |(result, _)| Some((Token::StringConstant(result), start_pos)), @@ -1610,7 +1606,7 @@ fn get_next_token_inner( _ => (), } - return parse_string_literal(stream, state, pos, c, false, true, true).map_or_else( + return parse_string_literal(stream, state, pos, c, true, false, true).map_or_else( |(err, err_pos)| Some((Token::LexError(err), err_pos)), |(result, interpolated)| { if interpolated { @@ -1635,7 +1631,7 @@ fn get_next_token_inner( |(err, err_pos)| (Token::LexError(err), err_pos), |(result, _)| { let mut chars = result.chars(); - let first = chars.next().expect("not empty"); + let first = chars.next().unwrap(); if chars.next().is_some() { ( @@ -1675,6 +1671,17 @@ fn get_next_token_inner( // Shebang ('#', '!') => return Some((Token::Reserved("#!".into()), start_pos)), + ('#', ' ') => { + eat_next(stream, pos); + let token = if stream.peek_next() == Some('{') { + eat_next(stream, pos); + "# {" + } else { + "#" + }; + return Some((Token::Reserved(token.into()), start_pos)); + } + ('#', _) => return Some((Token::Reserved("#".into()), start_pos)), // Operators @@ -2006,8 +2013,8 @@ fn get_identifier( /// Is this keyword allowed as a function? #[inline] #[must_use] -pub fn is_keyword_function(name: impl AsRef) -> bool { - match name.as_ref() { +pub fn is_keyword_function(name: &str) -> bool { + match name { KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR => true, @@ -2036,11 +2043,11 @@ pub fn is_valid_identifier(name: impl Iterator) -> bool { first_alphabetic } -/// Is a text string a valid scripted function name? +/// Is a text string a valid script-defined function name? #[inline(always)] #[must_use] -pub fn is_valid_function_name(name: impl AsRef) -> bool { - is_valid_identifier(name.as_ref().chars()) +pub fn is_valid_function_name(name: &str) -> bool { + is_valid_identifier(name.chars()) } /// Is a character valid to start an identifier? @@ -2202,19 +2209,14 @@ impl<'a> Iterator for TokenIterator<'a> { ("(*", false) | ("*)", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "'(* .. *)' is not a valid comment format. This is not Pascal! Should it be '/* .. */'?".to_string(), )), - ("#", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), + ("# {", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "'#' is not a valid symbol. Should it be '#{'?".to_string(), )), // Reserved keyword/operator that is custom. (_, true) => Token::Custom(s), - // Reserved operator that is not custom. - (token, false) if !is_valid_identifier(token.chars()) => { - let msg = format!("'{}' is a reserved symbol", token); - Token::LexError(LERR::ImproperSymbol(s.to_string(), msg)) - }, // Reserved keyword that is not custom and disabled. (token, false) if self.engine.disabled_symbols.contains(token) => { - let msg = format!("reserved symbol '{}' is disabled", token); + let msg = format!("reserved {} '{}' is disabled", if is_valid_identifier(token.chars()) { "keyword"} else {"symbol"}, token); Token::LexError(LERR::ImproperSymbol(s.to_string(), msg)) }, // Reserved keyword/operator that is not custom. @@ -2224,7 +2226,7 @@ impl<'a> Iterator for TokenIterator<'a> { Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(&*s) => { (Token::Custom(s), pos) } - // Custom standard keyword/symbol - must be disabled + // Custom keyword/symbol - must be disabled Some((token, pos)) if self.engine.custom_keywords.contains_key(&*token.syntax()) => { if self.engine.disabled_symbols.contains(&*token.syntax()) { // Disabled standard keyword/symbol diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 05f17c8b..3cb2f857 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -1,7 +1,7 @@ //! Helper module which defines the [`Any`] trait to to allow dynamic value handling. use crate::func::native::SendSync; -use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; +use crate::r#unsafe::{unsafe_cast, unsafe_cast_box, unsafe_try_cast}; use crate::{ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -9,12 +9,14 @@ use std::{ any::{type_name, Any, TypeId}, fmt, hash::{Hash, Hasher}, + mem, ops::{Deref, DerefMut}, str::FromStr, }; #[cfg(not(feature = "no_std"))] -#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_arch = "wasm64"))] use std::time::Instant; #[cfg(not(feature = "no_std"))] @@ -49,23 +51,19 @@ pub trait Variant: Any + private::Sealed { /// Convert this [`Variant`] trait object to [`&mut dyn Any`][Any]. #[must_use] - fn as_mut_any(&mut self) -> &mut dyn Any; + fn as_any_mut(&mut self) -> &mut dyn Any; - /// Convert this [`Variant`] trait object to an [`Any`] trait object. + /// Convert this [`Variant`] trait object to [`Box`]. #[must_use] - fn as_box_any(self: Box) -> Box; + fn as_boxed_any(self: Box) -> Box; /// Get the name of this type. #[must_use] fn type_name(&self) -> &'static str; - /// Convert into [`Dynamic`]. + /// Clone this [`Variant`] trait object. #[must_use] - fn into_dynamic(self) -> Dynamic; - - /// Clone into [`Dynamic`]. - #[must_use] - fn clone_into_dynamic(&self) -> Dynamic; + fn clone_object(&self) -> Box; } /// _(internals)_ Trait to represent any type. @@ -80,23 +78,19 @@ pub trait Variant: Any + Send + Sync + private::Sealed { /// Convert this [`Variant`] trait object to [`&mut dyn Any`][Any]. #[must_use] - fn as_mut_any(&mut self) -> &mut dyn Any; + fn as_any_mut(&mut self) -> &mut dyn Any; - /// Convert this [`Variant`] trait object to an [`Any`] trait object. + /// Convert this [`Variant`] trait object to [`Box`]. #[must_use] - fn as_box_any(self: Box) -> Box; + fn as_boxed_any(self: Box) -> Box; /// Get the name of this type. #[must_use] fn type_name(&self) -> &'static str; - /// Convert into [`Dynamic`]. + /// Clone this [`Variant`] trait object. #[must_use] - fn into_dynamic(self) -> Dynamic; - - /// Clone into [`Dynamic`]. - #[must_use] - fn clone_into_dynamic(&self) -> Dynamic; + fn clone_object(&self) -> Box; } impl Variant for T { @@ -105,11 +99,11 @@ impl Variant for T { self } #[inline(always)] - fn as_mut_any(&mut self) -> &mut dyn Any { + fn as_any_mut(&mut self) -> &mut dyn Any { self } #[inline(always)] - fn as_box_any(self: Box) -> Box { + fn as_boxed_any(self: Box) -> Box { self } #[inline(always)] @@ -117,12 +111,8 @@ impl Variant for T { type_name::() } #[inline(always)] - fn into_dynamic(self) -> Dynamic { - Dynamic::from(self) - } - #[inline(always)] - fn clone_into_dynamic(&self) -> Dynamic { - Dynamic::from(self.clone()) + fn clone_object(&self) -> Box { + Box::new(self.clone()) as Box } } @@ -411,7 +401,7 @@ impl Dynamic { #[cfg(not(feature = "no_std"))] Union::TimeStamp(_, _, _) => TypeId::of::(), - Union::Variant(ref value, _, _) => (***value).type_id(), + Union::Variant(ref v, _, _) => (***v).type_id(), #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] @@ -450,7 +440,7 @@ impl Dynamic { #[cfg(not(feature = "no_std"))] Union::TimeStamp(_, _, _) => "timestamp", - Union::Variant(ref value, _, _) => (***value).type_name(), + Union::Variant(ref v, _, _) => (***v).type_name(), #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] @@ -472,7 +462,7 @@ impl Hash for Dynamic { /// /// Panics if the [`Dynamic`] value contains an unrecognized trait object. fn hash(&self, state: &mut H) { - std::mem::discriminant(&self.0).hash(state); + mem::discriminant(&self.0).hash(state); match self.0 { Union::Unit(_, _, _) => ().hash(state), @@ -500,11 +490,13 @@ impl Hash for Dynamic { #[cfg(feature = "sync")] Union::Shared(ref cell, _, _) => (*cell.read().unwrap()).hash(state), - Union::Variant(ref _value, _, _) => { + Union::Variant(ref v, _, _) => { + let _v = v; + #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - let value_any = (***_value).as_any(); + let value_any = (***_v).as_any(); let type_id = value_any.type_id(); if type_id == TypeId::of::() { @@ -533,7 +525,8 @@ impl Hash for Dynamic { value_any.downcast_ref::().expect(CHECKED).hash(state); } - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_arch = "wasm64"))] if type_id == TypeId::of::() { TypeId::of::().hash(state); value_any.downcast_ref::().expect(CHECKED).hash(state); @@ -602,29 +595,26 @@ impl fmt::Display for Dynamic { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { Union::Unit(_, _, _) => write!(f, ""), - Union::Bool(ref value, _, _) => fmt::Display::fmt(value, f), - Union::Str(ref value, _, _) => fmt::Display::fmt(value, f), - Union::Char(ref value, _, _) => fmt::Display::fmt(value, f), - Union::Int(ref value, _, _) => fmt::Display::fmt(value, f), + Union::Bool(ref v, _, _) => fmt::Display::fmt(v, f), + Union::Str(ref v, _, _) => fmt::Display::fmt(v, f), + Union::Char(ref v, _, _) => fmt::Display::fmt(v, f), + Union::Int(ref v, _, _) => fmt::Display::fmt(v, f), #[cfg(not(feature = "no_float"))] - Union::Float(ref value, _, _) => fmt::Display::fmt(value, f), + Union::Float(ref v, _, _) => fmt::Display::fmt(v, f), #[cfg(feature = "decimal")] - Union::Decimal(ref value, _, _) => fmt::Display::fmt(value, f), + Union::Decimal(ref v, _, _) => fmt::Display::fmt(v, f), #[cfg(not(feature = "no_index"))] - Union::Array(ref value, _, _) => fmt::Debug::fmt(value, f), + Union::Array(_, _, _) => fmt::Debug::fmt(self, f), #[cfg(not(feature = "no_index"))] - Union::Blob(ref value, _, _) => fmt::Debug::fmt(value, f), + Union::Blob(_, _, _) => fmt::Debug::fmt(self, f), #[cfg(not(feature = "no_object"))] - Union::Map(ref value, _, _) => { - f.write_str("#")?; - fmt::Debug::fmt(value, f) - } - Union::FnPtr(ref value, _, _) => fmt::Display::fmt(value, f), + Union::Map(_, _, _) => fmt::Debug::fmt(self, f), + Union::FnPtr(ref v, _, _) => fmt::Display::fmt(v, f), #[cfg(not(feature = "no_std"))] Union::TimeStamp(_, _, _) => f.write_str(""), - Union::Variant(ref value, _, _) => { - let _value_any = (***value).as_any(); + Union::Variant(ref v, _, _) => { + let _value_any = (***v).as_any(); let _type_id = _value_any.type_id(); #[cfg(not(feature = "only_i32"))] @@ -648,13 +638,20 @@ impl fmt::Display for Dynamic { } #[cfg(not(feature = "no_float"))] + #[cfg(not(feature = "f32_float"))] if _type_id == TypeId::of::() { return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { + } + #[cfg(not(feature = "no_float"))] + #[cfg(feature = "f32_float")] + if _type_id == TypeId::of::() { return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_arch = "wasm64"))] if _type_id == TypeId::of::() { return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { @@ -669,7 +666,7 @@ impl fmt::Display for Dynamic { return write!(f, "{}..={}", range.start(), range.end()); } - f.write_str((***value).type_name()) + f.write_str((***v).type_name()) } #[cfg(not(feature = "no_closure"))] @@ -691,30 +688,39 @@ impl fmt::Display for Dynamic { impl fmt::Debug for Dynamic { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { - Union::Unit(ref value, _, _) => fmt::Debug::fmt(value, f), - Union::Bool(ref value, _, _) => fmt::Debug::fmt(value, f), - Union::Str(ref value, _, _) => fmt::Debug::fmt(value, f), - Union::Char(ref value, _, _) => fmt::Debug::fmt(value, f), - Union::Int(ref value, _, _) => fmt::Debug::fmt(value, f), + Union::Unit(ref v, _, _) => fmt::Debug::fmt(v, f), + Union::Bool(ref v, _, _) => fmt::Debug::fmt(v, f), + Union::Str(ref v, _, _) => fmt::Debug::fmt(v, f), + Union::Char(ref v, _, _) => fmt::Debug::fmt(v, f), + Union::Int(ref v, _, _) => fmt::Debug::fmt(v, f), #[cfg(not(feature = "no_float"))] - Union::Float(ref value, _, _) => fmt::Debug::fmt(value, f), + Union::Float(ref v, _, _) => fmt::Debug::fmt(v, f), #[cfg(feature = "decimal")] - Union::Decimal(ref value, _, _) => fmt::Debug::fmt(value, f), + Union::Decimal(ref v, _, _) => fmt::Debug::fmt(v, f), #[cfg(not(feature = "no_index"))] - Union::Array(ref value, _, _) => fmt::Debug::fmt(value, f), + Union::Array(ref v, _, _) => fmt::Debug::fmt(v, f), #[cfg(not(feature = "no_index"))] - Union::Blob(ref value, _, _) => fmt::Debug::fmt(value, f), - #[cfg(not(feature = "no_object"))] - Union::Map(ref value, _, _) => { - f.write_str("#")?; - fmt::Debug::fmt(value, f) + Union::Blob(ref v, _, _) => { + f.write_str("[")?; + v.iter().enumerate().try_for_each(|(i, v)| { + if i > 0 && i % 8 == 0 { + f.write_str(" ")?; + } + write!(f, "{:02x}", v) + })?; + f.write_str("]") } - Union::FnPtr(ref value, _, _) => fmt::Debug::fmt(value, f), + #[cfg(not(feature = "no_object"))] + Union::Map(ref v, _, _) => { + f.write_str("#")?; + fmt::Debug::fmt(v, f) + } + Union::FnPtr(ref v, _, _) => fmt::Debug::fmt(v, f), #[cfg(not(feature = "no_std"))] Union::TimeStamp(_, _, _) => write!(f, ""), - Union::Variant(ref value, _, _) => { - let _value_any = (***value).as_any(); + Union::Variant(ref v, _, _) => { + let _value_any = (***v).as_any(); let _type_id = _value_any.type_id(); #[cfg(not(feature = "only_i32"))] @@ -738,13 +744,20 @@ impl fmt::Debug for Dynamic { } #[cfg(not(feature = "no_float"))] + #[cfg(not(feature = "f32_float"))] if _type_id == TypeId::of::() { return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { + } + #[cfg(not(feature = "no_float"))] + #[cfg(feature = "f32_float")] + if _type_id == TypeId::of::() { return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_arch = "wasm64"))] if _type_id == TypeId::of::() { return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); } else if _type_id == TypeId::of::() { @@ -759,7 +772,7 @@ impl fmt::Debug for Dynamic { return write!(f, "{}..={}", range.start(), range.end()); } - f.write_str((***value).type_name()) + f.write_str((***v).type_name()) } #[cfg(not(feature = "no_closure"))] @@ -788,34 +801,30 @@ impl Clone for Dynamic { /// The cloned copy is marked read-write even if the original is read-only. fn clone(&self) -> Self { match self.0 { - Union::Unit(value, tag, _) => Self(Union::Unit(value, tag, ReadWrite)), - Union::Bool(value, tag, _) => Self(Union::Bool(value, tag, ReadWrite)), - Union::Str(ref value, tag, _) => Self(Union::Str(value.clone(), tag, ReadWrite)), - Union::Char(value, tag, _) => Self(Union::Char(value, tag, ReadWrite)), - Union::Int(value, tag, _) => Self(Union::Int(value, tag, ReadWrite)), + Union::Unit(v, tag, _) => Self(Union::Unit(v, tag, ReadWrite)), + Union::Bool(v, tag, _) => Self(Union::Bool(v, tag, ReadWrite)), + Union::Str(ref v, tag, _) => Self(Union::Str(v.clone(), tag, ReadWrite)), + Union::Char(v, tag, _) => Self(Union::Char(v, tag, ReadWrite)), + Union::Int(v, tag, _) => Self(Union::Int(v, tag, ReadWrite)), #[cfg(not(feature = "no_float"))] - Union::Float(value, tag, _) => Self(Union::Float(value, tag, ReadWrite)), + Union::Float(v, tag, _) => Self(Union::Float(v, tag, ReadWrite)), #[cfg(feature = "decimal")] - Union::Decimal(ref value, tag, _) => { - Self(Union::Decimal(value.clone(), tag, ReadWrite)) - } + Union::Decimal(ref v, tag, _) => Self(Union::Decimal(v.clone(), tag, ReadWrite)), #[cfg(not(feature = "no_index"))] - Union::Array(ref value, tag, _) => Self(Union::Array(value.clone(), tag, ReadWrite)), + Union::Array(ref v, tag, _) => Self(Union::Array(v.clone(), tag, ReadWrite)), #[cfg(not(feature = "no_index"))] - Union::Blob(ref value, tag, _) => Self(Union::Blob(value.clone(), tag, ReadWrite)), + Union::Blob(ref v, tag, _) => Self(Union::Blob(v.clone(), tag, ReadWrite)), #[cfg(not(feature = "no_object"))] - Union::Map(ref value, tag, _) => Self(Union::Map(value.clone(), tag, ReadWrite)), - Union::FnPtr(ref value, tag, _) => Self(Union::FnPtr(value.clone(), tag, ReadWrite)), + Union::Map(ref v, tag, _) => Self(Union::Map(v.clone(), tag, ReadWrite)), + Union::FnPtr(ref v, tag, _) => Self(Union::FnPtr(v.clone(), tag, ReadWrite)), #[cfg(not(feature = "no_std"))] - Union::TimeStamp(ref value, tag, _) => { - Self(Union::TimeStamp(value.clone(), tag, ReadWrite)) - } + Union::TimeStamp(ref v, tag, _) => Self(Union::TimeStamp(v.clone(), tag, ReadWrite)), - Union::Variant(ref value, tag, _) => { - let mut x = value.as_ref().as_ref().clone_into_dynamic(); - x.set_tag(tag); - x - } + Union::Variant(ref v, tag, _) => Self(Union::Variant( + v.as_ref().as_ref().clone_object().into(), + tag, + ReadWrite, + )), #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, tag, _) => Self(Union::Shared(cell.clone(), tag, ReadWrite)), @@ -949,7 +958,6 @@ impl Dynamic { /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] - #[cfg(not(feature = "f32_float"))] pub const FLOAT_PI: Self = Self::from_float(FloatConstants::PI); /// A [`Dynamic`] containing π/2. /// @@ -1022,6 +1030,24 @@ impl Dynamic { 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)] + 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)] + 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)] + pub fn from_map(map: crate::Map) -> Self { + Self(Union::Map(map.into(), DEFAULT_TAG_VALUE, ReadWrite)) + } /// Create a new [`Dynamic`] from an [`Instant`]. /// /// Not available under `no-std`. @@ -1168,11 +1194,14 @@ impl Dynamic { /// # Notes /// /// Beware that you need to pass in an [`Array`][crate::Array] type for it to be recognized as an [`Array`][crate::Array]. - /// A [`Vec`][Vec] does not get automatically converted to an [`Array`][crate::Array], but will be a generic - /// restricted trait object instead, because [`Vec`][Vec] is not a supported standard type. + /// A [`Vec`][Vec] does not get automatically converted to an [`Array`][crate::Array], but + /// will be a custom type instead (stored as a trait object). Use `Into` to convert a + /// [`Vec`][Vec] into a [`Dynamic`] as an [`Array`][crate::Array] value. /// /// Similarly, passing in a [`HashMap`][std::collections::HashMap] or - /// [`BTreeMap`][std::collections::BTreeMap] will not get a [`Map`][crate::Map] but a trait object. + /// [`BTreeMap`][std::collections::BTreeMap] will not get a [`Map`][crate::Map] + /// but a custom type. Again, use `Into` to get a [`Dynamic`] with a + /// [`Map`][crate::Map] value. /// /// # Examples /// @@ -1193,11 +1222,11 @@ impl Dynamic { /// ``` #[inline] #[must_use] - pub fn from(mut value: T) -> Self { + pub fn from(value: T) -> Self { // Coded this way in order to maximally leverage potentials for dead-code removal. if TypeId::of::() == TypeId::of::() { - return unsafe_try_cast::<_, Dynamic>(value).ok().expect(CHECKED); + return unsafe_cast::<_, Dynamic>(value); } let val = value.as_any(); @@ -1233,52 +1262,36 @@ impl Dynamic { return ().into(); } - value = match unsafe_try_cast::<_, String>(value) { - Ok(s) => return s.into(), - Err(value) => value, - }; - #[cfg(not(feature = "no_index"))] - { - value = match unsafe_try_cast::<_, crate::Array>(value) { - Ok(array) => return array.into(), - Err(value) => value, - }; + if TypeId::of::() == TypeId::of::() { + return unsafe_cast::<_, String>(value).into(); + } + #[cfg(not(feature = "no_float"))] + if TypeId::of::() == TypeId::of::() { + return unsafe_cast::<_, crate::FLOAT>(value).into(); } #[cfg(not(feature = "no_index"))] - { - value = match unsafe_try_cast::<_, crate::Blob>(value) { - Ok(blob) => return Self(Union::Blob(Box::new(blob), DEFAULT_TAG_VALUE, ReadWrite)), - Err(value) => value, - }; + if TypeId::of::() == TypeId::of::() { + return unsafe_cast::<_, crate::Array>(value).into(); + } + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + return Dynamic::from_blob(unsafe_cast::<_, crate::Blob>(value)); // don't use blob.into() because it'll be converted into an Array } - #[cfg(not(feature = "no_object"))] - { - value = match unsafe_try_cast::<_, crate::Map>(value) { - Ok(map) => return map.into(), - Err(value) => value, - }; + if TypeId::of::() == TypeId::of::() { + return unsafe_cast::<_, crate::Map>(value).into(); + } + if TypeId::of::() == TypeId::of::() { + return unsafe_cast::<_, FnPtr>(value).into(); } - - value = match unsafe_try_cast::<_, FnPtr>(value) { - Ok(fn_ptr) => return fn_ptr.into(), - Err(value) => value, - }; #[cfg(not(feature = "no_std"))] - { - value = match unsafe_try_cast::<_, Instant>(value) { - Ok(timestamp) => return timestamp.into(), - Err(value) => value, - }; + if TypeId::of::() == TypeId::of::() { + return unsafe_cast::<_, Instant>(value).into(); } - #[cfg(not(feature = "no_closure"))] - { - value = match unsafe_try_cast::<_, crate::Shared>>(value) { - Ok(value) => return value.into(), - Err(value) => value, - }; + if TypeId::of::() == TypeId::of::>>() { + return unsafe_cast::<_, crate::Shared>>(value).into(); } Self(Union::Variant( @@ -1288,8 +1301,7 @@ impl Dynamic { )) } /// Turn the [`Dynamic`] value into a shared [`Dynamic`] value backed by an - /// [`Rc`][std::rc::Rc]`<`[`RefCell`][std::cell::RefCell]`<`[`Dynamic`]`>>` or - /// [`Arc`][std::sync::Arc]`<`[`RwLock`][std::sync::RwLock]`<`[`Dynamic`]`>>` + /// [`Rc>`][std::rc::Rc] or [`Arc>`][std::sync::Arc] /// depending on the `sync` feature. /// /// Not available under `no_closure`. @@ -1350,12 +1362,12 @@ impl Dynamic { } if TypeId::of::() == TypeId::of::() { - return unsafe_try_cast::<_, T>(self).ok(); + return unsafe_try_cast::<_, T>(self); } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Int(value, _, _) => unsafe_try_cast(value).ok(), + Union::Int(v, _, _) => unsafe_try_cast(v), _ => None, }; } @@ -1363,7 +1375,7 @@ impl Dynamic { #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Float(value, _, _) => unsafe_try_cast(*value).ok(), + Union::Float(v, _, _) => unsafe_try_cast(*v), _ => None, }; } @@ -1371,35 +1383,35 @@ impl Dynamic { #[cfg(feature = "decimal")] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Decimal(value, _, _) => unsafe_try_cast(*value).ok(), + Union::Decimal(v, _, _) => unsafe_try_cast(*v), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Bool(value, _, _) => unsafe_try_cast(value).ok(), + Union::Bool(v, _, _) => unsafe_try_cast(v), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Str(value, _, _) => unsafe_try_cast(value).ok(), + Union::Str(v, _, _) => unsafe_try_cast(v), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Str(value, _, _) => unsafe_try_cast(value.into_owned()).ok(), + Union::Str(v, _, _) => unsafe_try_cast(v.to_string()), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Char(value, _, _) => unsafe_try_cast(value).ok(), + Union::Char(v, _, _) => unsafe_try_cast(v), _ => None, }; } @@ -1407,7 +1419,7 @@ impl Dynamic { #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Array(value, _, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), + Union::Array(v, _, _) => unsafe_cast_box::<_, T>(v), _ => None, }; } @@ -1415,7 +1427,7 @@ impl Dynamic { #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Blob(value, _, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), + Union::Blob(v, _, _) => unsafe_cast_box::<_, T>(v), _ => None, }; } @@ -1423,14 +1435,14 @@ impl Dynamic { #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Map(value, _, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), + Union::Map(v, _, _) => unsafe_cast_box::<_, T>(v), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::FnPtr(value, _, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), + Union::FnPtr(v, _, _) => unsafe_cast_box::<_, T>(v), _ => None, }; } @@ -1438,20 +1450,20 @@ impl Dynamic { #[cfg(not(feature = "no_std"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::TimeStamp(value, _, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), + Union::TimeStamp(v, _, _) => unsafe_cast_box::<_, T>(v), _ => None, }; } if TypeId::of::() == TypeId::of::<()>() { return match self.0 { - Union::Unit(value, _, _) => unsafe_try_cast(value).ok(), + Union::Unit(v, _, _) => unsafe_try_cast(v), _ => None, }; } match self.0 { - Union::Variant(value, _, _) => (*value).as_box_any().downcast().map(|x| *x).ok(), + Union::Variant(v, _, _) => (*v).as_boxed_any().downcast().ok().map(|x| *x), #[cfg(not(feature = "no_closure"))] Union::Shared(_, _, _) => unreachable!("Union::Shared case should be already handled"), _ => None, @@ -1462,11 +1474,9 @@ impl Dynamic { /// Casting to a [`Dynamic`] just returns as is, but if it contains a shared value, /// it is cloned into a [`Dynamic`] with a normal value. /// - /// /// # Panics or Deadlocks /// - /// Panics if the cast fails (e.g. the type of the actual value is not the - /// same as the specified type). + /// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type). /// /// 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. @@ -1587,21 +1597,19 @@ impl Dynamic { pub(crate) fn flatten_in_place(&mut self) -> &mut Self { match self.0 { #[cfg(not(feature = "no_closure"))] - Union::Shared(_, _, _) => match std::mem::take(self).0 { - Union::Shared(cell, _, _) => { - *self = crate::func::native::shared_try_take(cell).map_or_else( - #[cfg(not(feature = "sync"))] - |cell| cell.borrow().clone(), - #[cfg(feature = "sync")] - |cell| cell.read().unwrap().clone(), - #[cfg(not(feature = "sync"))] - |value| value.into_inner(), - #[cfg(feature = "sync")] - |value| value.into_inner().unwrap(), - ); - } - _ => unreachable!(), - }, + Union::Shared(ref mut cell, _, _) => { + let cell = mem::take(cell); + *self = crate::func::native::shared_try_take(cell).map_or_else( + #[cfg(not(feature = "sync"))] + |cell| cell.borrow().clone(), + #[cfg(feature = "sync")] + |cell| cell.read().unwrap().clone(), + #[cfg(not(feature = "sync"))] + |value| value.into_inner(), + #[cfg(feature = "sync")] + |value| value.into_inner().unwrap(), + ); + } _ => (), } self @@ -1624,7 +1632,6 @@ impl Dynamic { Union::Shared(ref _cell, _, _) => { #[cfg(not(feature = "sync"))] return _cell.try_borrow().is_err(); - #[cfg(feature = "sync")] return false; } @@ -1665,7 +1672,8 @@ impl Dynamic { } self.downcast_ref() - .map(|r| DynamicReadLock(DynamicReadLockInner::Reference(r))) + .map(DynamicReadLockInner::Reference) + .map(DynamicReadLock) } /// Get a mutable reference of a specific type to the [`Dynamic`]. /// Casting to [`Dynamic`] just returns a mutable reference to it. @@ -1696,7 +1704,8 @@ impl Dynamic { } self.downcast_mut() - .map(|r| DynamicWriteLock(DynamicWriteLockInner::Reference(r))) + .map(DynamicWriteLockInner::Reference) + .map(DynamicWriteLock) } /// Get a reference of a specific type to the [`Dynamic`]. /// Casting to [`Dynamic`] just returns a reference to it. @@ -1709,79 +1718,79 @@ impl Dynamic { if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Int(ref value, _, _) => value.as_any().downcast_ref::(), + Union::Int(ref v, _, _) => v.as_any().downcast_ref::(), _ => None, }; } #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Float(ref value, _, _) => value.as_ref().as_any().downcast_ref::(), + Union::Float(ref v, _, _) => v.as_ref().as_any().downcast_ref::(), _ => None, }; } #[cfg(feature = "decimal")] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Decimal(ref value, _, _) => value.as_ref().as_any().downcast_ref::(), + Union::Decimal(ref v, _, _) => v.as_ref().as_any().downcast_ref::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Bool(ref value, _, _) => value.as_any().downcast_ref::(), + Union::Bool(ref v, _, _) => v.as_any().downcast_ref::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Str(ref value, _, _) => value.as_any().downcast_ref::(), + Union::Str(ref v, _, _) => v.as_any().downcast_ref::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Char(ref value, _, _) => value.as_any().downcast_ref::(), + Union::Char(ref v, _, _) => v.as_any().downcast_ref::(), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Array(ref value, _, _) => value.as_ref().as_any().downcast_ref::(), + Union::Array(ref v, _, _) => v.as_ref().as_any().downcast_ref::(), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Blob(ref value, _, _) => value.as_ref().as_any().downcast_ref::(), + Union::Blob(ref v, _, _) => v.as_ref().as_any().downcast_ref::(), _ => None, }; } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Map(ref value, _, _) => value.as_ref().as_any().downcast_ref::(), + Union::Map(ref v, _, _) => v.as_ref().as_any().downcast_ref::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::FnPtr(ref value, _, _) => value.as_ref().as_any().downcast_ref::(), + Union::FnPtr(ref v, _, _) => v.as_ref().as_any().downcast_ref::(), _ => None, }; } #[cfg(not(feature = "no_std"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::TimeStamp(ref value, _, _) => value.as_ref().as_any().downcast_ref::(), + Union::TimeStamp(ref v, _, _) => v.as_ref().as_any().downcast_ref::(), _ => None, }; } if TypeId::of::() == TypeId::of::<()>() { return match self.0 { - Union::Unit(ref value, _, _) => value.as_any().downcast_ref::(), + Union::Unit(ref v, _, _) => v.as_any().downcast_ref::(), _ => None, }; } @@ -1790,7 +1799,7 @@ impl Dynamic { } match self.0 { - Union::Variant(ref value, _, _) => (***value).as_any().downcast_ref::(), + Union::Variant(ref v, _, _) => (***v).as_any().downcast_ref::(), #[cfg(not(feature = "no_closure"))] Union::Shared(_, _, _) => None, _ => None, @@ -1807,98 +1816,88 @@ impl Dynamic { if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Int(ref mut value, _, _) => value.as_mut_any().downcast_mut::(), + Union::Int(ref mut v, _, _) => v.as_any_mut().downcast_mut::(), _ => None, }; } #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Float(ref mut value, _, _) => { - value.as_mut().as_mut_any().downcast_mut::() - } + Union::Float(ref mut v, _, _) => v.as_mut().as_any_mut().downcast_mut::(), _ => None, }; } #[cfg(feature = "decimal")] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Decimal(ref mut value, _, _) => { - value.as_mut().as_mut_any().downcast_mut::() - } + Union::Decimal(ref mut v, _, _) => v.as_mut().as_any_mut().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Bool(ref mut value, _, _) => value.as_mut_any().downcast_mut::(), + Union::Bool(ref mut v, _, _) => v.as_any_mut().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Str(ref mut value, _, _) => value.as_mut_any().downcast_mut::(), + Union::Str(ref mut v, _, _) => v.as_any_mut().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Char(ref mut value, _, _) => value.as_mut_any().downcast_mut::(), + Union::Char(ref mut v, _, _) => v.as_any_mut().downcast_mut::(), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Array(ref mut value, _, _) => { - value.as_mut().as_mut_any().downcast_mut::() - } + Union::Array(ref mut v, _, _) => v.as_mut().as_any_mut().downcast_mut::(), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Blob(ref mut value, _, _) => value.as_mut().as_mut_any().downcast_mut::(), + Union::Blob(ref mut v, _, _) => v.as_mut().as_any_mut().downcast_mut::(), _ => None, }; } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Map(ref mut value, _, _) => value.as_mut().as_mut_any().downcast_mut::(), + Union::Map(ref mut v, _, _) => v.as_mut().as_any_mut().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::FnPtr(ref mut value, _, _) => { - value.as_mut().as_mut_any().downcast_mut::() - } + Union::FnPtr(ref mut v, _, _) => v.as_mut().as_any_mut().downcast_mut::(), _ => None, }; } #[cfg(not(feature = "no_std"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::TimeStamp(ref mut value, _, _) => { - value.as_mut().as_mut_any().downcast_mut::() - } + Union::TimeStamp(ref mut v, _, _) => v.as_mut().as_any_mut().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::<()>() { return match self.0 { - Union::Unit(ref mut value, _, _) => value.as_mut_any().downcast_mut::(), + Union::Unit(ref mut v, _, _) => v.as_any_mut().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { - return self.as_mut_any().downcast_mut::(); + return self.as_any_mut().downcast_mut::(); } match self.0 { - Union::Variant(ref mut value, _, _) => (***value).as_mut_any().downcast_mut::(), + Union::Variant(ref mut v, _, _) => (***v).as_any_mut().downcast_mut::(), #[cfg(not(feature = "no_closure"))] Union::Shared(_, _, _) => None, _ => None, @@ -1909,7 +1908,7 @@ impl Dynamic { #[inline] pub fn as_unit(&self) -> Result<(), &'static str> { match self.0 { - Union::Unit(value, _, _) => Ok(value), + Union::Unit(v, _, _) => Ok(v), #[cfg(not(feature = "no_closure"))] Union::Shared(_, _, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), @@ -2020,6 +2019,109 @@ impl Dynamic { _ => Err(self.type_name()), } } + /// Convert the [`Dynamic`] into an [`Array`][crate::Array]. + /// Returns the name of the actual type if the cast fails. + #[cfg(not(feature = "no_index"))] + #[inline(always)] + pub fn into_array(self) -> Result { + match self.0 { + Union::Array(a, _, _) => Ok(*a), + #[cfg(not(feature = "no_closure"))] + Union::Shared(cell, _, _) => { + #[cfg(not(feature = "sync"))] + let value = cell.borrow(); + #[cfg(feature = "sync")] + let value = cell.read().unwrap(); + + match value.0 { + Union::Array(ref a, _, _) => Ok(a.as_ref().clone()), + _ => Err((*value).type_name()), + } + } + _ => Err(self.type_name()), + } + } + /// Convert the [`Dynamic`] into a [`Vec`]. + /// Returns the name of the actual type if any cast fails. + #[cfg(not(feature = "no_index"))] + #[inline(always)] + pub fn into_typed_array(self) -> Result, &'static str> { + match self.0 { + Union::Array(a, _, _) => a + .into_iter() + .map(|v| { + #[cfg(not(feature = "no_closure"))] + let typ = if v.is_shared() { + // Avoid panics/deadlocks with shared values + "" + } else { + v.type_name() + }; + #[cfg(feature = "no_closure")] + let typ = v.type_name(); + + v.try_cast::().ok_or_else(|| typ) + }) + .collect(), + Union::Blob(_, _, _) if TypeId::of::() == TypeId::of::() => { + Ok(self.cast::>()) + } + #[cfg(not(feature = "no_closure"))] + Union::Shared(cell, _, _) => { + #[cfg(not(feature = "sync"))] + let value = cell.borrow(); + #[cfg(feature = "sync")] + let value = cell.read().unwrap(); + + match value.0 { + Union::Array(ref a, _, _) => { + a.iter() + .map(|v| { + #[cfg(not(feature = "no_closure"))] + let typ = if v.is_shared() { + // Avoid panics/deadlocks with shared values + "" + } else { + v.type_name() + }; + #[cfg(feature = "no_closure")] + let typ = v.type_name(); + + v.read_lock::().ok_or_else(|| typ).map(|v| v.clone()) + }) + .collect() + } + Union::Blob(_, _, _) if TypeId::of::() == TypeId::of::() => { + Ok((*value).clone().cast::>()) + } + _ => Err((*value).type_name()), + } + } + _ => Err(self.type_name()), + } + } + /// Convert the [`Dynamic`] into a [`Blob`][crate::Blob]. + /// Returns the name of the actual type if the cast fails. + #[cfg(not(feature = "no_index"))] + #[inline(always)] + pub fn into_blob(self) -> Result { + match self.0 { + Union::Blob(a, _, _) => Ok(*a), + #[cfg(not(feature = "no_closure"))] + Union::Shared(cell, _, _) => { + #[cfg(not(feature = "sync"))] + let value = cell.borrow(); + #[cfg(feature = "sync")] + let value = cell.read().unwrap(); + + match value.0 { + Union::Blob(ref a, _, _) => Ok(a.as_ref().clone()), + _ => Err((*value).type_name()), + } + } + _ => Err(self.type_name()), + } + } } impl From<()> for Dynamic { @@ -2087,14 +2189,6 @@ impl FromStr for Dynamic { } } #[cfg(not(feature = "no_index"))] -impl Dynamic { - /// Create a [`Dynamic`] from an [`Array`][crate::Array]. - #[inline(always)] - pub(crate) fn from_array(array: crate::Array) -> Self { - Self(Union::Array(array.into(), DEFAULT_TAG_VALUE, ReadWrite)) - } -} -#[cfg(not(feature = "no_index"))] impl From> for Dynamic { #[inline] fn from(value: Vec) -> Self { @@ -2127,122 +2221,6 @@ impl std::iter::FromIterator for Dynamic { )) } } -#[cfg(not(feature = "no_index"))] -impl Dynamic { - /// Convert the [`Dynamic`] into an [`Array`][crate::Array]. - /// Returns the name of the actual type if the cast fails. - #[inline(always)] - pub fn into_array(self) -> Result { - match self.0 { - Union::Array(a, _, _) => Ok(*a), - #[cfg(not(feature = "no_closure"))] - Union::Shared(cell, _, _) => { - #[cfg(not(feature = "sync"))] - let value = cell.borrow(); - #[cfg(feature = "sync")] - let value = cell.read().unwrap(); - - match value.0 { - Union::Array(ref a, _, _) => Ok(a.as_ref().clone()), - _ => Err((*value).type_name()), - } - } - _ => Err(self.type_name()), - } - } - /// Convert the [`Dynamic`] into a [`Vec`]. - /// Returns the name of the actual type if any cast fails. - #[inline(always)] - pub fn into_typed_array(self) -> Result, &'static str> { - match self.0 { - Union::Array(a, _, _) => a - .into_iter() - .map(|v| { - #[cfg(not(feature = "no_closure"))] - let typ = if v.is_shared() { - // Avoid panics/deadlocks with shared values - "" - } else { - v.type_name() - }; - #[cfg(feature = "no_closure")] - let typ = v.type_name(); - - v.try_cast::().ok_or_else(|| typ) - }) - .collect(), - Union::Blob(_, _, _) if TypeId::of::() == TypeId::of::() => { - Ok(self.cast::>()) - } - #[cfg(not(feature = "no_closure"))] - Union::Shared(cell, _, _) => { - #[cfg(not(feature = "sync"))] - let value = cell.borrow(); - #[cfg(feature = "sync")] - let value = cell.read().unwrap(); - - match value.0 { - Union::Array(ref a, _, _) => { - a.iter() - .map(|v| { - #[cfg(not(feature = "no_closure"))] - let typ = if v.is_shared() { - // Avoid panics/deadlocks with shared values - "" - } else { - v.type_name() - }; - #[cfg(feature = "no_closure")] - let typ = v.type_name(); - - v.read_lock::().ok_or_else(|| typ).map(|v| v.clone()) - }) - .collect() - } - _ => Err((*value).type_name()), - } - } - _ => Err(self.type_name()), - } - } -} -#[cfg(not(feature = "no_index"))] -impl Dynamic { - /// Create a [`Dynamic`] from a [`Vec`]. - #[inline(always)] - pub fn from_blob(blob: crate::Blob) -> Self { - Self(Union::Blob(Box::new(blob), DEFAULT_TAG_VALUE, ReadWrite)) - } - /// Convert the [`Dynamic`] into a [`Vec`]. - /// Returns the name of the actual type if the cast fails. - #[inline(always)] - pub fn into_blob(self) -> Result { - match self.0 { - Union::Blob(a, _, _) => Ok(*a), - #[cfg(not(feature = "no_closure"))] - Union::Shared(cell, _, _) => { - #[cfg(not(feature = "sync"))] - let value = cell.borrow(); - #[cfg(feature = "sync")] - let value = cell.read().unwrap(); - - match value.0 { - Union::Blob(ref a, _, _) => Ok(a.as_ref().clone()), - _ => Err((*value).type_name()), - } - } - _ => Err(self.type_name()), - } - } -} -#[cfg(not(feature = "no_object"))] -impl Dynamic { - /// Create a [`Dynamic`] from a [`Map`][crate::Map]. - #[inline(always)] - pub(crate) fn from_map(map: crate::Map) -> Self { - Self(Union::Map(map.into(), DEFAULT_TAG_VALUE, ReadWrite)) - } -} #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_std"))] impl, T: Variant + Clone> From> @@ -2319,12 +2297,6 @@ impl From for Dynamic { Self(Union::FnPtr(value.into(), DEFAULT_TAG_VALUE, ReadWrite)) } } -impl From> for Dynamic { - #[inline(always)] - fn from(value: Box) -> Self { - Self(Union::FnPtr(value, DEFAULT_TAG_VALUE, ReadWrite)) - } -} #[cfg(not(feature = "no_std"))] impl From for Dynamic { #[inline(always)] diff --git a/src/types/error.rs b/src/types/error.rs index f38a971c..93e7f3d1 100644 --- a/src/types/error.rs +++ b/src/types/error.rs @@ -1,6 +1,6 @@ //! Module containing error definitions for the evaluation process. -use crate::{Dynamic, ImmutableString, ParseErrorType, Position, INT}; +use crate::{Dynamic, ImmutableString, ParseErrorType, Position, RhaiError, INT}; #[cfg(feature = "no_std")] use core_error::Error; #[cfg(not(feature = "no_std"))] @@ -36,12 +36,12 @@ pub enum EvalAltResult { ErrorFunctionNotFound(String, Position), /// An error has occurred inside a called function. /// Wrapped values are the function name, function source, and the interior error. - ErrorInFunctionCall(String, String, Box, Position), + ErrorInFunctionCall(String, String, RhaiError, Position), /// Usage of an unknown [module][crate::Module]. Wrapped value is the [module][crate::Module] name. ErrorModuleNotFound(String, Position), /// An error has occurred while loading a [module][crate::Module]. /// Wrapped value are the [module][crate::Module] name and the interior error. - ErrorInModule(String, Box, Position), + ErrorInModule(String, RhaiError, Position), /// Access to `this` that is not bound. ErrorUnboundThis(Position), /// Data is not of the required type. @@ -222,7 +222,7 @@ impl> From for EvalAltResult { } } -impl> From for Box { +impl> From for RhaiError { #[inline(never)] fn from(err: T) -> Self { EvalAltResult::ErrorRuntime(err.as_ref().to_string().into(), Position::NONE).into() @@ -300,7 +300,7 @@ impl EvalAltResult { format!("{:?}", self) .split('(') .next() - .expect("debug format of error is `ErrorXXX(...)`") + .expect("`ErrorXXX(...)`") .into(), ); diff --git a/src/types/fn_ptr.rs b/src/types/fn_ptr.rs index 7475b7ef..249e50a0 100644 --- a/src/types/fn_ptr.rs +++ b/src/types/fn_ptr.rs @@ -3,8 +3,8 @@ use crate::tokenizer::is_valid_identifier; use crate::types::dynamic::Variant; use crate::{ - Dynamic, Engine, EvalAltResult, FuncArgs, Identifier, Module, NativeCallContext, Position, - RhaiResult, StaticVec, AST, + Dynamic, Engine, FuncArgs, Identifier, Module, NativeCallContext, Position, RhaiError, + RhaiResult, RhaiResultOf, StaticVec, AST, ERR, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -24,7 +24,12 @@ impl fmt::Debug for FnPtr { if !self.is_curried() { write!(f, "Fn({})", self.fn_name()) } else { - f.debug_tuple("Fn").field(&self.0).field(&self.1).finish() + self.1 + .iter() + .fold(f.debug_tuple("Fn").field(&self.0), |f, curry| { + f.field(curry) + }) + .finish() } } } @@ -32,7 +37,7 @@ impl fmt::Debug for FnPtr { impl FnPtr { /// Create a new function pointer. #[inline(always)] - pub fn new(name: impl Into) -> Result> { + pub fn new(name: impl Into) -> RhaiResultOf { name.into().try_into() } /// Create a new function pointer without checking its parameters. @@ -135,7 +140,7 @@ impl FnPtr { engine: &Engine, ast: &AST, args: impl FuncArgs, - ) -> Result> { + ) -> RhaiResultOf { let _ast = ast; let mut arg_values = crate::StaticVec::new_const(); args.parse(&mut arg_values); @@ -157,7 +162,7 @@ impl FnPtr { let typ = engine.map_type_name(result.type_name()); result.try_cast().ok_or_else(|| { - EvalAltResult::ErrorMismatchOutputType( + ERR::ErrorMismatchOutputType( engine.map_type_name(type_name::()).into(), typ.into(), Position::NONE, @@ -176,7 +181,7 @@ impl FnPtr { &self, context: &NativeCallContext, args: impl FuncArgs, - ) -> Result> { + ) -> RhaiResultOf { let mut arg_values = crate::StaticVec::new_const(); args.parse(&mut arg_values); @@ -185,7 +190,7 @@ impl FnPtr { let typ = context.engine().map_type_name(result.type_name()); result.try_cast().ok_or_else(|| { - EvalAltResult::ErrorMismatchOutputType( + ERR::ErrorMismatchOutputType( context.engine().map_type_name(type_name::()).into(), typ.into(), Position::NONE, @@ -204,10 +209,11 @@ impl FnPtr { /// /// This function is very low level. /// - /// ## Arguments + /// # Arguments /// /// All the arguments are _consumed_, meaning that they're replaced by `()`. /// This is to avoid unnecessarily cloning the arguments. + /// /// Do not use the arguments after this call. If they are needed afterwards, /// clone them _before_ calling this function. #[inline] @@ -247,53 +253,53 @@ impl fmt::Display for FnPtr { } impl TryFrom for FnPtr { - type Error = Box; + type Error = RhaiError; #[inline] - fn try_from(value: Identifier) -> Result { + fn try_from(value: Identifier) -> RhaiResultOf { if is_valid_identifier(value.chars()) { Ok(Self(value, StaticVec::new_const())) } else { - Err(EvalAltResult::ErrorFunctionNotFound(value.to_string(), Position::NONE).into()) + Err(ERR::ErrorFunctionNotFound(value.to_string(), Position::NONE).into()) } } } impl TryFrom for FnPtr { - type Error = Box; + type Error = RhaiError; #[inline(always)] - fn try_from(value: crate::ImmutableString) -> Result { + fn try_from(value: crate::ImmutableString) -> RhaiResultOf { let s: Identifier = value.into(); Self::try_from(s) } } impl TryFrom for FnPtr { - type Error = Box; + type Error = RhaiError; #[inline(always)] - fn try_from(value: String) -> Result { + fn try_from(value: String) -> RhaiResultOf { let s: Identifier = value.into(); Self::try_from(s) } } impl TryFrom> for FnPtr { - type Error = Box; + type Error = RhaiError; #[inline(always)] - fn try_from(value: Box) -> Result { + fn try_from(value: Box) -> RhaiResultOf { let s: Identifier = value.into(); Self::try_from(s) } } impl TryFrom<&str> for FnPtr { - type Error = Box; + type Error = RhaiError; #[inline(always)] - fn try_from(value: &str) -> Result { + fn try_from(value: &str) -> RhaiResultOf { let s: Identifier = value.into(); Self::try_from(s) } diff --git a/src/types/interner.rs b/src/types/interner.rs new file mode 100644 index 00000000..05e19fc5 --- /dev/null +++ b/src/types/interner.rs @@ -0,0 +1,95 @@ +use crate::{Identifier, ImmutableString}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; +use std::{collections::BTreeMap, ops::AddAssign}; + +/// _(internals)_ A factory of identifiers from text strings. +/// Exported under the `internals` feature only. +/// +/// Normal identifiers, property getters and setters are interned separately. +#[derive(Debug, Clone, Default, Hash)] +pub struct StringsInterner { + /// Normal strings. + strings: BTreeMap, + /// Property getters. + #[cfg(not(feature = "no_object"))] + getters: BTreeMap, + /// Property setters. + #[cfg(not(feature = "no_object"))] + setters: BTreeMap, +} + +impl StringsInterner { + /// Create a new [`StringsInterner`]. + #[inline] + #[must_use] + pub fn new() -> Self { + Self { + strings: BTreeMap::new(), + #[cfg(not(feature = "no_object"))] + getters: BTreeMap::new(), + #[cfg(not(feature = "no_object"))] + setters: BTreeMap::new(), + } + } + /// Get an identifier from a text string and prefix, adding it to the interner if necessary. + /// + /// # Prefix + /// + /// Currently recognized prefixes are: + /// + /// * `""` - None (normal string) + /// * `"get$"` - Property getter, not available under `no_object` + /// * `"set$"` - Property setter, not available under `no_object` + /// + /// # Panics + /// + /// Panics if the prefix is not recognized. + #[inline] + #[must_use] + pub fn get(&mut self, prefix: impl AsRef, text: impl AsRef) -> ImmutableString { + let (dict, mapper): (_, fn(&str) -> Identifier) = match prefix.as_ref() { + "" => (&mut self.strings, |s| s.into()), + + #[cfg(not(feature = "no_object"))] + crate::engine::FN_GET => (&mut self.getters, crate::engine::make_getter), + #[cfg(not(feature = "no_object"))] + crate::engine::FN_SET => (&mut self.setters, crate::engine::make_setter), + + _ => unreachable!("unsupported prefix {}", prefix.as_ref()), + }; + + if dict.contains_key(text.as_ref()) { + dict.get(text.as_ref()).unwrap().clone() + } else { + let value: ImmutableString = mapper(text.as_ref()).into(); + dict.insert(text.as_ref().into(), value.clone()); + value + } + } +} + +impl AddAssign for StringsInterner { + #[inline(always)] + fn add_assign(&mut self, rhs: Self) { + self.strings.extend(rhs.strings.into_iter()); + #[cfg(not(feature = "no_object"))] + self.getters.extend(rhs.getters.into_iter()); + #[cfg(not(feature = "no_object"))] + self.setters.extend(rhs.setters.into_iter()); + } +} + +impl AddAssign<&Self> for StringsInterner { + #[inline(always)] + fn add_assign(&mut self, rhs: &Self) { + self.strings + .extend(rhs.strings.iter().map(|(k, v)| (k.clone(), v.clone()))); + #[cfg(not(feature = "no_object"))] + self.getters + .extend(rhs.getters.iter().map(|(k, v)| (k.clone(), v.clone()))); + #[cfg(not(feature = "no_object"))] + self.setters + .extend(rhs.setters.iter().map(|(k, v)| (k.clone(), v.clone()))); + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index d20c2242..1012f8b1 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -4,6 +4,7 @@ pub mod dynamic; pub mod error; pub mod fn_ptr; pub mod immutable_string; +pub mod interner; pub mod parse_error; pub mod scope; @@ -11,5 +12,6 @@ pub use dynamic::Dynamic; pub use error::EvalAltResult; pub use fn_ptr::FnPtr; pub use immutable_string::ImmutableString; +pub use interner::StringsInterner; pub use parse_error::{LexError, ParseError, ParseErrorType}; pub use scope::Scope; diff --git a/src/types/parse_error.rs b/src/types/parse_error.rs index d814aa3a..50ab93da 100644 --- a/src/types/parse_error.rs +++ b/src/types/parse_error.rs @@ -1,6 +1,7 @@ //! Module containing error definitions for the parsing process. -use crate::{EvalAltResult, Position}; +use crate::tokenizer::is_valid_identifier; +use crate::{Position, RhaiError, ERR}; #[cfg(feature = "no_std")] use core_error::Error; #[cfg(not(feature = "no_std"))] @@ -65,7 +66,7 @@ impl LexError { } } -/// Type of error encountered when parsing a script. +/// Error encountered when parsing a script. /// /// Some errors never appear when certain features are turned on. /// They still exist so that the application can turn features on and off without going through @@ -108,9 +109,9 @@ pub enum ParseErrorType { DuplicatedSwitchCase, /// A variable name is duplicated. Wrapped value is the variable name. DuplicatedVariable(String), - /// An integer case of a `switch` statement is after a range case. + /// An integer case of a `switch` statement is in an appropriate place. WrongSwitchIntegerCase, - /// The default case of a `switch` statement is not the last. + /// The default case of a `switch` statement is in an appropriate place. WrongSwitchDefaultCase, /// The case condition of a `switch` statement is not appropriate. WrongSwitchCaseCondition, @@ -120,7 +121,7 @@ pub enum ParseErrorType { PropertyExpected, /// Missing a variable name after the `let`, `const`, `for` or `catch` keywords. VariableExpected, - /// An identifier is a reserved keyword. + /// An identifier is a reserved symbol. Reserved(String), /// An expression is of the wrong type. /// Wrapped values are the type requested and type of the actual result. @@ -260,7 +261,8 @@ impl fmt::Display for ParseErrorType { }, Self::LiteralTooLarge(typ, max) => write!(f, "{} exceeds the maximum limit ({})", typ, max), - Self::Reserved(s) => write!(f, "'{}' is a reserved keyword", s), + Self::Reserved(s) if is_valid_identifier(s.chars()) => write!(f, "'{}' is a reserved keyword", s), + Self::Reserved(s) => write!(f, "'{}' is a reserved symbol", s), Self::UnexpectedEOF => f.write_str("Script is incomplete"), Self::WrongSwitchIntegerCase => f.write_str("Integer switch case cannot follow a range case"), Self::WrongSwitchDefaultCase => f.write_str("Default switch case must be the last"), @@ -308,30 +310,30 @@ impl fmt::Display for ParseError { } } -impl From for Box { +impl From for RhaiError { #[inline(always)] fn from(err: ParseErrorType) -> Self { Box::new(err.into()) } } -impl From for EvalAltResult { +impl From for ERR { #[inline(always)] fn from(err: ParseErrorType) -> Self { - EvalAltResult::ErrorParsing(err, Position::NONE) + ERR::ErrorParsing(err, Position::NONE) } } -impl From for Box { +impl From for RhaiError { #[inline(always)] fn from(err: ParseError) -> Self { Box::new(err.into()) } } -impl From for EvalAltResult { +impl From for ERR { #[inline(always)] fn from(err: ParseError) -> Self { - EvalAltResult::ErrorParsing(*err.0, err.1) + ERR::ErrorParsing(*err.0, err.1) } } diff --git a/src/types/scope.rs b/src/types/scope.rs index 50bf3c69..84c4c446 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -406,7 +406,7 @@ impl<'a> Scope<'a> { self.push(name, value); } Some((index, AccessMode::ReadWrite)) => { - let value_ref = self.values.get_mut(index).expect("valid index"); + let value_ref = self.values.get_mut(index).unwrap(); *value_ref = Dynamic::from(value); } } @@ -446,7 +446,7 @@ impl<'a> Scope<'a> { } Some((_, AccessMode::ReadOnly)) => panic!("variable {} is constant", name.as_ref()), Some((index, AccessMode::ReadWrite)) => { - let value_ref = self.values.get_mut(index).expect("valid index"); + let value_ref = self.values.get_mut(index).unwrap(); *value_ref = Dynamic::from(value); } } @@ -492,7 +492,7 @@ impl<'a> Scope<'a> { #[inline] #[must_use] pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic { - self.values.get_mut(index).expect("valid index") + self.values.get_mut(index).unwrap() } /// Add an alias to an entry in the [`Scope`]. /// @@ -502,7 +502,7 @@ impl<'a> Scope<'a> { #[cfg(not(feature = "no_module"))] #[inline] pub(crate) fn add_entry_alias(&mut self, index: usize, alias: Identifier) -> &mut Self { - let (_, aliases) = self.names.get_mut(index).expect("valid index"); + let (_, aliases) = self.names.get_mut(index).unwrap(); match aliases { None => { let mut list = StaticVec::new_const(); diff --git a/src/unsafe.rs b/src/unsafe.rs index 3ce5b4c9..ab272fa8 100644 --- a/src/unsafe.rs +++ b/src/unsafe.rs @@ -8,35 +8,48 @@ use std::{ }; /// Cast a type into another type. -#[inline] -pub fn unsafe_try_cast(a: A) -> Result { +/// +/// # Undefined Behavior +/// +/// It is UB if the types are not compatible. +#[inline(always)] +#[must_use] +pub fn unsafe_cast(a: A) -> B { + unsafe { + let ret: B = ptr::read(&a as *const _ as *const B); + // We explicitly forget the value immediately after moving out, + // removing any chance of a destructor running or value otherwise + // being used again. + mem::forget(a); + ret + } +} + +/// Cast a type into another type. +#[inline(always)] +#[must_use] +pub fn unsafe_try_cast(a: A) -> Option { if TypeId::of::() == a.type_id() { - // SAFETY: Just checked we have the right type. We explicitly forget the - // value immediately after moving out, removing any chance of a destructor - // running or value otherwise being used again. - unsafe { - let ret: B = ptr::read(&a as *const _ as *const B); - mem::forget(a); - Ok(ret) - } + // SAFETY: Just checked we have the right type. + Some(unsafe_cast(a)) } else { - Err(a) + None } } /// Cast a Boxed type into another type. -#[inline] -pub fn unsafe_cast_box(item: Box) -> Result, Box> { +#[inline(always)] +#[must_use] +pub fn unsafe_cast_box(item: Box) -> Option { // Only allow casting to the exact same type if TypeId::of::() == TypeId::of::() { // SAFETY: just checked whether we are pointing to the correct type unsafe { let raw: *mut dyn Any = Box::into_raw(item as Box); - Ok(Box::from_raw(raw as *mut T)) + Some(*Box::from_raw(raw as *mut T)) } } else { - // Return the consumed item for chaining. - Err(item) + None } } diff --git a/tests/blobs.rs b/tests/blobs.rs index d478c740..5653e1ef 100644 --- a/tests/blobs.rs +++ b/tests/blobs.rs @@ -162,6 +162,13 @@ fn test_blobs_parse() -> Result<(), Box> { 0x1cf588 ); + assert_eq!( + engine.eval::( + "let x = blob(16, 0); write_be(x, 0, 8, 0x1234567890abcdef); write_be(x, 8, 8, 0xabcdef1234567890); x" + )?, + vec![0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90] + ); + Ok(()) } @@ -233,3 +240,37 @@ fn test_blobs_parse() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_blobs_write_string() -> Result<(), Box> { + let engine = Engine::new(); + + assert_eq!( + engine.eval::(r#"let x = blob(16, 0); write_ascii(x, 0, 14, "hello, world!"); x"#)?, + "hello, world!\0\0\0".as_bytes() + ); + + assert_eq!( + engine.eval::(r#"let x = blob(10, 0); write_ascii(x, 3..8, "hello, world!"); x"#)?, + "\0\0\0hello\0\0".as_bytes() + ); + + assert_eq!( + engine.eval::( + r#"let x = blob(10, 0); write_ascii(x, 0..9, "❤ hello, ❤ world! ❤❤❤"); x"# + )?, + " hello, \0".as_bytes() + ); + + assert_eq!( + engine.eval::(r#"let x = blob(10, 0); write_utf8(x, 3..9, "❤❤❤❤"); x"#)?, + "\0\0\0\u{2764}\u{2764}\0".as_bytes() + ); + + assert_eq!( + engine.eval::(r#"let x = blob(10, 0); write_utf8(x, 3..7, "❤❤❤❤"); x"#)?, + vec![0, 0, 0, 226, 157, 164, 226, 0, 0, 0] + ); + + Ok(()) +} diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 2542a0e4..0080dfcd 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -69,6 +69,36 @@ fn test_call_fn() -> Result<(), Box> { Ok(()) } +#[test] +fn test_call_fn_scope() -> Result<(), Box> { + let engine = Engine::new(); + let mut scope = Scope::new(); + + let ast = engine.compile( + " + fn foo(x) { + let hello = 42; + bar + hello + x + } + + let bar = 123; + ", + )?; + + for _ in 0..50 { + assert_eq!( + engine + .call_fn_raw(&mut scope, &ast, true, false, "foo", None, [Dynamic::THREE])? + .as_int()?, + 168 + ); + } + + assert_eq!(scope.len(), 100); + + Ok(()) +} + struct Options { pub foo: bool, pub bar: String, @@ -296,7 +326,7 @@ fn test_call_fn_events() -> Result<(), Box> { "end" => engine.call_fn(scope, ast, "end", (event_data,)).unwrap(), // The 'update' event maps to function 'update'. - // This event provides a default implementation when the scripted function is not found. + // This event provides a default implementation when the script-defined function is not found. "update" => engine .call_fn(scope, ast, "update", (event_data,)) .or_else(|err| match *err { diff --git a/tests/custom_syntax.rs b/tests/custom_syntax.rs index f381bdf0..efaf1675 100644 --- a/tests/custom_syntax.rs +++ b/tests/custom_syntax.rs @@ -25,13 +25,13 @@ fn test_custom_syntax() -> Result<(), Box> { ], true, |context, inputs| { - let var_name = inputs[0].get_variable_name().unwrap().to_string(); + let var_name = inputs[0].get_string_value().unwrap(); let op = inputs[1].get_literal_value::().unwrap(); let max = inputs[2].get_literal_value::().unwrap(); let stmt = &inputs[3]; let condition = &inputs[4]; - context.scope_mut().push(var_name.clone(), 0 as INT); + context.scope_mut().push(var_name.to_string(), 0 as INT); let mut count: INT = 0; @@ -151,14 +151,14 @@ fn test_custom_syntax() -> Result<(), Box> { &["var", "$ident$", "=", "$expr$"], true, |context, inputs| { - let var_name = inputs[0].get_variable_name().unwrap().to_string(); + let var_name = inputs[0].get_string_value().unwrap(); let expr = &inputs[1]; // Evaluate the expression let value = context.eval_expression_tree(expr)?; - if !context.scope().is_constant(&var_name).unwrap_or(false) { - context.scope_mut().set_value(var_name, value); + if !context.scope().is_constant(var_name).unwrap_or(false) { + context.scope_mut().set_value(var_name.to_string(), value); Ok(Dynamic::UNIT) } else { Err(format!("variable {} is constant", var_name).into()) @@ -206,7 +206,7 @@ fn test_custom_syntax_raw() -> Result<(), Box> { |context, inputs| { context.scope_mut().push("foo", 999 as INT); - Ok(match inputs[0].get_variable_name().unwrap() { + Ok(match inputs[0].get_string_value().unwrap() { "world" if inputs .last() @@ -239,3 +239,38 @@ fn test_custom_syntax_raw() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_custom_syntax_raw2() -> Result<(), Box> { + let mut engine = Engine::new(); + + engine.register_custom_syntax_raw( + "#", + |symbols, lookahead| match symbols.len() { + 1 if lookahead == "-" => Ok(Some("$symbol$".into())), + 1 => Ok(Some("$int$".into())), + 2 if symbols[1] == "-" => Ok(Some("$int$".into())), + 2 => Ok(None), + 3 => Ok(None), + _ => unreachable!(), + }, + false, + move |_, inputs| { + let id = if inputs.len() == 2 { + -inputs[1].get_literal_value::().unwrap() + } else { + inputs[0].get_literal_value::().unwrap() + }; + Ok(id.into()) + }, + ); + + assert_eq!(engine.eval::("#-1")?, -1); + assert_eq!(engine.eval::("let x = 41; x + #1")?, 42); + #[cfg(not(feature = "no_object"))] + assert_eq!(engine.eval::("#-42.abs()")?, 42); + assert_eq!(engine.eval::("#42/2")?, 21); + assert_eq!(engine.eval::("sign(#1)")?, 1); + + Ok(()) +} diff --git a/tests/data_size.rs b/tests/data_size.rs index 303496f8..a4dc1818 100644 --- a/tests/data_size.rs +++ b/tests/data_size.rs @@ -30,7 +30,7 @@ fn test_max_string_size() -> Result<(), Box> { assert!(matches!( *engine - .eval::( + .run( r#" let x = "hello, "; let y = "world!"; @@ -44,7 +44,7 @@ fn test_max_string_size() -> Result<(), Box> { #[cfg(not(feature = "no_object"))] assert!(matches!( *engine - .eval::( + .run( r#" let x = "hello"; x.pad(100, '!'); @@ -90,7 +90,7 @@ fn test_max_array_size() -> Result<(), Box> { assert!(matches!( *engine - .eval::( + .run( " let x = [1,2,3,4,5,6]; let y = [7,8,9,10,11,12]; @@ -101,10 +101,22 @@ fn test_max_array_size() -> Result<(), Box> { EvalAltResult::ErrorDataTooLarge(_, _) )); + assert!(matches!( + *engine + .run( + " + let x = [ 42 ]; + loop { x[0] = x; } + " + ) + .expect_err("should error"), + EvalAltResult::ErrorDataTooLarge(_, _) + )); + #[cfg(not(feature = "no_object"))] assert!(matches!( *engine - .eval::( + .run( " let x = [1,2,3,4,5,6]; x.pad(100, 42); @@ -117,7 +129,7 @@ fn test_max_array_size() -> Result<(), Box> { assert!(matches!( *engine - .eval::( + .run( " let x = [1,2,3]; [x, x, x, x] @@ -130,7 +142,7 @@ fn test_max_array_size() -> Result<(), Box> { #[cfg(not(feature = "no_object"))] assert!(matches!( *engine - .eval::( + .run( " let x = #{a:1, b:2, c:3}; [x, x, x, x] @@ -142,7 +154,7 @@ fn test_max_array_size() -> Result<(), Box> { assert!(matches!( *engine - .eval::( + .run( " let x = [1]; let y = [x, x]; @@ -220,7 +232,7 @@ fn test_max_map_size() -> Result<(), Box> { assert!(matches!( *engine - .eval::( + .run( " let x = #{a:1,b:2,c:3,d:4,e:5,f:6}; let y = #{g:7,h:8,i:9,j:10,k:11,l:12}; @@ -233,7 +245,7 @@ fn test_max_map_size() -> Result<(), Box> { assert!(matches!( *engine - .eval::( + .run( " let x = #{a:1,b:2,c:3}; #{u:x, v:x, w:x, z:x} @@ -246,7 +258,7 @@ fn test_max_map_size() -> Result<(), Box> { #[cfg(not(feature = "no_index"))] assert!(matches!( *engine - .eval::( + .run( " let x = [1, 2, 3]; #{u:x, v:x, w:x, z:x} diff --git a/tests/eval.rs b/tests/eval.rs index 18b4145d..5187acf8 100644 --- a/tests/eval.rs +++ b/tests/eval.rs @@ -6,6 +6,20 @@ fn test_eval() -> Result<(), Box> { assert_eq!(engine.eval::(r#"eval("40 + 2")"#)?, 42); + assert_eq!( + engine.eval::( + r#" + let foo = 42; + + eval("let foo = 123"); + eval("let xyz = 10"); + + foo + xyz + "# + )?, + 133 + ); + Ok(()) } @@ -18,9 +32,9 @@ fn test_eval_blocks() -> Result<(), Box> { r#" let x = 999; - eval("let x = x + 123"); + eval("let x = x - 1000"); - let y = if x > 0 { + let y = if x < 0 { eval("let x = 42"); x } else { @@ -30,7 +44,73 @@ fn test_eval_blocks() -> Result<(), Box> { x + y "# )?, - 1164 + 41 + ); + + assert_eq!( + engine.eval::( + r#" + let foo = 42; + + eval("{ let foo = 123; }"); + + foo + "# + )?, + 42 + ); + + assert_eq!( + engine.eval::( + r#" + let foo = 42; + { { { + eval("let foo = 123"); + } } } + foo + "# + )?, + 42 + ); + + Ok(()) +} + +#[cfg(not(feature = "no_function"))] +#[cfg(not(feature = "no_module"))] +#[test] +fn test_eval_globals() -> Result<(), Box> { + let engine = Engine::new(); + + assert_eq!( + engine.eval::( + r#" + const XYZ = 123; + + fn foo() { global::XYZ } + { + eval("const XYZ = 42;"); + } + + foo() + "# + )?, + 123 + ); + + assert_eq!( + engine.eval::( + r#" + const XYZ = 123; + + fn foo() { global::XYZ } + + eval("const XYZ = 42;"); + + foo() + "# + )?, + 42 ); Ok(()) diff --git a/tests/for.rs b/tests/for.rs index 59e7223a..62112a12 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -371,3 +371,32 @@ fn test_for_module_iterator() -> Result<(), Box> { Ok(()) } + +#[test] +#[cfg(not(feature = "no_index"))] +#[cfg(not(feature = "no_closure"))] +fn test_for_capture() -> Result<(), Box> { + let engine = Engine::new(); + + assert_eq!( + engine.eval::( + " + let a = []; + + for (x, i) in 100..110 { + a += || i + x; + } + + let sum = 0; + + for fp in a { + sum += call(fp); + } + + sum + " + )?, + 1180 + ); + Ok(()) +} diff --git a/tests/functions.rs b/tests/functions.rs index 491f9559..a81c56a3 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -181,5 +181,43 @@ fn test_functions_bang() -> Result<(), Box> { 123 ); + assert_eq!( + engine.eval::( + " + fn foo() { + let hello = bar + 42; + } + + let bar = 999; + let hello = 123; + + foo!(); + + hello + ", + )?, + 123 + ); + + assert_eq!( + engine.eval::( + r#" + fn foo(x) { + let hello = bar + 42 + x; + } + + let bar = 999; + let hello = 123; + + let f = Fn("foo"); + + call!(f, 1); + + hello + "#, + )?, + 123 + ); + Ok(()) } diff --git a/tests/number_literals.rs b/tests/number_literals.rs index 2e7b6b01..daade83b 100644 --- a/tests/number_literals.rs +++ b/tests/number_literals.rs @@ -27,6 +27,11 @@ fn test_hex_literal() -> Result<(), Box> { assert_eq!(engine.eval::("let x = 0Xf; x")?, 15); assert_eq!(engine.eval::("let x = 0xff; x")?, 255); + #[cfg(not(feature = "only_i32"))] + assert_eq!(engine.eval::("let x = 0xffffffffffffffff; x")?, -1); + #[cfg(feature = "only_i32")] + assert_eq!(engine.eval::("let x = 0xffffffff; x")?, -1); + Ok(()) } @@ -51,6 +56,18 @@ fn test_binary_literal() -> Result<(), Box> { engine.eval::("let x = 0b0011_1100_1010_0101; x")?, 15525 ); + #[cfg(not(feature = "only_i32"))] + assert_eq!( + engine.eval::( + "let x = 0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111; x" + )?, + -1 + ); + #[cfg(feature = "only_i32")] + assert_eq!( + engine.eval::("let x = 0b11111111_11111111_11111111_11111111; x")?, + -1 + ); Ok(()) } diff --git a/tests/ops.rs b/tests/ops.rs index 290c9ada..d50bf517 100644 --- a/tests/ops.rs +++ b/tests/ops.rs @@ -10,8 +10,10 @@ fn test_ops() -> Result<(), Box> { Ok(()) } +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] #[test] -fn test_ops_numbers() -> Result<(), Box> { +fn test_ops_other_number_types() -> Result<(), Box> { let engine = Engine::new(); let mut scope = Scope::new(); diff --git a/tests/optimizer.rs b/tests/optimizer.rs index 1a1c05db..b6d6a289 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -80,21 +80,21 @@ fn test_optimizer_parse() -> Result<(), Box> { assert_eq!( format!("{:?}", ast), - "AST { source: None, body: Block[Expr(123 @ 1:53)], functions: Module, resolver: None }" + "AST { source: \"\", body: Block[Expr(123 @ 1:53)], functions: Module, resolver: None }" ); let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?; assert_eq!( format!("{:?}", ast), - r#"AST { source: None, body: Block[Var(false @ 1:18, "DECISION" @ 1:7, (Constant), 1:1), Expr(123 @ 1:51)], functions: Module, resolver: None }"# + r#"AST { source: "", body: Block[Var(false @ 1:18, "DECISION" @ 1:7, (Constant), 1:1), Expr(123 @ 1:51)], functions: Module, resolver: None }"# ); let ast = engine.compile("if 1 == 2 { 42 }")?; assert_eq!( format!("{:?}", ast), - "AST { source: None, body: Block[], functions: Module, resolver: None }" + "AST { source: \"\", body: Block[], functions: Module, resolver: None }" ); engine.set_optimization_level(OptimizationLevel::Full); @@ -103,7 +103,7 @@ fn test_optimizer_parse() -> Result<(), Box> { assert_eq!( format!("{:?}", ast), - "AST { source: None, body: Block[Expr(42 @ 1:1)], functions: Module, resolver: None }" + "AST { source: \"\", body: Block[Expr(42 @ 1:1)], functions: Module, resolver: None }" ); Ok(()) diff --git a/tests/plugins.rs b/tests/plugins.rs index 40e2a31a..5d0099f2 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -1,4 +1,5 @@ -#![cfg(not(any(feature = "no_index", feature = "no_module")))] +#![cfg(not(feature = "no_index"))] +#![cfg(not(feature = "no_module"))] use rhai::plugin::*; use rhai::{Engine, EvalAltResult, INT}; diff --git a/tests/plugins_unroll.rs b/tests/plugins_unroll.rs index d94a7282..366f6dcb 100644 --- a/tests/plugins_unroll.rs +++ b/tests/plugins_unroll.rs @@ -1,4 +1,5 @@ -#![cfg(not(any(feature = "no_index", feature = "no_module")))] +#![cfg(not(feature = "no_index"))] +#![cfg(not(feature = "no_module"))] use rhai::plugin::*; use rhai::{Engine, EvalAltResult, Module, INT}; diff --git a/tests/serde.rs b/tests/serde.rs index 32b0c64e..5e20f04a 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -12,6 +12,7 @@ use rhai::Array; use rhai::Map; #[cfg(not(feature = "no_float"))] use rhai::FLOAT; +#[cfg(not(feature = "no_float"))] #[cfg(feature = "decimal")] use rust_decimal::Decimal; diff --git a/tests/time.rs b/tests/time.rs index b2f0294b..03df04bc 100644 --- a/tests/time.rs +++ b/tests/time.rs @@ -1,5 +1,6 @@ #![cfg(not(feature = "no_std"))] #![cfg(not(target_arch = "wasm32"))] +#![cfg(not(target_arch = "wasm64"))] use rhai::{Engine, EvalAltResult}; diff --git a/tests/tokens.rs b/tests/tokens.rs index 3da33e39..392f8953 100644 --- a/tests/tokens.rs +++ b/tests/tokens.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, LexError, ParseErrorType, INT}; +use rhai::{Engine, EvalAltResult, ParseErrorType, INT}; #[test] fn test_tokens_disabled() { @@ -26,7 +26,7 @@ fn test_tokens_disabled() { assert!(matches!( *engine.compile("let x = += 0;").expect_err("should error").0, - ParseErrorType::BadInput(LexError::UnexpectedInput(err)) if err == "+=" + ParseErrorType::Reserved(err) if err == "+=" )); }