diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5242963f..03e69ede 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,9 +67,9 @@ jobs: strategy: matrix: include: - - {os: ubuntu-latest, flags: "--profile unix -Z unstable-options", experimental: false} - - {os: windows-latest, flags: "--profile windows -Z unstable-options", experimental: true} - - {os: macos-latest, flags: "--profile macos -Z unstable-options", experimental: false} + - {os: ubuntu-latest, flags: "--profile unix", experimental: false} + - {os: windows-latest, flags: "--profile windows", experimental: true} + - {os: macos-latest, flags: "--profile macos", experimental: false} steps: - name: Checkout uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d99ecf9..24034e1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,13 @@ Rhai Release Notes ================== -Version 1.5.0 +Version 1.4.1 ============= Bug fixes --------- +* Expressions such as `x = x + 1` no longer panics. * Padding arrays with another array via `pad` no longer loops indefinitely. * `chop` for arrays and BLOB's now works properly. * `set_bit` for bit-flags with negative index now works correctly. @@ -15,6 +16,8 @@ Bug fixes * Missing `to_hex`, `to_octal` and `to_binary` for `i128` and `u128` are added. * `remove` for arrays and BLOB's now treat negative index correctly. * `parse_int` now works properly for negative numbers. +* `Engine::gen_fn_signatures` now generates signatures for external packages registered via `Engine::register_global_module`. +* `\r\n` pairs are now recognized correctly for doc-comments. Enhancements ------------ @@ -22,6 +25,7 @@ Enhancements * Formatting of return types in functions metadata info is improved. * Use `SmartString` for `Scope` variable names and remove `unsafe` lifetime casting. * Functions in the standard library now have doc-comments (which can be obtained via `Engine::gen_fn_metadata_to_json`). +* `get` and `set` methods are added to arrays, BLOB's, object maps and strings. Version 1.4.0 diff --git a/benches/eval_module.rs b/benches/eval_module.rs index de7843c0..55fb0fb4 100644 --- a/benches/eval_module.rs +++ b/benches/eval_module.rs @@ -3,7 +3,7 @@ ///! Test evaluating with scope extern crate test; -use rhai::{Engine, Module, OptimizationLevel}; +use rhai::{Engine, Module, OptimizationLevel, Scope}; use test::Bencher; #[bench] @@ -18,7 +18,7 @@ fn bench_eval_module(bench: &mut Bencher) { let ast = engine.compile(script).unwrap(); - let module = Module::eval_ast_as_new(Default::default(), &ast, &engine).unwrap(); + let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine).unwrap(); engine.register_static_module("testing", module.into()); diff --git a/codegen/src/attrs.rs b/codegen/src/attrs.rs index 0997430b..2258ede3 100644 --- a/codegen/src/attrs.rs +++ b/codegen/src/attrs.rs @@ -142,33 +142,34 @@ pub fn inner_item_attributes( } #[cfg(feature = "metadata")] -pub fn doc_attributes(attrs: &mut Vec) -> syn::Result> { +pub fn doc_attributes(attrs: &[syn::Attribute]) -> 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(); + for attr in attrs { + if let Some(i) = attr.path.get_ident() { + if *i == "doc" { + match attr.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, "///"); + 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); + } + _ => (), } - - comments.push(line); } - _ => continue, } } diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 29e2a2cf..59f4f5ce 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -407,7 +407,7 @@ impl Parse for ExportedFn { params: Default::default(), cfg_attrs, #[cfg(feature = "metadata")] - comments: Default::default(), + comments: Vec::new(), }) } } diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 14290c59..2a071ffc 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -127,7 +127,7 @@ impl Parse for Module { 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)?); + f.set_comments(crate::attrs::doc_attributes(&item_fn.attrs)?); Ok(f) })?; @@ -144,12 +144,12 @@ impl Parse for Module { attrs, ty, .. - }) if matches!(vis, syn::Visibility::Public(_)) => consts.push(( - ident.to_string(), - ty.clone(), - expr.as_ref().clone(), - crate::attrs::collect_cfg_attr(&attrs), - )), + }) if matches!(vis, syn::Visibility::Public(_)) => consts.push(ExportedConst { + name: ident.to_string(), + typ: ty.clone(), + expr: expr.as_ref().clone(), + cfg_attrs: crate::attrs::collect_cfg_attr(&attrs), + }), _ => {} } } diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index 01bacf62..b922c231 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -10,7 +10,13 @@ use crate::function::{ }; use crate::module::Module; -pub type ExportedConst = (String, Box, syn::Expr, Vec); +#[derive(Debug)] +pub struct ExportedConst { + pub name: String, + pub typ: Box, + pub expr: syn::Expr, + pub cfg_attrs: Vec, +} pub fn generate_body( fns: &mut [ExportedFn], @@ -25,7 +31,12 @@ pub fn generate_body( let str_type_path = syn::parse2::(quote! { str }).unwrap(); let string_type_path = syn::parse2::(quote! { String }).unwrap(); - for (const_name, _, _, cfg_attrs) in consts { + for ExportedConst { + name: const_name, + cfg_attrs, + .. + } in consts + { let const_literal = syn::LitStr::new(&const_name, Span::call_site()); let const_ref = syn::Ident::new(&const_name, Span::call_site()); diff --git a/codegen/src/test/module.rs b/codegen/src/test/module.rs index 4ba006f7..f4700e83 100644 --- a/codegen/src/test/module.rs +++ b/codegen/src/test/module.rs @@ -38,6 +38,7 @@ mod module_tests { } #[test] + #[cfg(feature = "metadata")] fn one_factory_fn_with_comments_module() { let input_tokens: TokenStream = quote! { pub mod one_fn { @@ -150,9 +151,9 @@ mod module_tests { assert!(item_mod.fns().is_empty()); assert!(item_mod.consts().is_empty()); assert_eq!(item_mod.sub_modules().len(), 1); - assert_eq!(&item_mod.sub_modules()[0].consts()[0].0, "MYSTIC_NUMBER"); + assert_eq!(&item_mod.sub_modules()[0].consts()[0].name, "MYSTIC_NUMBER"); assert_eq!( - item_mod.sub_modules()[0].consts()[0].2, + item_mod.sub_modules()[0].consts()[0].expr, syn::parse2::(quote! { 42 }).unwrap() ); } @@ -211,9 +212,9 @@ mod module_tests { let item_mod = syn::parse2::(input_tokens).unwrap(); assert!(item_mod.fns().is_empty()); assert_eq!(item_mod.consts().len(), 1); - assert_eq!(&item_mod.consts()[0].0, "MYSTIC_NUMBER"); + assert_eq!(&item_mod.consts()[0].name, "MYSTIC_NUMBER"); assert_eq!( - item_mod.consts()[0].2, + item_mod.consts()[0].expr, syn::parse2::(quote! { 42 }).unwrap() ); } @@ -386,6 +387,14 @@ mod generate_tests { let expected_tokens = 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 } @@ -401,8 +410,13 @@ mod generate_tests { #[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()); + 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)] diff --git a/scripts/assignment.rhai b/scripts/assignment.rhai index 93a03c45..2d713b43 100644 --- a/scripts/assignment.rhai +++ b/scripts/assignment.rhai @@ -1,5 +1,5 @@ -print("x should be 78:"); +// This script contains a single assignment statement. let x = 78; -print(x); +print(`x should be 78: ${x}`); diff --git a/scripts/doc-comments.rhai b/scripts/doc-comments.rhai index 9245065d..6717039f 100644 --- a/scripts/doc-comments.rhai +++ b/scripts/doc-comments.rhai @@ -3,9 +3,9 @@ /// /// # Parameters /// -/// `x` - `i64` -/// `y` - `string` -/// `z` - `bool` +/// * `x` - `i64` +/// * `y` - `string` +/// * `z` - `bool` /// /// # Notes /// @@ -13,6 +13,13 @@ /// /// An example is the `rhai-doc` app. /// +/// # Example +/// +/// ```rhai +/// let x = foo(42, "hello", true); +/// +/// print(x); // prints 47 +/// ``` fn foo(x, y, z) { print(`hello, world! ${if z { x + y.len() } else { x } }`); } diff --git a/scripts/for1.rhai b/scripts/for1.rhai index 4818b202..fe022762 100644 --- a/scripts/for1.rhai +++ b/scripts/for1.rhai @@ -1,7 +1,8 @@ -// This script runs for-loops +// This script runs for-loops. let arr = [1, true, 123.456, "hello", 3, 42]; +// Loop over array with counter for (a, i) in arr { for (b, j) in ['x', 42, (), 123, 99, 0.5] { if b > 100 { continue; } diff --git a/scripts/for2.rhai b/scripts/for2.rhai index 5ded413a..478f53c9 100644 --- a/scripts/for2.rhai +++ b/scripts/for2.rhai @@ -1,3 +1,5 @@ +// This script runs for-loops + const MAX = 1_000_000; print(`Iterating an array with ${MAX} items...`); @@ -8,6 +10,7 @@ let now = timestamp(); let list = []; +// Loop over range for i in 0..MAX { list.push(i); } @@ -16,6 +19,7 @@ print(`Time = ${now.elapsed} seconds...`); let sum = 0; +// Loop over array for i in list { sum += i; } diff --git a/scripts/for3.rhai b/scripts/for3.rhai index de799684..d370a388 100644 --- a/scripts/for3.rhai +++ b/scripts/for3.rhai @@ -1,3 +1,5 @@ +// This script runs for-loops with closures. + const MAX = 100; const CHECK = ((MAX - 1) ** 2) * MAX; @@ -9,6 +11,7 @@ print(`Creating ${MAX} closures...`); let list = []; +// Loop over range for i in 0..MAX { list.push(|| i ** 2); } @@ -18,6 +21,7 @@ print(`Summing ${MAX} closures...`); let sum = 0; +// Loop over array for f in list { sum += f.call(); } diff --git a/scripts/function_decl1.rhai b/scripts/function_decl1.rhai index 2e6a8958..8c237760 100644 --- a/scripts/function_decl1.rhai +++ b/scripts/function_decl1.rhai @@ -1,9 +1,9 @@ -// This script defines a function and calls it +// This script defines a function and calls it. -fn bob() { +fn call_me() { return 3; } -let result = bob(); +let result = call_me(); -print(`bob() should be 3: ${result}`); +print(`call_me() should be 3: ${result}`); diff --git a/scripts/function_decl2.rhai b/scripts/function_decl2.rhai index 99507209..3640537f 100644 --- a/scripts/function_decl2.rhai +++ b/scripts/function_decl2.rhai @@ -1,14 +1,14 @@ -// This script defines a function with two parameters +// This script defines a function with two parameters and local variables. let a = 3; -fn addme(a, b) { +fn add(a, b) { a = 42; // notice that 'a' is passed by value a + b; // notice that the last value is returned even if terminated by a semicolon } -let result = addme(a, 4); +let result = add(a, 4); -print(`addme(a, 4) should be 46: ${result}`); +print(`add(a, 4) should be 46: ${result}`); -print(`a should still be 3: ${a}`); // should print 3 - 'a' is never changed +print(`a should still be 3: ${a}`); // prints 3: 'a' is never changed diff --git a/scripts/function_decl3.rhai b/scripts/function_decl3.rhai index c1c84631..2cc9c6d9 100644 --- a/scripts/function_decl3.rhai +++ b/scripts/function_decl3.rhai @@ -1,9 +1,11 @@ -// This script defines a function with many parameters and calls it +// This script defines a function with many parameters. +// const KEY = 38; fn f(a, b, c, d, e, f) { - a - b * c - d * e - f + global::KEY + let x = global::KEY; // <- access global module + a - b * c - d * e - f + x } let result = f(100, 5, 2, 9, 6, 32); diff --git a/scripts/function_decl4.rhai b/scripts/function_decl4.rhai index a09ed3fc..e5bc9271 100644 --- a/scripts/function_decl4.rhai +++ b/scripts/function_decl4.rhai @@ -1,4 +1,4 @@ -// This script defines a function that acts as a method +// This script defines a function that acts as a method. // Use 'this' to refer to the object of a method call fn action(x, y) { diff --git a/scripts/if1.rhai b/scripts/if1.rhai index 4e264d64..ed74c227 100644 --- a/scripts/if1.rhai +++ b/scripts/if1.rhai @@ -1,3 +1,5 @@ +// This script runs if statements. + let a = 42; let b = 123; let x = 999; @@ -7,7 +9,7 @@ if a > b { } else if a < b { print("a < b, x should be 0"); - let x = 0; // this 'x' shadows the global 'x' + let x = 0; // <- this 'x' shadows the global 'x' print(x); // should print 0 } else { print("Oops! a == b"); diff --git a/scripts/if2.rhai b/scripts/if2.rhai index a42bb337..202fac89 100644 --- a/scripts/if2.rhai +++ b/scripts/if2.rhai @@ -1,7 +1,9 @@ +// This script runs an if expression. + let a = 42; let b = 123; -let x = if a <= b { // if-expression +let x = if a <= b { // <- if-expression b - a } else { a - b diff --git a/scripts/loop.rhai b/scripts/loop.rhai index 707152c4..0cfab44d 100644 --- a/scripts/loop.rhai +++ b/scripts/loop.rhai @@ -1,4 +1,4 @@ -// This script runs an infinite loop, ending it with a break statement +// This script runs an infinite loop, ending it with a break statement. let x = 10; diff --git a/scripts/mat_mul.rhai b/scripts/mat_mul.rhai index 35b1df70..40283aa7 100644 --- a/scripts/mat_mul.rhai +++ b/scripts/mat_mul.rhai @@ -1,12 +1,14 @@ +// This script simulates multi-dimensional matrix calculations. + const SIZE = 50; fn new_mat(x, y) { let row = []; row.pad(y, 0.0); - + let matrix = []; matrix.pad(x, row); - + matrix } @@ -20,13 +22,13 @@ fn mat_gen() { m[i][j] = tmp * (i - j) * (i + j); } } - + m } fn mat_mul(a, b) { let b2 = new_mat(a[0].len, b[0].len); - + for i in 0..a[0].len { for j in 0..b[0].len { b2[j][i] = b[i][j]; @@ -38,7 +40,7 @@ fn mat_mul(a, b) { for i in 0..c.len { for j in 0..c[i].len { c[i][j] = 0.0; - + for z in 0..a[i].len { c[i][j] += a[i][z] * b2[j][z]; } diff --git a/scripts/module.rhai b/scripts/module.rhai index f92fb162..29a6704d 100644 --- a/scripts/module.rhai +++ b/scripts/module.rhai @@ -1,3 +1,5 @@ +// This script imports an external script as a module. + import "loop" as x; print(`Module test! foo = ${x::foo}`); diff --git a/scripts/op1.rhai b/scripts/op1.rhai index bedfa563..27333f9a 100644 --- a/scripts/op1.rhai +++ b/scripts/op1.rhai @@ -1,3 +1,5 @@ +// This script runs a single expression. + print("The result should be 46:"); print(34 + 12); diff --git a/scripts/op2.rhai b/scripts/op2.rhai index 471e8ee4..37481557 100644 --- a/scripts/op2.rhai +++ b/scripts/op2.rhai @@ -1,3 +1,5 @@ +// This script runs a complex expression. + print("The result should be 182:"); let x = 12 + 34 * 5; diff --git a/scripts/op3.rhai b/scripts/op3.rhai index 73cec23b..30f57dcc 100644 --- a/scripts/op3.rhai +++ b/scripts/op3.rhai @@ -1,3 +1,5 @@ +// This script runs a complex expression. + print("The result should be 230:"); let x = (12 + 34) * 5; diff --git a/scripts/speed_test.rhai b/scripts/speed_test.rhai index d07dadf3..219ddb71 100644 --- a/scripts/speed_test.rhai +++ b/scripts/speed_test.rhai @@ -1,5 +1,4 @@ -// This script runs 1 million iterations -// to test the speed of the scripting engine. +// This script runs 1 million iterations to test the speed of the scripting engine. let now = timestamp(); let x = 1_000_000; diff --git a/scripts/string.rhai b/scripts/string.rhai index 21ea022b..12d9a75e 100644 --- a/scripts/string.rhai +++ b/scripts/string.rhai @@ -1,4 +1,4 @@ -// This script tests string operations +// This script tests string operations. print("hello"); print("this\nis \\ nice"); // escape sequences diff --git a/scripts/strings_map.rhai b/scripts/strings_map.rhai index ec528ecf..0acaf361 100644 --- a/scripts/strings_map.rhai +++ b/scripts/strings_map.rhai @@ -1,3 +1,5 @@ +// This script tests object maps and strings. + print("Ready... Go!"); let now = timestamp(); diff --git a/scripts/switch.rhai b/scripts/switch.rhai index 971ff989..77292630 100644 --- a/scripts/switch.rhai +++ b/scripts/switch.rhai @@ -1,13 +1,22 @@ +// This script runs a switch statement in a for-loop. + let arr = [42, 123.456, "hello", true, "hey", 'x', 999, 1, 2, 3, 4]; for item in arr { switch item { + // Match single integer 42 => print("The Answer!"), + // Match single floating-point number 123.456 => print(`Floating point... ${item}`), + // Match single string "hello" => print(`${item} world!`), + // Match another integer 999 => print(`Got 999: ${item}`), + // Match range with condition 0..100 if item % 2 == 0 => print(`A small even number: ${item}`), + // Match another range 0..100 => print(`A small odd number: ${item}`), + // Default case _ => print(`Something else: <${item}> is ${type_of(item)}`) } } diff --git a/scripts/while.rhai b/scripts/while.rhai index e17493b2..0dd575c1 100644 --- a/scripts/while.rhai +++ b/scripts/while.rhai @@ -1,4 +1,4 @@ -// This script runs a while loop +// This script runs a while loop. let x = 10; diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index 3bb29821..c35c49af 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -151,7 +151,7 @@ impl Deref for Expression<'_> { } } -impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_> { +impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_, '_, '_> { /// Evaluate an [expression tree][Expression]. /// /// # WARNING - Low Level API diff --git a/src/api/register.rs b/src/api/register.rs index 71ef20e9..b489a2b8 100644 --- a/src/api/register.rs +++ b/src/api/register.rs @@ -60,12 +60,12 @@ impl Engine { #[cfg(feature = "metadata")] let mut param_type_names: crate::StaticVec<_> = F::param_names() .iter() - .map(|ty| format!("_: {}", self.map_type_name(ty))) + .map(|ty| format!("_: {}", self.format_type_name(ty))) .collect(); #[cfg(feature = "metadata")] if F::return_type() != TypeId::of::<()>() { - param_type_names.push(self.map_type_name(F::return_type_name()).into()); + param_type_names.push(self.format_type_name(F::return_type_name()).into()); } #[cfg(feature = "metadata")] @@ -122,9 +122,9 @@ impl Engine { #[cfg(feature = "metadata")] let param_type_names: crate::StaticVec<_> = F::param_names() .iter() - .map(|ty| format!("_: {}", self.map_type_name(ty))) + .map(|ty| format!("_: {}", self.format_type_name(ty))) .chain(std::iter::once( - self.map_type_name(F::return_type_name()).into(), + self.format_type_name(F::return_type_name()).into(), )) .collect(); @@ -1027,7 +1027,8 @@ impl Engine { /// Functions from the following sources are included, in order: /// 1) Functions registered into the global namespace /// 2) Functions in registered sub-modules - /// 3) Functions in packages (optional) + /// 3) Functions in registered packages + /// 4) Functions in standard packages (optional) #[cfg(feature = "metadata")] #[inline] #[must_use] @@ -1040,14 +1041,13 @@ impl Engine { signatures.extend(m.gen_fn_signatures().map(|f| format!("{}::{}", name, f))) }); - if include_packages { - signatures.extend( - self.global_modules - .iter() - .skip(1) - .flat_map(|m| m.gen_fn_signatures()), - ); - } + signatures.extend( + self.global_modules + .iter() + .skip(1) + .filter(|m| !m.internal && (include_packages || !m.standard)) + .flat_map(|m| m.gen_fn_signatures()), + ); signatures } diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index a4580c1b..136356a1 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -45,7 +45,7 @@ impl OpAssignment<'_> { #[must_use] pub fn new_from_token(op: Token) -> Self { let op_raw = op - .map_op_assignment() + .get_base_op_from_assignment() .expect("op-assignment operator") .literal_syntax(); Self { @@ -54,6 +54,26 @@ impl OpAssignment<'_> { op: op.literal_syntax(), } } + /// Create a new [`OpAssignment`] from a base operator. + /// + /// # Panics + /// + /// Panics if the name is not an operator that can be converted into an op-operator. + #[must_use] + #[inline(always)] + pub fn new_from_base(name: &str) -> Self { + Self::new_from_base_token(Token::lookup_from_syntax(name).expect("operator")) + } + /// Convert a [`Token`] into a new [`OpAssignment`]. + /// + /// # Panics + /// + /// Panics if the token is cannot be converted into an op-assignment operator. + #[inline(always)] + #[must_use] + pub fn new_from_base_token(op: Token) -> Self { + Self::new_from_token(op.convert_to_op_assignment().expect("operator")) + } } /// _(internals)_ A scoped block of statements. diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs index ddbdbd92..1b77f562 100644 --- a/src/bin/rhai-repl.rs +++ b/src/bin/rhai-repl.rs @@ -1,4 +1,4 @@ -use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST}; +use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST, INT}; use std::{ env, @@ -46,6 +46,7 @@ fn print_help() { println!("help => print this help"); println!("quit, exit => quit"); println!("scope => print all variables in the scope"); + println!("strict => toggle on/off Strict Variables Mode"); #[cfg(feature = "metadata")] println!("functions => print all functions defined"); #[cfg(feature = "metadata")] @@ -56,6 +57,30 @@ fn print_help() { println!(); } +/// Display the scope. +fn print_scope(scope: &Scope) { + scope + .iter_raw() + .enumerate() + .for_each(|(i, (name, constant, value))| { + #[cfg(not(feature = "no_closure"))] + let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; + #[cfg(feature = "no_closure")] + let value_is_shared = ""; + + println!( + "[{}] {}{}{} = {:?}", + i + 1, + if constant { "const " } else { "" }, + name, + value_is_shared, + *value.read_lock::().unwrap(), + ) + }); + + println!(); +} + fn main() { let title = format!("Rhai REPL tool (version {})", env!("CARGO_PKG_VERSION")); println!("{}", title); @@ -155,8 +180,12 @@ fn main() { engine.set_module_resolver(resolver); } - // Make Engine immutable - let engine = engine; + engine + .register_fn("test", |x: INT, y: INT| format!("{} {}", x, y)) + .register_fn("test", |x: &mut INT, y: INT, z: &str| { + *x += y; + println!("{} {} {}", x, y, z); + }); // Create scope let mut scope = Scope::new(); @@ -208,26 +237,18 @@ fn main() { continue; } "exit" | "quit" => break, // quit + "strict" if engine.strict_variables() => { + engine.set_strict_variables(false); + println!("Strict Variables Mode turned OFF."); + continue; + } + "strict" => { + engine.set_strict_variables(true); + println!("Strict Variables Mode turned ON."); + continue; + } "scope" => { - scope - .iter_raw() - .enumerate() - .for_each(|(i, (name, constant, value))| { - #[cfg(not(feature = "no_closure"))] - let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; - #[cfg(feature = "no_closure")] - let value_is_shared = ""; - - println!( - "[{}] {}{}{} = {:?}", - i + 1, - if constant { "const " } else { "" }, - name, - value_is_shared, - *value.read_lock::().unwrap(), - ) - }); - println!(); + print_scope(&scope); continue; } "astu" => { diff --git a/src/engine.rs b/src/engine.rs index 8bbe3046..58abe68b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -329,13 +329,47 @@ impl Engine { /// /// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name], /// the type name provided for the registration will be used. + /// + /// # Panics + /// + /// Panics if the type name is `&mut`. #[inline] #[must_use] pub fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str { self.type_names .get(name) .map(|s| s.as_str()) - .unwrap_or_else(|| map_std_type_name(name)) + .unwrap_or_else(|| map_std_type_name(name, true)) + } + + /// Format a type name. + /// + /// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name], + /// the type name provided for the registration will be used. + #[cfg(feature = "metadata")] + #[inline] + #[must_use] + pub(crate) fn format_type_name<'a>(&'a self, name: &'a str) -> std::borrow::Cow<'a, str> { + if name.starts_with("&mut ") { + let x = &name[5..]; + let r = self.format_type_name(x); + return if x != r { + format!("&mut {}", r).into() + } else { + name.into() + }; + } + + self.type_names + .get(name) + .map(|s| s.as_str()) + .unwrap_or_else(|| match name { + "INT" => return type_name::(), + #[cfg(not(feature = "no_float"))] + "FLOAT" => return type_name::(), + _ => map_std_type_name(name, false), + }) + .into() } /// Make a `Box<`[`EvalAltResult`][ERR::ErrorMismatchDataType]`>`. diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index d2589f0b..ee225503 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -968,18 +968,11 @@ impl Engine { _ if use_indexers => { let args = &mut [target, &mut idx]; let hash_get = crate::ast::FnCallHashes::from_native(global.hash_idx_get()); + let fn_name = crate::engine::FN_IDX_GET; + let pos = Position::NONE; + self.exec_fn_call( - global, - state, - lib, - crate::engine::FN_IDX_GET, - hash_get, - args, - true, - true, - Position::NONE, - None, - level, + global, state, lib, fn_name, hash_get, args, true, true, pos, None, level, ) .map(|(v, _)| v.into()) } diff --git a/src/eval/eval_context.rs b/src/eval/eval_context.rs index 630b8cea..7b0aa5dd 100644 --- a/src/eval/eval_context.rs +++ b/src/eval/eval_context.rs @@ -7,15 +7,15 @@ use std::prelude::v1::*; /// Context of a script evaluation process. #[derive(Debug)] -pub struct EvalContext<'a, 'x, 'px, 'm, 's, 'b, 't, 'pt> { +pub struct EvalContext<'a, 'x, 'px, 'm, 'pm, 's, 'ps, 'b, 't, 'pt> { /// The current [`Engine`]. pub(crate) engine: &'a Engine, /// The current [`Scope`]. pub(crate) scope: &'x mut Scope<'px>, /// The current [`GlobalRuntimeState`]. - pub(crate) global: &'m mut GlobalRuntimeState, + pub(crate) global: &'m mut GlobalRuntimeState<'pm>, /// The current [evaluation state][EvalState]. - pub(crate) state: &'s mut EvalState, + pub(crate) state: &'s mut EvalState<'ps>, /// The current stack of imported [modules][Module]. pub(crate) lib: &'b [&'b Module], /// The current bound `this` pointer, if any. @@ -24,7 +24,7 @@ pub struct EvalContext<'a, 'x, 'px, 'm, 's, 'b, 't, 'pt> { pub(crate) level: usize, } -impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, 'pt> { +impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, '_, '_, 'pt> { /// The current [`Engine`]. #[inline(always)] #[must_use] @@ -56,7 +56,7 @@ impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, 'pt> { #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn iter_imports(&self) -> impl Iterator { - self.global.iter_modules() + self.global.iter_imports() } /// _(internals)_ The current [`GlobalRuntimeState`]. /// Exported under the `internals` feature only. diff --git a/src/eval/eval_state.rs b/src/eval/eval_state.rs index 3d15fb34..d0e9b2bc 100644 --- a/src/eval/eval_state.rs +++ b/src/eval/eval_state.rs @@ -3,13 +3,14 @@ use crate::func::call::FnResolutionCache; use crate::StaticVec; use std::collections::BTreeMap; +use std::marker::PhantomData; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// _(internals)_ A type that holds all the current states of the [`Engine`]. /// Exported under the `internals` feature only. #[derive(Debug, Clone)] -pub struct EvalState { +pub struct EvalState<'a> { /// Force a [`Scope`] search by name. /// /// Normally, access to variables are parsed with a relative offset into the [`Scope`] to avoid a lookup. @@ -25,17 +26,20 @@ pub struct EvalState { pub scope_level: usize, /// Stack of function resolution caches. fn_resolution_caches: StaticVec, + /// Take care of the lifetime parameter + dummy: PhantomData>, } -impl EvalState { +impl EvalState<'_> { /// Create a new [`EvalState`]. #[inline(always)] #[must_use] - pub const fn new() -> Self { + pub fn new() -> Self { Self { always_search_scope: false, scope_level: 0, fn_resolution_caches: StaticVec::new_const(), + dummy: PhantomData::default(), } } /// Get the number of function resolution cache(s) in the stack. diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 775bb8ae..47fbffef 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -32,12 +32,12 @@ impl Engine { }; if let Some(index) = index { - let offset = global.num_imported_modules() - index.get(); - Some(global.get_shared_module(offset).unwrap()) + let offset = global.num_imports() - index.get(); + Some(global.get_shared_import(offset).unwrap()) } else { global - .find_module(root) - .map(|n| global.get_shared_module(n).unwrap()) + .find_import(root) + .map(|n| global.get_shared_import(n).unwrap()) .or_else(|| self.global_sub_modules.get(root).cloned()) } } diff --git a/src/eval/global_state.rs b/src/eval/global_state.rs index aaa8e50a..b2dc0181 100644 --- a/src/eval/global_state.rs +++ b/src/eval/global_state.rs @@ -8,6 +8,7 @@ use std::{ any::TypeId, fmt, iter::{FromIterator, Rev, Zip}, + marker::PhantomData, }; /// _(internals)_ A stack of imported [modules][Module] plus mutable global runtime states. @@ -19,7 +20,7 @@ use std::{ // 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 GlobalRuntimeState { +pub struct GlobalRuntimeState<'a> { /// Stack of module names. // // We cannot use Cow here because `eval` may load a [module][Module] and @@ -45,20 +46,22 @@ pub struct GlobalRuntimeState { #[cfg(not(feature = "no_function"))] constants: Option>>>, + /// Take care of the lifetime parameter. + dummy: PhantomData<&'a ()>, } -impl Default for GlobalRuntimeState { +impl Default for GlobalRuntimeState<'_> { #[inline(always)] fn default() -> Self { Self::new() } } -impl GlobalRuntimeState { +impl GlobalRuntimeState<'_> { /// Create a new [`GlobalRuntimeState`]. #[inline(always)] #[must_use] - pub const fn new() -> Self { + pub fn new() -> Self { Self { keys: StaticVec::new_const(), modules: StaticVec::new_const(), @@ -72,31 +75,32 @@ impl GlobalRuntimeState { #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] constants: None, + dummy: PhantomData::default(), } } /// Get the length of the stack of globally-imported [modules][Module]. #[inline(always)] #[must_use] - pub fn num_imported_modules(&self) -> usize { + pub fn num_imports(&self) -> usize { self.keys.len() } /// Get the globally-imported [module][Module] at a particular index. #[inline(always)] #[must_use] - pub fn get_shared_module(&self, index: usize) -> Option> { + pub fn get_shared_import(&self, index: usize) -> Option> { self.modules.get(index).cloned() } /// 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_shared_module_mut(&mut self, index: usize) -> Option<&mut Shared> { + pub(crate) fn get_shared_import_mut(&mut self, index: usize) -> Option<&mut Shared> { self.modules.get_mut(index) } /// Get the index of a globally-imported [module][Module] by name. #[inline] #[must_use] - pub fn find_module(&self, name: &str) -> Option { + pub fn find_import(&self, name: &str) -> Option { let len = self.keys.len(); self.keys.iter().rev().enumerate().find_map(|(i, key)| { @@ -109,20 +113,20 @@ impl GlobalRuntimeState { } /// Push an imported [module][Module] onto the stack. #[inline(always)] - pub fn push_module(&mut self, name: impl Into, module: impl Into>) { + pub fn push_import(&mut self, name: impl Into, module: impl Into>) { self.keys.push(name.into()); self.modules.push(module.into()); } /// Truncate the stack of globally-imported [modules][Module] to a particular length. #[inline(always)] - pub fn truncate_modules(&mut self, size: usize) { + pub fn truncate_imports(&mut self, size: usize) { self.keys.truncate(size); self.modules.truncate(size); } /// Get an iterator to the stack of globally-imported [modules][Module] in reverse order. #[allow(dead_code)] #[inline] - pub fn iter_modules(&self) -> impl Iterator { + pub fn iter_imports(&self) -> impl Iterator { self.keys .iter() .rev() @@ -132,26 +136,26 @@ impl GlobalRuntimeState { /// Get an iterator to the stack of globally-imported [modules][Module] in reverse order. #[allow(dead_code)] #[inline] - pub(crate) fn iter_modules_raw(&self) -> impl Iterator)> { + pub(crate) fn iter_imports_raw(&self) -> impl Iterator)> { self.keys.iter().rev().zip(self.modules.iter().rev()) } /// Get an iterator to the stack of globally-imported [modules][Module] in forward order. #[allow(dead_code)] #[inline] - pub(crate) fn scan_modules_raw(&self) -> impl Iterator)> { + pub(crate) fn scan_imports_raw(&self) -> impl Iterator)> { self.keys.iter().zip(self.modules.iter()) } /// 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 { + pub fn contains_qualified_fn(&self, hash: u64) -> bool { self.modules.iter().any(|m| m.contains_qualified_fn(hash)) } /// 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<&str>)> { + pub fn get_qualified_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&str>)> { self.modules .iter() .rev() @@ -230,7 +234,7 @@ impl GlobalRuntimeState { } } -impl IntoIterator for GlobalRuntimeState { +impl IntoIterator for GlobalRuntimeState<'_> { type Item = (Identifier, Shared); type IntoIter = Zip>, Rev; 3]>>>; @@ -244,7 +248,7 @@ impl IntoIterator for GlobalRuntimeState { } } -impl, M: Into>> FromIterator<(K, M)> for GlobalRuntimeState { +impl, M: Into>> FromIterator<(K, M)> for GlobalRuntimeState<'_> { #[inline] fn from_iter>(iter: T) -> Self { let mut lib = Self::new(); @@ -253,7 +257,7 @@ impl, M: Into>> FromIterator<(K, M)> for Glob } } -impl, M: Into>> Extend<(K, M)> for GlobalRuntimeState { +impl, M: Into>> Extend<(K, M)> for GlobalRuntimeState<'_> { #[inline] fn extend>(&mut self, iter: T) { iter.into_iter().for_each(|(k, m)| { @@ -263,7 +267,7 @@ impl, M: Into>> Extend<(K, M)> for GlobalRunt } } -impl fmt::Debug for GlobalRuntimeState { +impl fmt::Debug for GlobalRuntimeState<'_> { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = f.debug_struct("GlobalRuntimeState"); diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index aef4a313..47760a04 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -28,7 +28,7 @@ impl Engine { let orig_always_search_scope = state.always_search_scope; let orig_scope_len = scope.len(); - let orig_mods_len = global.num_imported_modules(); + let orig_mods_len = global.num_imports(); let orig_fn_resolution_caches_len = state.fn_resolution_caches_len(); if restore_orig_state { @@ -38,7 +38,7 @@ impl Engine { let mut result = Dynamic::UNIT; for stmt in statements { - let _mods_len = global.num_imported_modules(); + let _mods_len = global.num_imports(); result = self.eval_stmt( scope, @@ -56,7 +56,7 @@ impl Engine { // Get the extra modules - see if any functions are marked global. // Without global functions, the extra modules never affect function resolution. if global - .scan_modules_raw() + .scan_imports_raw() .skip(_mods_len) .any(|(_, m)| m.contains_indexed_global_functions()) { @@ -82,7 +82,7 @@ impl Engine { if restore_orig_state { scope.rewind(orig_scope_len); state.scope_level -= 1; - global.truncate_modules(orig_mods_len); + global.truncate_imports(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 @@ -778,9 +778,9 @@ 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(); - global.push_module(name, module); + global.push_import(name, module); } else { - global.push_module(name, module); + global.push_import(name, module); } } diff --git a/src/func/call.rs b/src/func/call.rs index b032049b..b20a34f5 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -179,9 +179,9 @@ impl Engine { /// Search order: /// 1) AST - script functions in the AST /// 2) Global namespace - functions registered via Engine::register_XXX - /// 3) Global modules - packages + /// 3) Global registered modules - packages /// 4) Imported modules - functions marked with global namespace - /// 5) Global sub-modules - functions marked with global namespace + /// 5) Static registered modules #[must_use] fn resolve_fn<'s>( &self, @@ -220,7 +220,7 @@ impl Engine { loop { let func = lib .iter() - .find_map(|m| { + .find_map(|&m| { m.get_fn(hash).cloned().map(|func| FnResolutionCacheEntry { func, source: m.id_raw().clone(), @@ -235,13 +235,13 @@ impl Engine { }) }) .or_else(|| { - global - .get_fn(hash) - .map(|(func, source)| FnResolutionCacheEntry { + global.get_qualified_fn(hash).map(|(func, source)| { + FnResolutionCacheEntry { func: func.clone(), source: source .map_or_else(|| Identifier::new_const(), Into::into), - }) + } + }) }) .or_else(|| { self.global_sub_modules.values().find_map(|m| { diff --git a/src/func/native.rs b/src/func/native.rs index 1217aacd..17b93eac 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -65,7 +65,7 @@ pub struct NativeCallContext<'a> { /// Function source, if any. source: Option<&'a str>, /// The current [`GlobalRuntimeState`], if any. - global: Option<&'a GlobalRuntimeState>, + global: Option<&'a GlobalRuntimeState<'a>>, /// The current stack of loaded [modules][Module]. lib: &'a [&'a Module], /// [Position] of the function call. @@ -77,7 +77,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef + 'a + ?Sized> &'a Engine, &'a S, Option<&'a S>, - &'a GlobalRuntimeState, + &'a GlobalRuntimeState<'a>, &'a M, Position, )> for NativeCallContext<'a> @@ -199,7 +199,7 @@ impl<'a> NativeCallContext<'a> { #[cfg(not(feature = "no_module"))] #[inline] pub fn iter_imports(&self) -> impl Iterator { - self.global.iter().flat_map(|&m| m.iter_modules()) + self.global.iter().flat_map(|&m| m.iter_imports()) } /// Get an iterator over the current set of modules imported via `import` statements in reverse order. #[cfg(not(feature = "no_module"))] @@ -208,7 +208,7 @@ impl<'a> NativeCallContext<'a> { pub(crate) fn iter_imports_raw( &self, ) -> impl Iterator)> { - self.global.iter().flat_map(|&m| m.iter_modules_raw()) + self.global.iter().flat_map(|&m| m.iter_imports_raw()) } /// _(internals)_ The current [`GlobalRuntimeState`], if any. /// Exported under the `internals` feature only. diff --git a/src/func/register.rs b/src/func/register.rs index 70085821..90a1e01c 100644 --- a/src/func/register.rs +++ b/src/func/register.rs @@ -107,8 +107,8 @@ macro_rules! def_register { // ^ function ABI type // ^ function parameter generic type name (A, B, C etc.) // ^ call argument(like A, *B, &mut C etc) - // ^ function parameter marker type (T, Ref or Mut) - // ^ function parameter actual type (T, &T or &mut T) + // ^ function parameter marker type (A, Ref or Mut) + // ^ function parameter actual type (A, &B or &mut C) // ^ argument let statement impl< @@ -117,7 +117,7 @@ macro_rules! def_register { RET: Variant + Clone > RegisterNativeFunction<($($mark,)*), ()> 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 param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$param>()),*].into_boxed_slice() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::() } #[inline(always)] fn into_callable_function(self) -> CallableFunction { @@ -145,7 +145,7 @@ macro_rules! def_register { RET: Variant + Clone > RegisterNativeFunction<(NativeCallContext<'static>, $($mark,)*), ()> 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 param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$param>()),*].into_boxed_slice() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::() } #[inline(always)] fn into_callable_function(self) -> CallableFunction { @@ -173,7 +173,7 @@ macro_rules! def_register { RET: Variant + Clone > 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 param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$param>()),*].into_boxed_slice() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::>() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::>() } #[inline(always)] fn into_callable_function(self) -> CallableFunction { @@ -198,7 +198,7 @@ macro_rules! def_register { RET: Variant + Clone > 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 param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$param>()),*].into_boxed_slice() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::>() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::>() } #[inline(always)] fn into_callable_function(self) -> CallableFunction { diff --git a/src/func/script.rs b/src/func/script.rs index baf4a66c..396dfedf 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -70,7 +70,7 @@ impl Engine { } let orig_scope_len = scope.len(); - let orig_mods_len = global.num_imported_modules(); + let orig_mods_len = global.num_imports(); // Put arguments into scope as variables scope.extend(fn_def.params.iter().cloned().zip(args.into_iter().map(|v| { @@ -100,7 +100,7 @@ impl Engine { modules .iter() .cloned() - .for_each(|(n, m)| global.push_module(n, m)); + .for_each(|(n, m)| global.push_import(n, m)); } // Evaluate the function @@ -144,7 +144,7 @@ impl Engine { // Remove arguments only, leaving new variables in the scope scope.remove_range(orig_scope_len, args.len()) } - global.truncate_modules(orig_mods_len); + global.truncate_imports(orig_mods_len); // Restore state state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len); @@ -172,7 +172,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 - || global.map_or(false, |m| m.contains_fn(hash_script)) + || global.map_or(false, |m| m.contains_qualified_fn(hash_script)) // Then check sub-modules || self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script)); diff --git a/src/module/mod.rs b/src/module/mod.rs index 51ce2653..7e79214e 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -92,18 +92,60 @@ impl FuncInfo { /// `()` is cleared. /// [`RhaiResult`][crate::RhaiResult] and [`RhaiResultOf`] are expanded. #[cfg(feature = "metadata")] - pub fn format_return_type(typ: &str) -> std::borrow::Cow { + pub fn format_type(typ: &str, is_return_type: bool) -> std::borrow::Cow { const RHAI_RESULT_TYPE: &str = "RhaiResult"; const RHAI_RESULT_TYPE_EXPAND: &str = "Result>"; const RHAI_RESULT_OF_TYPE: &str = "RhaiResultOf<"; const RHAI_RESULT_OF_TYPE_EXPAND: &str = "Result<{}, Box>"; + const RHAI_RANGE: &str = "ExclusiveRange"; + const RHAI_RANGE_TYPE: &str = "Range<"; + const RHAI_RANGE_EXPAND: &str = "Range<{}>"; + const RHAI_INCLUSIVE_RANGE: &str = "InclusiveRange"; + const RHAI_INCLUSIVE_RANGE_TYPE: &str = "RangeInclusive<"; + const RHAI_INCLUSIVE_RANGE_EXPAND: &str = "RangeInclusive<{}>"; + + let typ = typ.trim(); + + if typ.starts_with("rhai::") { + return Self::format_type(&typ[6..], is_return_type); + } else if typ.starts_with("&mut ") { + let x = &typ[5..]; + let r = Self::format_type(x, false); + return if r == x { + typ.into() + } else { + format!("&mut {}", r).into() + }; + } match typ { - "" | "()" => "".into(), + "" | "()" if is_return_type => "".into(), + "INT" => std::any::type_name::().into(), + #[cfg(not(feature = "no_float"))] + "FLOAT" => std::any::type_name::().into(), + RHAI_RANGE => RHAI_RANGE_EXPAND + .replace("{}", std::any::type_name::()) + .into(), + RHAI_INCLUSIVE_RANGE => RHAI_INCLUSIVE_RANGE_EXPAND + .replace("{}", std::any::type_name::()) + .into(), RHAI_RESULT_TYPE => RHAI_RESULT_TYPE_EXPAND.into(), + ty if ty.starts_with(RHAI_RANGE_TYPE) && ty.ends_with('>') => { + let inner = &ty[RHAI_RANGE_TYPE.len()..ty.len() - 1]; + RHAI_RANGE_EXPAND + .replace("{}", Self::format_type(inner, false).trim()) + .into() + } + ty if ty.starts_with(RHAI_INCLUSIVE_RANGE_TYPE) && ty.ends_with('>') => { + let inner = &ty[RHAI_INCLUSIVE_RANGE_TYPE.len()..ty.len() - 1]; + RHAI_INCLUSIVE_RANGE_EXPAND + .replace("{}", Self::format_type(inner, false).trim()) + .into() + } ty if ty.starts_with(RHAI_RESULT_OF_TYPE) && ty.ends_with('>') => { + let inner = &ty[RHAI_RESULT_OF_TYPE.len()..ty.len() - 1]; RHAI_RESULT_OF_TYPE_EXPAND - .replace("{}", ty[RHAI_RESULT_OF_TYPE.len()..ty.len() - 1].trim()) + .replace("{}", Self::format_type(inner, false).trim()) .into() } ty => ty.into(), @@ -116,22 +158,30 @@ impl FuncInfo { pub fn gen_signature(&self) -> String { let mut sig = format!("{}(", self.metadata.name); - let return_type = Self::format_return_type(&self.metadata.return_type); + let return_type = Self::format_type(&self.metadata.return_type, true); if !self.metadata.params_info.is_empty() { let params: StaticVec<_> = self .metadata .params_info .iter() - .map(|s| s.as_str()) + .map(|s| { + let mut seg = s.splitn(2, ':'); + let name = match seg.next().unwrap().trim() { + "" => "_", + s => s, + }; + let result: std::borrow::Cow = match seg.next() { + Some(typ) => { + format!("{}: {}", name, FuncInfo::format_type(typ, false)).into() + } + None => name.into(), + }; + result + }) .collect(); sig.push_str(¶ms.join(", ")); sig.push(')'); - - if !return_type.is_empty() { - sig.push_str(" -> "); - sig.push_str(&return_type); - } } else { for x in 0..self.metadata.params { sig.push('_'); @@ -139,17 +189,12 @@ impl FuncInfo { sig.push_str(", "); } } - sig.push(')'); + } - if !self.func.is_script() { - sig.push(')'); - - if !return_type.is_empty() { - sig.push_str(" -> "); - sig.push_str(&return_type); - } - } + if !self.func.is_script() && !return_type.is_empty() { + sig.push_str(" -> "); + sig.push_str(&return_type); } sig @@ -541,7 +586,7 @@ impl Module { #[cfg(feature = "metadata")] params_info, #[cfg(feature = "metadata")] - return_type: "Dynamic".into(), + return_type: "".into(), #[cfg(feature = "metadata")] comments: None, }, @@ -705,7 +750,7 @@ impl Module { let return_type = param_names.pop().unwrap(); (param_names, return_type) } else { - (param_names, Default::default()) + (param_names, crate::SmartString::new_const()) }; f.metadata.params_info = param_names; f.metadata.return_type = return_type_name; @@ -837,7 +882,7 @@ impl Module { let return_type = if names.len() > arg_types.as_ref().len() { names.pop().unwrap() } else { - Default::default() + crate::SmartString::new_const() }; names.shrink_to_fit(); (names, return_type) @@ -1402,14 +1447,14 @@ impl Module { /// Merge another [`Module`] into this [`Module`]. #[inline(always)] pub fn merge(&mut self, other: &Self) -> &mut Self { - self.merge_filtered(other, &|_, _, _, _, _| true) + self.merge_filtered(other, |_, _, _, _, _| true) } /// Merge another [`Module`] into this [`Module`] based on a filter predicate. pub(crate) fn merge_filtered( &mut self, other: &Self, - _filter: &impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, + _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool + Copy, ) -> &mut Self { #[cfg(not(feature = "no_function"))] other.modules.iter().for_each(|(k, v)| { @@ -1620,7 +1665,7 @@ impl Module { ) -> RhaiResultOf { let mut scope = scope; let mut global = crate::eval::GlobalRuntimeState::new(); - let orig_mods_len = global.num_imported_modules(); + let orig_mods_len = global.num_imports(); // Run the script engine.eval_ast_with_scope_raw(&mut scope, &mut global, &ast, 0)?; diff --git a/src/optimizer.rs b/src/optimizer.rs index 030ef99c..7bfe8bbf 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -96,7 +96,7 @@ impl<'a> OptimizerState<'a> { pub fn restore_var(&mut self, len: usize) { self.variables.truncate(len) } - /// Add a new constant to the list. + /// Add a new variable to the list. #[inline(always)] pub fn push_var( &mut self, @@ -423,7 +423,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b match x.2 { Expr::FnCall(ref mut x2, _) => { state.set_dirty(); - x.1 = Some(OpAssignment::new(&x2.name)); + x.1 = Some(OpAssignment::new_from_base(&x2.name)); let value = mem::take(&mut x2.args[1]); diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 83af8683..5e7e2eaf 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -31,6 +31,70 @@ pub mod array_functions { pub fn len(array: &mut Array) -> INT { array.len() as INT } + /// Get a copy of the element at the `index` position in the array. + /// + /// * If `index` < 0, position counts from the end of the array (`-1` is the last element). + /// * If `index` < -length of array, `()` is returned. + /// * If `index` ≥ length of array, `()` is returned. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3]; + /// + /// print(x.get(0)); // prints 1 + /// + /// print(x.get(-1)); // prints 3 + /// + /// print(x.get(99)); // prints empty (for '()') + /// ``` + pub fn get(array: &mut Array, index: INT) -> Dynamic { + if array.is_empty() { + return Dynamic::UNIT; + } + + let (index, _) = calc_offset_len(array.len(), index, 0); + + if index >= array.len() { + Dynamic::UNIT + } else { + array[index].clone() + } + } + /// Set the element at the `index` position in the array to a new `value`. + /// + /// * If `index` < 0, position counts from the end of the array (`-1` is the last element). + /// * If `index` < -length of array, the array is not modified. + /// * If `index` ≥ length of array, the array is not modified. + /// + /// # Example + /// + /// ```rhai + /// let x = [1, 2, 3]; + /// + /// x.set(0, 42); + /// + /// print(x); // prints "[42, 2, 3]" + /// + /// x.set(-3, 0); + /// + /// print(x); // prints "[0, 2, 3]" + /// + /// x.set(99, 123); + /// + /// print(x); // prints "[0, 2, 3]" + /// ``` + pub fn set(array: &mut Array, index: INT, value: Dynamic) { + if array.is_empty() { + return; + } + + let (index, _) = calc_offset_len(array.len(), index, 0); + + if index < array.len() { + array[index] = value; + } + } /// Add a new element, which is not another array, to the end of the array. /// /// If `item` is `Array`, then `append` is more specific and will be called instead. @@ -1929,7 +1993,7 @@ pub mod array_functions { ) -> RhaiResultOf { drain(ctx, array, FnPtr::new(filter)?) } - /// Remove all elements in the array within an exclusive range and return them as a new array. + /// Remove all elements in the array within an exclusive `range` and return them as a new array. /// /// # Example /// @@ -1954,7 +2018,7 @@ pub mod array_functions { let end = INT::max(range.end, start); drain_range(array, start, end - start) } - /// Remove all elements in the array within an inclusive range and return them as a new array. + /// Remove all elements in the array within an inclusive `range` and return them as a new array. /// /// # Example /// @@ -2125,7 +2189,7 @@ pub mod array_functions { ) -> RhaiResultOf { retain(ctx, array, FnPtr::new(filter)?) } - /// Remove all elements in the array not within an exclusive range and return them as a new array. + /// Remove all elements in the array not within an exclusive `range` and return them as a new array. /// /// # Example /// @@ -2150,7 +2214,7 @@ pub mod array_functions { let end = INT::max(range.end, start); retain_range(array, start, end - start) } - /// Remove all elements in the array not within an inclusive range and return them as a new array. + /// Remove all elements in the array not within an inclusive `range` and return them as a new array. /// /// # Example /// diff --git a/src/packages/blob_basic.rs b/src/packages/blob_basic.rs index ca240df3..0e1997c0 100644 --- a/src/packages/blob_basic.rs +++ b/src/packages/blob_basic.rs @@ -100,6 +100,75 @@ pub mod blob_functions { pub fn len(blob: &mut Blob) -> INT { blob.len() as INT } + + /// Get the byte value at the `index` position in the BLOB. + /// + /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last element). + /// * If `index` < -length of BLOB, zero is returned. + /// * If `index` ≥ length of BLOB, zero is returned. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// print(b.get(0)); // prints 1 + /// + /// print(b.get(-1)); // prints 5 + /// + /// print(b.get(99)); // prints 0 + /// ``` + pub fn get(blob: &mut Blob, index: INT) -> INT { + if blob.is_empty() { + return 0; + } + + let (index, _) = calc_offset_len(blob.len(), index, 0); + + if index >= blob.len() { + 0 + } else { + blob[index] as INT + } + } + /// Set the particular `index` position in the BLOB to a new byte `value`. + /// + /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `index` < -length of BLOB, the BLOB is not modified. + /// * If `index` ≥ length of BLOB, the BLOB is not modified. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// b.set(0, 0x42); + /// + /// print(b); // prints "[4202030405]" + /// + /// b.set(-3, 0); + /// + /// print(b); // prints "[4202000405]" + /// + /// b.set(99, 123); + /// + /// print(b); // prints "[4202000405]" + /// ``` + pub fn set(blob: &mut Blob, index: INT, value: INT) { + if blob.is_empty() { + return; + } + + let (index, _) = calc_offset_len(blob.len(), index, 0); + + if index < blob.len() { + blob[index] = (value & 0x000000ff) as u8; + } + } /// Add a new byte `value` to the end of the BLOB. /// /// Only the lower 8 bits of the `value` are used; all other bits are ignored. @@ -114,8 +183,7 @@ pub mod blob_functions { /// print(b); // prints "[42]" /// ``` pub fn push(blob: &mut Blob, value: INT) { - let value = (value & 0x000000ff) as u8; - blob.push(value); + blob.push((value & 0x000000ff) as u8); } /// Add another BLOB to the end of the BLOB. /// @@ -396,7 +464,7 @@ pub mod blob_functions { blob.reverse(); } } - /// Replace an exclusive range of the BLOB with another BLOB. + /// Replace an exclusive `range` of the BLOB with another BLOB. /// /// # Example /// @@ -414,7 +482,7 @@ pub mod blob_functions { let end = INT::max(range.end, start); splice(blob, start, end - start, replace) } - /// Replace an inclusive range of the BLOB with another BLOB. + /// Replace an inclusive `range` of the BLOB with another BLOB. /// /// # Example /// @@ -468,7 +536,7 @@ pub mod blob_functions { blob.splice(start..start + len, replace); } } - /// Copy an exclusive range of the BLOB and return it as a new BLOB. + /// Copy an exclusive `range` of the BLOB and return it as a new BLOB. /// /// # Example /// @@ -487,7 +555,7 @@ pub mod blob_functions { let end = INT::max(range.end, start); extract(blob, start, end - start) } - /// Copy an inclusive range of the BLOB and return it as a new BLOB. + /// Copy an inclusive `range` of the BLOB and return it as a new BLOB. /// /// # Example /// @@ -608,7 +676,7 @@ pub mod blob_functions { result } } - /// Remove all bytes in the BLOB within an exclusive range and return them as a new BLOB. + /// Remove all bytes in the BLOB within an exclusive `range` and return them as a new BLOB. /// /// # Example /// @@ -635,7 +703,7 @@ pub mod blob_functions { let end = INT::max(range.end, start); drain(blob, start, end - start) } - /// Remove all bytes in the BLOB within an inclusive range and return them as a new BLOB. + /// Remove all bytes in the BLOB within an inclusive `range` and return them as a new BLOB. /// /// # Example /// @@ -702,7 +770,7 @@ pub mod blob_functions { blob.drain(start..start + len).collect() } } - /// Remove all bytes in the BLOB not within an exclusive range and return them as a new BLOB. + /// Remove all bytes in the BLOB not within an exclusive `range` and return them as a new BLOB. /// /// # Example /// @@ -729,7 +797,7 @@ pub mod blob_functions { let end = INT::max(range.end, start); retain(blob, start, end - start) } - /// Remove all bytes in the BLOB not within an inclusive range and return them as a new BLOB. + /// Remove all bytes in the BLOB not within an inclusive `range` and return them as a new BLOB. /// /// # Example /// @@ -827,33 +895,135 @@ mod parse_int_functions { } } + /// Parse the bytes within an exclusive `range` in the BLOB as an `INT` + /// in little-endian byte order. + /// + /// * If number of bytes in `range` < number of bytes for `INT`, zeros are padded. + /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes are ignored. + /// + /// ```ignore + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// let x = b.parse_le_int(1..3); // parse two bytes + /// + /// print(x.to_hex()); // prints "0302" + /// ``` #[rhai_fn(name = "parse_le_int")] pub fn parse_le_int_range(blob: &mut Blob, range: ExclusiveRange) -> INT { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); parse_le_int(blob, start, end - start) } + /// Parse the bytes within an inclusive `range` in the BLOB as an `INT` + /// in little-endian byte order. + /// + /// * If number of bytes in `range` < number of bytes for `INT`, zeros are padded. + /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes are ignored. + /// + /// ```ignore + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// let x = b.parse_le_int(1..=3); // parse three bytes + /// + /// print(x.to_hex()); // prints "040302" + /// ``` #[rhai_fn(name = "parse_le_int")] pub fn parse_le_int_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> INT { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); parse_le_int(blob, start, end - start + 1) } + /// Parse the bytes beginning at the `start` position in the BLOB as an `INT` + /// in little-endian byte order. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, zero is returned. + /// * If `len` ≤ 0, zero is returned. + /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed. + /// + /// * If number of bytes in range < number of bytes for `INT`, zeros are padded. + /// * If number of bytes in range > number of bytes for `INT`, extra bytes are ignored. + /// + /// ```ignore + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// let x = b.parse_le_int(1, 2); + /// + /// print(x.to_hex()); // prints "0302" + /// ``` pub fn parse_le_int(blob: &mut Blob, start: INT, len: INT) -> INT { parse_int(blob, start, len, true) } + /// Parse the bytes within an exclusive `range` in the BLOB as an `INT` + /// in big-endian byte order. + /// + /// * If number of bytes in `range` < number of bytes for `INT`, zeros are padded. + /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes are ignored. + /// + /// ```ignore + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// let x = b.parse_be_int(1..3); // parse two bytes + /// + /// print(x.to_hex()); // prints "02030000...00" + /// ``` #[rhai_fn(name = "parse_be_int")] pub fn parse_be_int_range(blob: &mut Blob, range: ExclusiveRange) -> INT { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); parse_be_int(blob, start, end - start) } + /// Parse the bytes within an inclusive `range` in the BLOB as an `INT` + /// in big-endian byte order. + /// + /// * If number of bytes in `range` < number of bytes for `INT`, zeros are padded. + /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes are ignored. + /// + /// ```ignore + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// let x = b.parse_be_int(1..=3); // parse three bytes + /// + /// print(x.to_hex()); // prints "0203040000...00" + /// ``` #[rhai_fn(name = "parse_be_int")] pub fn parse_be_int_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> INT { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); parse_be_int(blob, start, end - start + 1) } + /// Parse the bytes beginning at the `start` position in the BLOB as an `INT` + /// in big-endian byte order. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, zero is returned. + /// * If `len` ≤ 0, zero is returned. + /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed. + /// + /// * If number of bytes in range < number of bytes for `INT`, zeros are padded. + /// * If number of bytes in range > number of bytes for `INT`, extra bytes are ignored. + /// + /// ```ignore + /// let b = blob(); + /// + /// b += 1; b += 2; b += 3; b += 4; b += 5; + /// + /// let x = b.parse_be_int(1, 2); + /// + /// print(x.to_hex()); // prints "02030000...00" + /// ``` pub fn parse_be_int(blob: &mut Blob, start: INT, len: INT) -> INT { parse_int(blob, start, len, false) } @@ -887,33 +1057,75 @@ mod parse_float_functions { } } + /// Parse the bytes within an exclusive `range` in the BLOB as a `FLOAT` + /// in little-endian byte order. + /// + /// * If number of bytes in `range` < number of bytes for `FLOAT`, zeros are padded. + /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes are ignored. #[rhai_fn(name = "parse_le_float")] pub fn parse_le_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); parse_le_float(blob, start, end - start) } + /// Parse the bytes within an inclusive `range` in the BLOB as a `FLOAT` + /// in little-endian byte order. + /// + /// * If number of bytes in `range` < number of bytes for `FLOAT`, zeros are padded. + /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes are ignored. #[rhai_fn(name = "parse_le_float")] pub fn parse_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); parse_le_float(blob, start, end - start + 1) } + /// Parse the bytes beginning at the `start` position in the BLOB as a `FLOAT` + /// in little-endian byte order. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, zero is returned. + /// * If `len` ≤ 0, zero is returned. + /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed. + /// + /// * If number of bytes in range < number of bytes for `FLOAT`, zeros are padded. + /// * If number of bytes in range > number of bytes for `FLOAT`, extra bytes are ignored. pub fn parse_le_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT { parse_float(blob, start, len, true) } + /// Parse the bytes within an exclusive `range` in the BLOB as a `FLOAT` + /// in big-endian byte order. + /// + /// * If number of bytes in `range` < number of bytes for `FLOAT`, zeros are padded. + /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes are ignored. #[rhai_fn(name = "parse_be_float")] pub fn parse_be_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); parse_be_float(blob, start, end - start) } + /// Parse the bytes within an inclusive `range` in the BLOB as a `FLOAT` + /// in big-endian byte order. + /// + /// * If number of bytes in `range` < number of bytes for `FLOAT`, zeros are padded. + /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes are ignored. #[rhai_fn(name = "parse_be_float")] pub fn parse_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); parse_be_float(blob, start, end - start + 1) } + /// Parse the bytes beginning at the `start` position in the BLOB as a `FLOAT` + /// in big-endian byte order. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, zero is returned. + /// * If `len` ≤ 0, zero is returned. + /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed. + /// + /// * If number of bytes in range < number of bytes for `FLOAT`, zeros are padded. + /// * If number of bytes in range > number of bytes for `FLOAT`, extra bytes are ignored. pub fn parse_be_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT { parse_float(blob, start, len, false) } @@ -943,34 +1155,124 @@ mod write_int_functions { blob[start..][..len].copy_from_slice(&buf[..len]); } + /// Write an `INT` value to the bytes within an exclusive `range` in the BLOB + /// in little-endian byte order. + /// + /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. + /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. + /// + /// ```ignore + /// let b = blob(8); + /// + /// b.write_le_int(1..3, 0x12345678); + /// + /// print(b); // prints "[0078560000000000]" + /// ``` #[rhai_fn(name = "write_le")] pub fn write_le_int_range(blob: &mut Blob, range: ExclusiveRange, value: INT) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); write_le_int(blob, start, end - start, value) } + /// Write an `INT` value to the bytes within an inclusive `range` in the BLOB + /// in little-endian byte order. + /// + /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. + /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. + /// + /// ```ignore + /// let b = blob(8); + /// + /// b.write_le_int(1..=3, 0x12345678); + /// + /// print(b); // prints "[0078563400000000]" + /// ``` #[rhai_fn(name = "write_le")] pub fn write_le_int_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: INT) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); write_le_int(blob, start, end - start + 1, value) } + /// Write an `INT` value to the bytes beginning at the `start` position in the BLOB + /// in little-endian byte order. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, zero is returned. + /// * If `len` ≤ 0, zero is returned. + /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed. + /// + /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. + /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. + /// + /// ```ignore + /// let b = blob(8); + /// + /// b.write_le_int(1, 3, 0x12345678); + /// + /// print(b); // prints "[0078563400000000]" + /// ``` #[rhai_fn(name = "write_le")] pub fn write_le_int(blob: &mut Blob, start: INT, len: INT, value: INT) { write_int(blob, start, len, value, true) } + /// Write an `INT` value to the bytes within an exclusive `range` in the BLOB + /// in big-endian byte order. + /// + /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. + /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. + /// + /// ```ignore + /// let b = blob(8, 0x42); + /// + /// b.write_be_int(1..3, 0x99); + /// + /// print(b); // prints "[4200004242424242]" + /// ``` #[rhai_fn(name = "write_be")] pub fn write_be_int_range(blob: &mut Blob, range: ExclusiveRange, value: INT) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); write_be_int(blob, start, end - start, value) } + /// Write an `INT` value to the bytes within an inclusive `range` in the BLOB + /// in big-endian byte order. + /// + /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. + /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. + /// + /// ```ignore + /// let b = blob(8, 0x42); + /// + /// b.write_be_int(1..=3, 0x99); + /// + /// print(b); // prints "[4200000042424242]" + /// ``` #[rhai_fn(name = "write_be")] pub fn write_be_int_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: INT) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); write_be_int(blob, start, end - start + 1, value) } + /// Write an `INT` value to the bytes beginning at the `start` position in the BLOB + /// in big-endian byte order. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, zero is returned. + /// * If `len` ≤ 0, zero is returned. + /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed. + /// + /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. + /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. + /// + /// ```ignore + /// let b = blob(8, 0x42); + /// + /// b.write_be_int(1, 3, 0x99); + /// + /// print(b); // prints "[4200000042424242]" + /// ``` #[rhai_fn(name = "write_be")] pub fn write_be_int(blob: &mut Blob, start: INT, len: INT, value: INT) { write_int(blob, start, len, value, false) @@ -1001,34 +1303,76 @@ mod write_float_functions { blob[start..][..len].copy_from_slice(&buf[..len]); } + /// Write a `FLOAT` value to the bytes within an exclusive `range` in the BLOB + /// in little-endian byte order. + /// + /// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written. + /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified. #[rhai_fn(name = "write_le")] pub fn write_le_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); write_le_float(blob, start, end - start, value) } + /// Write a `FLOAT` value to the bytes within an inclusive `range` in the BLOB + /// in little-endian byte order. + /// + /// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written. + /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified. #[rhai_fn(name = "write_le")] pub fn write_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); write_le_float(blob, start, end - start + 1, value) } + /// Write a `FLOAT` value to the bytes beginning at the `start` position in the BLOB + /// in little-endian byte order. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, zero is returned. + /// * If `len` ≤ 0, zero is returned. + /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed. + /// + /// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written. + /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified. #[rhai_fn(name = "write_le")] pub fn write_le_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) { write_float(blob, start, len, value, true) } + /// Write a `FLOAT` value to the bytes within an exclusive `range` in the BLOB + /// in big-endian byte order. + /// + /// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written. + /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified. #[rhai_fn(name = "write_be")] pub fn write_be_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); write_be_float(blob, start, end - start, value) } + /// Write a `FLOAT` value to the bytes within an inclusive `range` in the BLOB + /// in big-endian byte order. + /// + /// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written. + /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified. #[rhai_fn(name = "write_be")] pub fn write_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); write_be_float(blob, start, end - start + 1, value) } + /// Write a `FLOAT` value to the bytes beginning at the `start` position in the BLOB + /// in big-endian byte order. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, zero is returned. + /// * If `len` ≤ 0, zero is returned. + /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed. + /// + /// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written. + /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified. #[rhai_fn(name = "write_be")] pub fn write_be_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) { write_float(blob, start, len, value, false) @@ -1063,32 +1407,100 @@ mod write_string_functions { 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) - } + /// Write a string to the bytes within an exclusive `range` in the BLOB in UTF-8 encoding. + /// + /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. + /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. + /// + /// ```ignore + /// let b = blob(8); + /// + /// b.write_utf8(1..5, "朝には紅顔ありて夕べには白骨となる"); + /// + /// print(b); // prints "[00e69c9de3000000]" + /// ``` #[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) } + /// Write a string to the bytes within an inclusive `range` in the BLOB in UTF-8 encoding. + /// + /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. + /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. + /// + /// ```ignore + /// let b = blob(8); + /// + /// b.write_utf8(1..=5, "朝には紅顔ありて夕べには白骨となる"); + /// + /// print(b); // prints "[00e69c9de3810000]" + /// ``` #[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) + /// Write a string to the bytes within an inclusive `range` in the BLOB in UTF-8 encoding. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, the BLOB is not modified. + /// * If `len` ≤ 0, the BLOB is not modified. + /// * If `start` position + `len` ≥ length of BLOB, only the portion of the BLOB after the `start` position is modified. + /// + /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. + /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. + /// + /// ```ignore + /// let b = blob(8); + /// + /// b.write_utf8(1, 5, "朝には紅顔ありて夕べには白骨となる"); + /// + /// print(b); // prints "[00e69c9de3810000]" + /// ``` + #[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) } + /// Write an ASCII string to the bytes within an exclusive `range` in the BLOB. + /// + /// Each ASCII character encodes to one single byte in the BLOB. + /// Non-ASCII characters are ignored. + /// + /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. + /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. + /// + /// ```ignore + /// let b = blob(8); + /// + /// b.write_ascii(1..5, "hello, world!"); + /// + /// print(b); // prints "[0068656c6c000000]" + /// ``` #[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) } + /// Write an ASCII string to the bytes within an inclusive `range` in the BLOB. + /// + /// Each ASCII character encodes to one single byte in the BLOB. + /// Non-ASCII characters are ignored. + /// + /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. + /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. + /// + /// ```ignore + /// let b = blob(8); + /// + /// b.write_ascii(1..=5, "hello, world!"); + /// + /// print(b); // prints "[0068656c6c6f0000]" + /// ``` #[rhai_fn(name = "write_ascii")] pub fn write_ascii_string_range_inclusive( blob: &mut Blob, @@ -1099,4 +1511,26 @@ mod write_string_functions { let end = INT::max(*range.end(), start); write_string(blob, start, end - start + 1, string, true) } + /// Write an ASCII string to the bytes within an exclusive `range` in the BLOB. + /// + /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). + /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. + /// * If `start` ≥ length of BLOB, the BLOB is not modified. + /// * If `len` ≤ 0, the BLOB is not modified. + /// * If `start` position + `len` ≥ length of BLOB, only the portion of the BLOB after the `start` position is modified. + /// + /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. + /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. + /// + /// ```ignore + /// let b = blob(8); + /// + /// b.write_ascii(1, 5, "hello, world!"); + /// + /// print(b); // prints "[0068656c6c6f0000]" + /// ``` + #[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) + } } diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index fe844e70..78ee4519 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -481,6 +481,12 @@ def_package! { // Register string iterator lib.set_iterator::(); + #[cfg(feature = "metadata")] + let (range_type, range_inclusive_type) = ( + format!("range: Range<{}>", std::any::type_name::()), + format!("range: RangeInclusive<{}>", std::any::type_name::()), + ); + let _hash = lib.set_native_fn("chars", |string, range: ExclusiveRange| { let from = INT::max(range.start, 0); let to = INT::max(range.end, from); @@ -489,7 +495,7 @@ def_package! { #[cfg(feature = "metadata")] lib.update_fn_metadata_with_comments( _hash, - ["string: &str", "range: Range", "Iterator"], + ["string: &str", &range_type, "Iterator"], [ "/// Return an iterator over an exclusive range of characters in the string.", "///", @@ -511,7 +517,7 @@ def_package! { #[cfg(feature = "metadata")] lib.update_fn_metadata_with_comments( _hash, - ["string: &str", "range: RangeInclusive", "Iterator"], + ["string: &str", &range_inclusive_type, "Iterator"], [ "/// Return an iterator over an inclusive range of characters in the string.", "///", @@ -621,7 +627,7 @@ def_package! { #[cfg(feature = "metadata")] lib.update_fn_metadata_with_comments( _hash, - ["value: INT", "range: Range", "Iterator"], + ["value: INT", &range_type, "Iterator"], [ "/// Return an iterator over an exclusive range of bits in the number.", "///", @@ -645,7 +651,7 @@ def_package! { #[cfg(feature = "metadata")] lib.update_fn_metadata_with_comments( _hash, - ["value: INT", "range: RangeInclusive", "Iterator"], + ["value: INT", &range_inclusive_type, "Iterator"], [ "/// Return an iterator over an inclusive range of bits in the number.", "///", @@ -733,7 +739,7 @@ def_package! { #[cfg(feature = "metadata")] lib.update_fn_metadata_with_comments( _hash, - ["value: &mut INT", "range: Range", "Iterator"], + ["value: &mut INT", "Iterator"], [ "/// Return an iterator over all the bits in the number.", "///", diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index ea1057c8..d0319a98 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -10,16 +10,17 @@ def_package! { crate::LanguageCorePackage => |lib| { lib.standard = true; - combine_with_exported_module!(lib, "language_core", core_functions); + combine_with_exported_module!(lib, "core", core_functions); + + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_index"))] + #[cfg(not(feature = "no_object"))] + combine_with_exported_module!(lib, "reflection", reflection_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 @@ -53,19 +54,38 @@ mod core_functions { Ok(()) } } +} - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_index"))] - #[cfg(not(feature = "no_object"))] +#[cfg(not(feature = "no_function"))] +#[cfg(not(feature = "no_index"))] +#[cfg(not(feature = "no_object"))] +#[export_module] +mod reflection_functions { pub fn get_fn_metadata_list(ctx: NativeCallContext) -> crate::Array { - collect_fn_metadata(ctx) + collect_fn_metadata(ctx, |_, _, _, _, _| true) + } + #[rhai_fn(name = "get_fn_metadata_list")] + pub fn get_fn_metadata(ctx: NativeCallContext, name: &str) -> crate::Array { + collect_fn_metadata(ctx, |_, _, n, _, _| n == name) + } + #[rhai_fn(name = "get_fn_metadata_list")] + pub fn get_fn_metadata2(ctx: NativeCallContext, name: &str, params: INT) -> crate::Array { + if params < 0 { + crate::Array::new() + } else { + collect_fn_metadata(ctx, |_, _, n, p, _| p == (params as usize) && n == name) + } } } #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_object"))] -fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { +fn collect_fn_metadata( + ctx: NativeCallContext, + filter: impl Fn(FnNamespace, FnAccess, &str, usize, &crate::Shared) -> bool + + Copy, +) -> crate::Array { use crate::{ast::ScriptFnDef, Array, Identifier, Map}; use std::collections::BTreeSet; @@ -125,13 +145,26 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { .map(|&s| s.into()) .collect(); - let mut _list = ctx.iter_namespaces().flat_map(Module::iter_script_fn).fold( - Array::new(), - |mut list, (_, _, _, _, f)| { - list.push(make_metadata(&dict, None, f).into()); - list - }, - ); + let mut list = Array::new(); + + ctx.iter_namespaces() + .flat_map(Module::iter_script_fn) + .filter(|(s, a, n, p, f)| filter(*s, *a, n, *p, f)) + .for_each(|(_, _, _, _, f)| list.push(make_metadata(&dict, None, f).into())); + + ctx.engine() + .global_modules + .iter() + .flat_map(|m| m.iter_script_fn()) + .filter(|(ns, a, n, p, f)| filter(*ns, *a, n, *p, f)) + .for_each(|(_, _, _, _, f)| list.push(make_metadata(&dict, None, f).into())); + + ctx.engine() + .global_sub_modules + .values() + .flat_map(|m| m.iter_script_fn()) + .filter(|(ns, a, n, p, f)| filter(*ns, *a, n, *p, f)) + .for_each(|(_, _, _, _, f)| list.push(make_metadata(&dict, None, f).into())); #[cfg(not(feature = "no_module"))] { @@ -141,10 +174,21 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { dict: &BTreeSet, namespace: Identifier, module: &Module, + filter: impl Fn( + FnNamespace, + FnAccess, + &str, + usize, + &crate::Shared, + ) -> bool + + Copy, ) { - module.iter_script_fn().for_each(|(_, _, _, _, f)| { - list.push(make_metadata(dict, Some(namespace.clone()), f).into()) - }); + module + .iter_script_fn() + .filter(|(s, a, n, p, f)| filter(*s, *a, n, *p, f)) + .for_each(|(_, _, _, _, f)| { + list.push(make_metadata(dict, Some(namespace.clone()), f).into()) + }); module.iter_sub_modules().for_each(|(ns, m)| { let ns = format!( "{}{}{}", @@ -152,13 +196,13 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { crate::tokenizer::Token::DoubleColon.literal_syntax(), ns ); - scan_module(list, dict, ns.into(), m.as_ref()) + scan_module(list, dict, ns.into(), m.as_ref(), filter) }); } ctx.iter_imports_raw() - .for_each(|(ns, m)| scan_module(&mut _list, &dict, ns.clone(), m.as_ref())); + .for_each(|(ns, m)| scan_module(&mut list, &dict, ns.clone(), m.as_ref(), filter)); } - _list + list } diff --git a/src/packages/logic.rs b/src/packages/logic.rs index e577be58..4c9130d9 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -62,6 +62,8 @@ def_package! { reg_functions!(lib += float; f64); combine_with_exported_module!(lib, "f64", f64_functions); } + + combine_with_exported_module!(lib, "logic", logic_functions); } } @@ -83,6 +85,14 @@ gen_cmp_functions!(float => f32); #[cfg(feature = "f32_float")] gen_cmp_functions!(float => f64); +#[export_module] +mod logic_functions { + #[rhai_fn(name = "!")] + pub fn not(x: bool) -> bool { + !x + } +} + #[cfg(not(feature = "no_float"))] #[export_module] mod f32_functions { diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 497bb0cc..8eaf6b17 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -25,6 +25,51 @@ mod map_functions { pub fn len(map: &mut Map) -> INT { map.len() as INT } + + /// Get the value of the `property` in the object map and return a copy. + /// + /// If `property` does not exist in the object map, `()` is returned. + /// + /// # Example + /// + /// ```rhai + /// let m = #{a: 1, b: 2, c: 3}; + /// + /// print(m.get("b")); // prints 2 + /// + /// print(m.get("x")); // prints empty (for '()') + /// ``` + pub fn get(map: &mut Map, property: &str) -> Dynamic { + if map.is_empty() { + return Dynamic::UNIT; + } + + map.get(property).cloned().unwrap_or(Dynamic::UNIT) + } + /// Set the value of the `property` in the object map to a new `value`. + /// + /// If `property` does not exist in the object map, it is added. + /// + /// # Example + /// + /// ```rhai + /// let m = #{a: 1, b: 2, c: 3}; + /// + /// m.set("b", 42)' + /// + /// print(m); // prints "#{a: 1, b: 42, c: 3}" + /// + /// x.set("x", 0); + /// + /// print(m); // prints "#{a: 1, b: 42, c: 3, x: 0}" + /// ``` + pub fn set(map: &mut Map, property: &str, value: Dynamic) { + if let Some(value_ref) = map.get_mut(property) { + *value_ref = value; + } else { + map.insert(property.into(), value); + } + } /// Clear the object map. pub fn clear(map: &mut Map) { if !map.is_empty() { @@ -46,9 +91,9 @@ mod map_functions { /// /// print(m); // prints "#{a:1, c:3}" /// ``` - pub fn remove(map: &mut Map, name: ImmutableString) -> Dynamic { + pub fn remove(map: &mut Map, property: &str) -> Dynamic { if !map.is_empty() { - map.remove(name.as_str()).unwrap_or_else(|| Dynamic::UNIT) + map.remove(property).unwrap_or_else(|| Dynamic::UNIT) } else { Dynamic::UNIT } diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 7ab926e2..ff0373c6 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -551,6 +551,85 @@ mod string_functions { } } + /// Get the character at the `index` position in the string. + /// + /// * If `index` < 0, position counts from the end of the string (`-1` is the last character). + /// * If `index` < -length of string, zero is returned. + /// * If `index` ≥ length of string, zero is returned. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// print(text.get(0)); // prints 'h' + /// + /// print(text.get(-1)); // prints '!' + /// + /// print(text.get(99)); // prints empty (for '()')' + /// ``` + pub fn get(string: &str, index: INT) -> Dynamic { + if index >= 0 { + string + .chars() + .nth(index as usize) + .map_or_else(|| Dynamic::UNIT, Into::into) + } else if let Some(abs_index) = index.checked_abs() { + // Count from end if negative + string + .chars() + .rev() + .nth((abs_index as usize) - 1) + .map_or_else(|| Dynamic::UNIT, Into::into) + } else { + Dynamic::UNIT + } + } + /// Set the `index` position in the string to a new `character`. + /// + /// * If `index` < 0, position counts from the end of the string (`-1` is the last character). + /// * If `index` < -length of string, the string is not modified. + /// * If `index` ≥ length of string, the string is not modified. + /// + /// # Example + /// + /// ```rhai + /// let text = "hello, world!"; + /// + /// text.set(3, 'x'); + /// + /// print(text); // prints "helxo, world!" + /// + /// text.set(-3, 'x'); + /// + /// print(text); // prints "hello, worxd!" + /// + /// text.set(99, 'x'); + /// + /// print(text); // prints "hello, worxd!" + /// ``` + pub fn set(string: &mut ImmutableString, index: INT, character: char) { + if index >= 0 { + let index = index as usize; + *string = string + .chars() + .enumerate() + .map(|(i, ch)| if i == index { character } else { ch }) + .collect(); + } else if let Some(abs_index) = index.checked_abs() { + let string_len = string.chars().count(); + + if abs_index as usize <= string_len { + let index = string_len - (abs_index as usize); + *string = string + .chars() + .enumerate() + .map(|(i, ch)| if i == index { character } else { ch }) + .collect(); + } + } + } + /// Copy an exclusive range of characters from the string and return it as a new string. /// /// # Example @@ -685,7 +764,7 @@ mod string_functions { } } - /// Remove all characters from the string except those within an exclusive range. + /// Remove all characters from the string except those within an exclusive `range`. /// /// # Example /// @@ -702,7 +781,7 @@ mod string_functions { let end = INT::max(range.end, start); crop(string, start, end - start) } - /// Remove all characters from the string except those within an inclusive range. + /// Remove all characters from the string except those within an inclusive `range`. /// /// # Example /// diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index 3ccadfb3..ded1e362 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -7,7 +7,7 @@ use crate::{calc_fn_hash, Engine, AST}; use serde::{Deserialize, Serialize}; #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{cmp::Ordering, collections::BTreeMap, iter::empty}; +use std::{borrow::Cow, cmp::Ordering, collections::BTreeMap, iter::empty}; #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -54,7 +54,7 @@ struct FnParam<'a> { #[serde(skip_serializing_if = "Option::is_none")] pub name: Option<&'a str>, #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - pub typ: Option<&'a str>, + pub typ: Option>, } #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] @@ -125,12 +125,12 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> { "_" => None, s => Some(s), }; - let typ = seg.next().map(&str::trim); + let typ = seg.next().map(|s| FuncInfo::format_type(s, false)); FnParam { name, typ } }) .collect(), _dummy: None, - return_type: FuncInfo::format_return_type(&info.metadata.return_type).into_owned(), + return_type: FuncInfo::format_type(&info.metadata.return_type, true).into_owned(), signature: info.gen_signature(), doc_comments: if info.func.is_script() { #[cfg(feature = "no_function")] @@ -196,11 +196,12 @@ impl Engine { /// 1) Functions defined in an [`AST`][crate::AST] /// 2) Functions registered into the global namespace /// 3) Functions in static modules - /// 4) Functions in global modules (optional) + /// 4) Functions in registered global packages + /// 5) Functions in standard packages (optional) pub fn gen_fn_metadata_with_ast_to_json( &self, ast: &AST, - include_global: bool, + include_packages: bool, ) -> serde_json::Result { let _ast = ast; let mut global = ModuleMetadata::new(); @@ -211,14 +212,20 @@ impl Engine { self.global_modules .iter() - .take(if include_global { usize::MAX } else { 1 }) + .filter(|m| include_packages || !m.standard) .flat_map(|m| m.iter_fn()) - .for_each(|f| global.functions.push(f.into())); + .for_each(|f| { + let mut meta: FnMetadata = f.into(); + meta.namespace = FnNamespace::Global; + global.functions.push(meta); + }); #[cfg(not(feature = "no_function"))] - _ast.shared_lib() - .iter_fn() - .for_each(|f| global.functions.push(f.into())); + _ast.shared_lib().iter_fn().for_each(|f| { + let mut meta: FnMetadata = f.into(); + meta.namespace = FnNamespace::Global; + global.functions.push(meta); + }); global.functions.sort(); @@ -233,7 +240,7 @@ impl Engine { /// 2) Functions in static modules /// 3) Functions in global modules (optional) #[inline(always)] - pub fn gen_fn_metadata_to_json(&self, include_global: bool) -> serde_json::Result { - self.gen_fn_metadata_with_ast_to_json(&AST::empty(), include_global) + pub fn gen_fn_metadata_to_json(&self, include_packages: bool) -> serde_json::Result { + self.gen_fn_metadata_with_ast_to_json(&AST::empty(), include_packages) } } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index df7508da..b23c630e 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -636,7 +636,7 @@ impl Token { /// Get the corresponding operator of the token if it is an op-assignment operator. #[must_use] - pub const fn map_op_assignment(&self) -> Option { + pub const fn get_base_op_from_assignment(&self) -> Option { Some(match self { Self::PlusAssign => Self::Plus, Self::MinusAssign => Self::Minus, @@ -675,7 +675,7 @@ impl Token { /// Get the corresponding op-assignment operator of the token. #[must_use] - pub const fn make_op_assignment(&self) -> Option { + pub const fn convert_to_op_assignment(&self) -> Option { Some(match self { Self::Plus => Self::PlusAssign, Self::Minus => Self::MinusAssign, @@ -1594,7 +1594,7 @@ fn get_next_token_inner( eat_next(stream, pos); pos.new_line(); // `\r\n - if stream.peek_next().map(|ch| ch == '\n').unwrap_or(false) { + if let Some('\n') = stream.peek_next() { eat_next(stream, pos); } } @@ -1761,6 +1761,14 @@ fn get_next_token_inner( }; while let Some(c) = stream.get_next() { + if c == '\r' { + pos.new_line(); + // \r\n + if let Some('\n') = stream.peek_next() { + eat_next(stream, pos); + } + break; + } if c == '\n' { pos.new_line(); break; diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 800ffb2b..d61cd612 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -546,44 +546,66 @@ impl Hash for Dynamic { /// Map the name of a standard type into a friendly form. #[inline] #[must_use] -pub(crate) fn map_std_type_name(name: &str) -> &str { +pub(crate) fn map_std_type_name(name: &str, shorthands: bool) -> &str { + let name = name.trim(); + + if name.starts_with("rhai::") { + return map_std_type_name(&name[6..], shorthands); + } + if name == type_name::() { - return "string"; + return if shorthands { "string" } else { "String" }; } if name == type_name::() { - return "string"; + return if shorthands { + "string" + } else { + "ImmutableString" + }; } if name == type_name::<&str>() { - return "string"; - } - if name == type_name::() { - return "Fn"; + return if shorthands { "string" } else { "&str" }; } #[cfg(feature = "decimal")] if name == type_name::() { - return "decimal"; + return if shorthands { "decimal" } else { "Decimal" }; + } + if name == type_name::() { + return if shorthands { "Fn" } else { "FnPtr" }; } #[cfg(not(feature = "no_index"))] if name == type_name::() { - return "array"; + return if shorthands { "array" } else { "Array" }; } #[cfg(not(feature = "no_index"))] if name == type_name::() { - return "blob"; + return if shorthands { "blob" } else { "Blob" }; } #[cfg(not(feature = "no_object"))] if name == type_name::() { - return "map"; + return if shorthands { "map" } else { "Map" }; } #[cfg(not(feature = "no_std"))] if name == type_name::() { - return "timestamp"; + return if shorthands { "timestamp" } else { "Instant" }; } - if name == type_name::() { - return "range"; + if name == type_name::() || name == "ExclusiveRange" { + return if shorthands { + "range" + } else if cfg!(feature = "only_i32") { + "Range" + } else { + "Range" + }; } - if name == type_name::() { - return "range="; + if name == type_name::() || name == "InclusiveRange" { + return if shorthands { + "range=" + } else if cfg!(feature = "only_i32") { + "RangeInclusive" + } else { + "RangeInclusive" + }; } name diff --git a/tests/get_set.rs b/tests/get_set.rs index 1b8468fa..ee8e55f5 100644 --- a/tests/get_set.rs +++ b/tests/get_set.rs @@ -254,3 +254,51 @@ fn test_get_set_chain_without_write_back() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_get_set_collection() -> Result<(), Box> { + type MyItem = INT; + type MyBag = std::collections::BTreeSet; + + let mut engine = Engine::new(); + + engine + .register_type_with_name::("MyBag") + .register_iterator::() + .register_fn("new_bag", || MyBag::new()) + .register_fn("len", |col: &mut MyBag| col.len() as INT) + .register_get("len", |col: &mut MyBag| col.len() as INT) + .register_fn("clear", |col: &mut MyBag| col.clear()) + .register_fn("contains", |col: &mut MyBag, item: INT| col.contains(&item)) + .register_fn("add", |col: &mut MyBag, item: MyItem| col.insert(item)) + .register_fn("+=", |col: &mut MyBag, item: MyItem| col.insert(item)) + .register_fn("remove", |col: &mut MyBag, item: MyItem| col.remove(&item)) + .register_fn("-=", |col: &mut MyBag, item: MyItem| col.remove(&item)) + .register_fn("+", |mut col1: MyBag, col2: MyBag| { + col1.extend(col2.into_iter()); + col1 + }); + + let result = engine.eval::( + " + let bag = new_bag(); + + bag += 1; + bag += 2; + bag += 39; + bag -= 2; + + if !bag.contains(2) { + let sum = 0; + for n in bag { sum += n; } + sum + bag.len + } else { + -1 + } + ", + )?; + + assert_eq!(result, 42); + + Ok(()) +} diff --git a/tests/ops.rs b/tests/ops.rs index d50bf517..df5be33c 100644 --- a/tests/ops.rs +++ b/tests/ops.rs @@ -6,6 +6,11 @@ fn test_ops() -> Result<(), Box> { assert_eq!(engine.eval::("60 + 5")?, 65); assert_eq!(engine.eval::("(1 + 2) * (6 - 4) / 2")?, 3); + assert_eq!(engine.eval::("let x = 41; x = x + 1; x")?, 42); + assert_eq!( + engine.eval::(r#"let s = "hello"; s = s + 42; s"#)?, + "hello42" + ); Ok(()) }