diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..df310705 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,29 @@ +name: Benchmark +on: + push: + branches: + - master + +jobs: + benchmark: + name: Run Rust benchmark + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: rustup toolchain update nightly && rustup default nightly + - name: Run benchmark + run: cargo +nightly bench | tee output.txt + - name: Store benchmark result + uses: rhysd/github-action-benchmark@v1 + with: + name: Rust Benchmark + tool: 'cargo' + output-file-path: output.txt + # Use personal access token instead of GITHUB_TOKEN due to https://github.community/t5/GitHub-Actions/Github-action-not-triggering-gh-pages-upon-push/td-p/26869/highlight/false + github-token: ${{ secrets.RHAI }} + auto-push: true + # Show alert with commit comment on detecting possible performance regression + alert-threshold: '200%' + comment-on-alert: true + fail-on-alert: true + alert-comment-cc-users: '@schungx' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 37d8c233..1ffeec22 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -77,6 +77,29 @@ jobs: with: command: build args: --manifest-path=no_std/no_std_test/Cargo.toml ${{matrix.flags}} + rustfmt: + name: Check Formatting + runs-on: windows-latest + continue-on-error: true + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + components: rustfmt, clippy + - name: Run Rustfmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + - name: Run Clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --all -- -Aclippy::all -Dclippy::perf codegen_build: name: Codegen Build runs-on: ${{matrix.os}} diff --git a/Cargo.toml b/Cargo.toml index 5e529f38..0def9306 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ keywords = [ "scripting" ] categories = [ "no-std", "embedded", "wasm", "parser-implementations" ] [dependencies] -smallvec = { version = "1.4.1", default-features = false } +smallvec = { version = "1.4.2", default-features = false } rhai_codegen = { version = "0.1", path = "codegen" } [features] @@ -81,7 +81,7 @@ features = ["compile-time-rng"] optional = true [dependencies.serde] -version = "1.0.111" +version = "1.0.116" default_features = false features = ["derive", "alloc"] optional = true @@ -92,7 +92,7 @@ default_features = false optional = true [target.'cfg(target_arch = "wasm32")'.dependencies] -instant= { version = "0.1.4", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant +instant= { version = "0.1.7", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant [package.metadata.docs.rs] features = [ "serde", "internals" ] diff --git a/RELEASES.md b/RELEASES.md index ccba5394..3ecbd4ab 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -11,12 +11,20 @@ Bug fixes * Fixes a bug in `Module::set_fn_4_mut`. * Module API's now properly handle `&str` and `String` parameters. * Indexers are available under `no_object`. +* Registered operator-assignment functions (e.g. `+=`) now work correctly. + +Breaking changes +---------------- + +* `Engine::register_set_result` and `Engine::register_indexer_set_result` now take a function that returns `Result<(), Box>`. +* `Engine::register_indexer_XXX` and `Module::set_indexer_XXX` panic when the type is `Arrary`, `Map` or `String`. New features ------------ * Plugins support via procedural macros. * Scripted functions are allowed in packages. +* `parse_int` and `parse_float` functions. Version 0.18.3 diff --git a/codegen/src/attrs.rs b/codegen/src/attrs.rs index 32d258f7..eb8d999a 100644 --- a/codegen/src/attrs.rs +++ b/codegen/src/attrs.rs @@ -32,7 +32,10 @@ pub struct ExportInfo { pub fn parse_attr_items(args: ParseStream) -> syn::Result { if args.is_empty() { - return Ok(ExportInfo { item_span: args.span(), items: Vec::new()}); + return Ok(ExportInfo { + item_span: args.span(), + items: Vec::new(), + }); } let arg_list = args .call(syn::punctuated::Punctuated::::parse_separated_nonempty)?; @@ -80,10 +83,17 @@ pub fn parse_punctuated_items( .ok_or_else(|| syn::Error::new(attr_path.span(), "expecting attribute name"))?, x => return Err(syn::Error::new(x.span(), "expecting identifier")), }; - attrs.push(AttrItem { key, value, span: arg_span }); + attrs.push(AttrItem { + key, + value, + span: arg_span, + }); } - Ok(ExportInfo { item_span: list_span, items: attrs }) + Ok(ExportInfo { + item_span: list_span, + items: attrs, + }) } pub(crate) fn outer_item_attributes( diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 2fca6d96..2126fd73 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -16,6 +16,7 @@ use quote::{quote, quote_spanned}; use syn::{parse::Parse, parse::ParseStream, parse::Parser, spanned::Spanned}; use crate::attrs::{ExportInfo, ExportScope, ExportedParams}; +use crate::rhai_module::flatten_type_groups; #[derive(Clone, Debug, Eq, PartialEq)] pub enum Index { @@ -223,27 +224,29 @@ impl Parse for ExportedFn { syn::FnArg::Receiver(syn::Receiver { reference: Some(_), .. }) => true, - syn::FnArg::Typed(syn::PatType { ref ty, .. }) => match ty.as_ref() { - &syn::Type::Reference(syn::TypeReference { - mutability: Some(_), - .. - }) => true, - &syn::Type::Reference(syn::TypeReference { - mutability: None, - ref elem, - .. - }) => match elem.as_ref() { - &syn::Type::Path(ref p) if p.path == str_type_path => false, - _ => { - return Err(syn::Error::new( - ty.span(), - "references from Rhai in this position \ + syn::FnArg::Typed(syn::PatType { ref ty, .. }) => { + match flatten_type_groups(ty.as_ref()) { + &syn::Type::Reference(syn::TypeReference { + mutability: Some(_), + .. + }) => true, + &syn::Type::Reference(syn::TypeReference { + mutability: None, + ref elem, + .. + }) => match flatten_type_groups(elem.as_ref()) { + &syn::Type::Path(ref p) if p.path == str_type_path => false, + _ => { + return Err(syn::Error::new( + ty.span(), + "references from Rhai in this position \ must be mutable", - )) - } - }, - _ => false, - }, + )) + } + }, + _ => false, + } + } _ => false, } } else { @@ -257,7 +260,7 @@ impl Parse for ExportedFn { syn::FnArg::Typed(syn::PatType { ref ty, .. }) => ty, _ => panic!("internal error: receiver argument outside of first position!?"), }; - let is_ok = match ty.as_ref() { + let is_ok = match flatten_type_groups(ty.as_ref()) { &syn::Type::Reference(syn::TypeReference { mutability: Some(_), .. @@ -266,7 +269,9 @@ impl Parse for ExportedFn { mutability: None, ref elem, .. - }) => matches!(elem.as_ref(), &syn::Type::Path(ref p) if p.path == str_type_path), + }) => { + matches!(flatten_type_groups(elem.as_ref()), &syn::Type::Path(ref p) if p.path == str_type_path) + } &syn::Type::Verbatim(_) => false, _ => true, }; @@ -605,13 +610,9 @@ impl ExportedFn { let var = syn::Ident::new("arg0", proc_macro2::Span::call_site()); match first_arg { syn::FnArg::Typed(pattern) => { - let arg_type: &syn::Type = { - match pattern.ty.as_ref() { - &syn::Type::Reference(syn::TypeReference { ref elem, .. }) => { - elem.as_ref() - } - ref p => p, - } + let arg_type: &syn::Type = match flatten_type_groups(pattern.ty.as_ref()) { + &syn::Type::Reference(syn::TypeReference { ref elem, .. }) => elem.as_ref(), + p => p, }; let downcast_span = quote_spanned!( arg_type.span()=> &mut args[0usize].write_lock::<#arg_type>().unwrap()); @@ -648,12 +649,12 @@ impl ExportedFn { match arg { syn::FnArg::Typed(pattern) => { let arg_type: &syn::Type = pattern.ty.as_ref(); - let downcast_span = match pattern.ty.as_ref() { + let downcast_span = match flatten_type_groups(pattern.ty.as_ref()) { &syn::Type::Reference(syn::TypeReference { mutability: None, ref elem, .. - }) => match elem.as_ref() { + }) => match flatten_type_groups(elem.as_ref()) { &syn::Type::Path(ref p) if p.path == str_type_path => { is_string = true; is_ref = true; @@ -672,7 +673,7 @@ impl ExportedFn { is_string = false; is_ref = false; quote_spanned!(arg_type.span()=> - mem::take(args[#i]).clone().cast::<#arg_type>()) + mem::take(args[#i]).cast::<#arg_type>()) } }; diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index c25c9484..b65d891b 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -85,12 +85,12 @@ pub(crate) fn generate_body( .map(|fnarg| match fnarg { syn::FnArg::Receiver(_) => panic!("internal error: receiver fn outside impl!?"), syn::FnArg::Typed(syn::PatType { ref ty, .. }) => { - let arg_type = match ty.as_ref() { + let arg_type = match flatten_type_groups(ty.as_ref()) { syn::Type::Reference(syn::TypeReference { mutability: None, ref elem, .. - }) => match elem.as_ref() { + }) => match flatten_type_groups(elem.as_ref()) { syn::Type::Path(ref p) if p.path == str_type_path => { syn::parse2::(quote! { ImmutableString }) @@ -107,11 +107,11 @@ pub(crate) fn generate_body( mutability: Some(_), ref elem, .. - }) => match elem.as_ref() { + }) => match flatten_type_groups(elem.as_ref()) { syn::Type::Path(ref p) => syn::parse2::(quote! { #p }) .unwrap(), - _ => panic!("internal error: non-string shared reference!?"), + _ => panic!("internal error: invalid mutable reference!?"), }, t => t.clone(), }; @@ -174,6 +174,14 @@ pub(crate) fn generate_body( } } +pub(crate) fn flatten_type_groups(ty: &syn::Type) -> &syn::Type { + match ty { + syn::Type::Group(syn::TypeGroup { ref elem, .. }) + | syn::Type::Paren(syn::TypeParen { ref elem, .. }) => flatten_type_groups(elem.as_ref()), + _ => ty, + } +} + pub(crate) fn check_rename_collisions(fns: &Vec) -> Result<(), syn::Error> { let mut renames = HashMap::::new(); let mut names = HashMap::::new(); diff --git a/codegen/src/test/function.rs b/codegen/src/test/function.rs index f22f1fe2..a5b86ba1 100644 --- a/codegen/src/test/function.rs +++ b/codegen/src/test/function.rs @@ -323,7 +323,7 @@ mod generate_tests { ) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); + let arg0 = mem::take(args[0usize]).cast::(); Ok(Dynamic::from(do_something(arg0))) } @@ -364,7 +364,7 @@ mod generate_tests { ) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); + let arg0 = mem::take(args[0usize]).cast::(); Ok(Dynamic::from(do_something(arg0))) } @@ -398,8 +398,8 @@ mod generate_tests { ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - let arg1 = mem::take(args[1usize]).clone().cast::(); + let arg0 = mem::take(args[0usize]).cast::(); + let arg1 = mem::take(args[1usize]).cast::(); Ok(Dynamic::from(add_together(arg0, arg1))) } @@ -445,7 +445,7 @@ mod generate_tests { ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); - let arg1 = mem::take(args[1usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).cast::(); let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0, arg1))) } diff --git a/codegen/src/test/module.rs b/codegen/src/test/module.rs index 80a93f28..472c949d 100644 --- a/codegen/src/test/module.rs +++ b/codegen/src/test/module.rs @@ -369,7 +369,7 @@ mod generate_tests { ) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); + let arg0 = mem::take(args[0usize]).cast::(); Ok(Dynamic::from(add_one_to(arg0))) } @@ -446,7 +446,7 @@ mod generate_tests { ) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); + let arg0 = mem::take(args[0usize]).cast::(); Ok(Dynamic::from(add_one_to(arg0))) } @@ -474,8 +474,8 @@ mod generate_tests { ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - let arg1 = mem::take(args[1usize]).clone().cast::(); + let arg0 = mem::take(args[0usize]).cast::(); + let arg1 = mem::take(args[1usize]).cast::(); Ok(Dynamic::from(add_n_to(arg0, arg1))) } @@ -540,8 +540,8 @@ mod generate_tests { ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - let arg1 = mem::take(args[1usize]).clone().cast::(); + let arg0 = mem::take(args[0usize]).cast::(); + let arg1 = mem::take(args[1usize]).cast::(); Ok(Dynamic::from(add_together(arg0, arg1))) } @@ -613,8 +613,8 @@ mod generate_tests { ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - let arg1 = mem::take(args[1usize]).clone().cast::(); + let arg0 = mem::take(args[0usize]).cast::(); + let arg1 = mem::take(args[1usize]).cast::(); Ok(Dynamic::from(add_together(arg0, arg1))) } @@ -1447,7 +1447,7 @@ mod generate_tests { ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); - let arg1 = mem::take(args[1usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).cast::(); let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(int_foo(arg0, arg1))) } @@ -1518,7 +1518,7 @@ mod generate_tests { ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); - let arg1 = mem::take(args[1usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).cast::(); let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(int_foo(arg0, arg1))) } @@ -1585,7 +1585,7 @@ mod generate_tests { ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); - let arg1 = mem::take(args[1usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).cast::(); let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(get_by_index(arg0, arg1))) } @@ -1657,7 +1657,7 @@ mod generate_tests { ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); - let arg1 = mem::take(args[1usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).cast::(); let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(get_by_index(arg0, arg1))) } @@ -1726,8 +1726,8 @@ mod generate_tests { ) -> Result> { debug_assert_eq!(args.len(), 3usize, "wrong arg count: {} != {}", args.len(), 3usize); - let arg1 = mem::take(args[1usize]).clone().cast::(); - let arg2 = mem::take(args[2usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).cast::(); + let arg2 = mem::take(args[2usize]).cast::(); let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(set_by_index(arg0, arg1, arg2))) } @@ -1802,8 +1802,8 @@ mod generate_tests { ) -> Result> { debug_assert_eq!(args.len(), 3usize, "wrong arg count: {} != {}", args.len(), 3usize); - let arg1 = mem::take(args[1usize]).clone().cast::(); - let arg2 = mem::take(args[2usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).cast::(); + let arg2 = mem::take(args[2usize]).cast::(); let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(set_by_index(arg0, arg1, arg2))) } diff --git a/codegen/tests/test_nested.rs b/codegen/tests/test_nested.rs index f0144990..4d0d39aa 100644 --- a/codegen/tests/test_nested.rs +++ b/codegen/tests/test_nested.rs @@ -187,7 +187,6 @@ fn export_nested_by_prefix_test() -> Result<(), Box> { if s == "math::bar_fourth_adders::add_int (i64, i64)" && p == rhai::Position::new(3, 42))); - assert!(matches!(*engine.eval::( r#"import "Math::Advanced" as math; let ex = 41.0; diff --git a/diag_test/Cargo.toml b/diag_test/Cargo.toml deleted file mode 100644 index 17998ecf..00000000 --- a/diag_test/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "diag_test" -version = "0.1.0" -authors = ["J Henry Waugh "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[[bin]] -name = "test_template" -path = "test_template.rs" - -[dependencies] -rhai = { version = "*", path = ".." } diff --git a/diag_test/test_template.rs b/diag_test/test_template.rs deleted file mode 100644 index 98088637..00000000 --- a/diag_test/test_template.rs +++ /dev/null @@ -1,35 +0,0 @@ -use rhai::plugin::*; - -#[derive(Clone)] -pub struct Point { - x: f32, - y: f32, -} - -#[export_module] -pub mod test_module { - #[rhai_mod(name = "bar")] - pub mod test_mod { - #[rhai_fn(name = "foo")] - pub fn test_fn(input: Point) -> bool { - input.x > input.y - } - - #[rhai_fn(return_raw)] - pub fn test_fn_raw(input: Point) -> Result> { - Ok(Dynamic::from(input.x > input.y)) - } - } -} - -fn main() { - let n = Point { - x: 0.0, - y: 10.0, - }; - if test_module::test_mod::test_fn(n) { - println!("yes"); - } else { - println!("no"); - } -} diff --git a/doc/src/language/arrays.md b/doc/src/language/arrays.md index 5c0ce9e9..2e1b8cc9 100644 --- a/doc/src/language/arrays.md +++ b/doc/src/language/arrays.md @@ -33,7 +33,9 @@ The following methods (mostly defined in the [`BasicArrayPackage`][packages] but | Function | Parameter(s) | Description | | ------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `push` | element to insert | inserts an element at the end | -| `+=` operator, `append` | array to append | concatenates the second array to the end of the first | +| `+=` operator | array, element to insert (not another array) | inserts an element at the end | +| `append` | array to append | concatenates the second array to the end of the first | +| `+=` operator | array, array to append | concatenates the second array to the end of the first | | `+` operator | first array, second array | concatenates the first array with the second | | `insert` | element to insert, position
(beginning if <= 0, end if >= length) | insert an element at a certain index | | `pop` | _none_ | removes the last element and returns it ([`()`] if empty) | @@ -49,7 +51,9 @@ Use Custom Types With Arrays --------------------------- To use a [custom type] with arrays, a number of array functions need to be manually implemented, -in particular `push`, `pad` and the `==` operator (in order to support the `in` operator). +in particular `push`, `pad` and the `+=` operator. In addition, the `==` operator must be +implemented for the [custom type] in order to support the `in` operator which uses `==` to +compare elements. See the section on [custom types] for more details. @@ -104,7 +108,7 @@ let foo = y[0]; foo == 1; y.push(4); // 4 elements -y.push(5); // 5 elements +y += 5; // 5 elements y.len == 5; diff --git a/doc/src/language/num-fn.md b/doc/src/language/num-fn.md index c9427a35..df656053 100644 --- a/doc/src/language/num-fn.md +++ b/doc/src/language/num-fn.md @@ -9,11 +9,11 @@ Integer Functions The following standard functions (defined in the [`BasicMathPackage`][packages] but excluded if using a [raw `Engine`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: -| Function | Description | -| ------------ | ----------------------------------------------------------------------- | -| `abs` | absolute value | -| `sign` | returns -1 (`INT`) if the number is negative, +1 if positive, 0 if zero | -| [`to_float`] | converts an integer type to `FLOAT` | +| Function | No available under | Description | +| -------- | :----------------: | ---------------------------------------------------------------------- | +| `abs` | | absolute value | +| `sign` | | return -1 (`INT`) if the number is negative, +1 if positive, 0 if zero | + Floating-Point Functions ----------------------- @@ -31,3 +31,16 @@ operate on `f64` only: | Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` methods and properties | | Conversion | [`to_int`] | | Testing | `is_nan`, `is_finite`, `is_infinite` methods and properties | + + +Conversion Functions +------------------- + +The following standard functions (defined in the [`BasicMathPackage`][packages] but excluded if using a [raw `Engine`]) +parse numbers: + +| Function | No available under | Description | +| --------------- | :----------------: | -------------------------------------------------- | +| [`to_float`] | [`no_float`] | convert an integer type to `FLOAT` | +| [`parse_int`] | | convert a [string] to `INT` with an optional radix | +| [`parse_float`] | [`no_float`] | convert a [string] to `FLOAT` | diff --git a/doc/src/rust/custom.md b/doc/src/rust/custom.md index 22bea701..824588d5 100644 --- a/doc/src/rust/custom.md +++ b/doc/src/rust/custom.md @@ -161,13 +161,15 @@ x.type_of() == "Hello"; Use the Custom Type With Arrays ------------------------------ -The `push` and `pad` functions for [arrays] are only defined for standard built-in types. -For custom types, type-specific versions must be registered: +The `push` and `pad` functions, as well as the `+=` operator, for [arrays] are only defined for +standard built-in types. For custom types, type-specific versions must be registered: ```rust engine .register_fn("push", |list: &mut Array, item: TestStruct| { list.push(Dynamic::from(item)); + }).register_fn("+=", |list: &mut Array, item: TestStruct| { + list.push(Dynamic::from(item)); }).register_fn("pad", |list: &mut Array, len: i64, item: TestStruct| { if len as usize > list.len() { list.resize(len as usize, item); @@ -176,7 +178,7 @@ engine ``` In particular, in order to use the `in` operator with a custom type for an [array], -the `==` operator must be registered for that custom type: +the `==` operator must be registered for the custom type: ```rust // Assume 'TestStruct' implements `PartialEq` diff --git a/doc/src/rust/getters-setters.md b/doc/src/rust/getters-setters.md index 6f6184ce..f1e95c92 100644 --- a/doc/src/rust/getters-setters.md +++ b/doc/src/rust/getters-setters.md @@ -12,10 +12,19 @@ Getters and setters are disabled when the [`no_object`] feature is used. | `Engine` API | Description | Return Value of Function | | --------------------- | ------------------------------------------------- | :-----------------------------------: | | `register_get` | Register a getter | _Any_ | -| `register_set` | Register a setter | _Any_ | +| `register_set` | Register a setter | _None_ | | `register_get_set` | Short-hand to register both a getter and a setter | _None_ | | `register_get_result` | Register a getter | `Result>` | -| `register_set_result` | Register a setter | `Result>` | +| `register_set_result` | Register a setter | `Result<(), Box>` | + + +Cannot Override Object Maps +-------------------------- + +Getters and setters are only intended for [custom types]. + +Any getter or setter function registered for [object maps] is simply ignored because +the get/set calls will be interpreted as properties on the [object maps]. Examples @@ -28,15 +37,13 @@ struct TestStruct { } impl TestStruct { - // Returning a 'String' is OK - Rhai converts it into 'ImmutableString' + // Remember &mut must be used even for getters fn get_field(&mut self) -> String { self.field.clone() } - // Remember Rhai uses 'ImmutableString' or '&str' instead of 'String' - fn set_field(&mut self, new_val: ImmutableString) { - // Get a 'String' from an 'ImmutableString' - self.field = (*new_val).clone(); + fn set_field(&mut self, new_val: &str) { + self.field = new_val.to_string(); } fn new() -> Self { @@ -51,7 +58,6 @@ let mut engine = Engine::new(); .register_get_set("xyz", TestStruct::get_field, TestStruct::set_field) .register_fn("new_ts", TestStruct::new); -// Return result can be 'String' - Rhai will automatically convert it from 'ImmutableString' let result = engine.eval::(r#"let a = new_ts(); a.xyz = "42"; a.xyz"#)?; println!("Answer: {}", result); // prints 42 diff --git a/doc/src/rust/indexers.md b/doc/src/rust/indexers.md index 107ed5c9..3906bf7a 100644 --- a/doc/src/rust/indexers.md +++ b/doc/src/rust/indexers.md @@ -13,16 +13,22 @@ Like getters and setters, indexers take a `&mut` reference to the first paramete Indexers are disabled when the [`no_index`] feature is used. -For efficiency reasons, indexers **cannot** be used to overload (i.e. override) built-in indexing operations for -[arrays] and [object maps]. - | `Engine` API | Description | Return Value of Function | | ----------------------------- | -------------------------------------------------------- | :-----------------------------------: | | `register_indexer_get` | Register an index getter | _Any_ | -| `register_indexer_set` | Register an index setter | _Any_ | +| `register_indexer_set` | Register an index setter | _None_ | | `register_indexer_get_set` | Short-hand to register both an index getter and a setter | _None_ | | `register_indexer_get_result` | Register an index getter | `Result>` | -| `register_indexer_set_result` | Register an index setter | `Result>` | +| `register_indexer_set_result` | Register an index setter | `Result<(), Box>` | + + +Cannot Override Arrays, Object Maps and Strings +---------------------------------------------- + +For efficiency reasons, indexers **cannot** be used to overload (i.e. override) +built-in indexing operations for [arrays], [object maps] and [strings]. + +Attempting to register indexers for an [array], [object map] or [string] panics. Examples @@ -35,6 +41,7 @@ struct TestStruct { } impl TestStruct { + // Remember &mut must be used even for getters fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } @@ -60,3 +67,5 @@ let result = engine.eval::("let a = new_ts(); a[2] = 42; a[2]")?; println!("Answer: {}", result); // prints 42 ``` + +**IMPORTANT: Rhai does NOT support normal references (i.e. `&T`) as parameters.** diff --git a/src/api.rs b/src/api.rs index b2cc4b31..944b0be5 100644 --- a/src/api.rs +++ b/src/api.rs @@ -12,7 +12,10 @@ use crate::scope::Scope; use crate::token::{lex, Position}; #[cfg(not(feature = "no_index"))] -use crate::engine::{FN_IDX_GET, FN_IDX_SET}; +use crate::{ + engine::{Array, FN_IDX_GET, FN_IDX_SET}, + utils::ImmutableString, +}; #[cfg(not(feature = "no_object"))] use crate::{ @@ -36,6 +39,7 @@ use crate::optimize::optimize_into_ast; use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, + string::String, }; #[cfg(not(feature = "no_optimize"))] @@ -191,7 +195,6 @@ impl Engine { /// /// impl TestStruct { /// fn new() -> Self { TestStruct { field: 1 } } - /// /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self) -> i64 { self.field } /// } @@ -242,8 +245,7 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { field: 1 } } - /// + /// fn new() -> Self { TestStruct { field: 1 } } /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self) -> Result> { /// Ok(self.field.into()) @@ -324,7 +326,7 @@ impl Engine { } /// Register a setter function for a member of a registered type with the `Engine`. - /// Returns `Result>`. + /// Returns `Result<(), Box>`. /// /// # Example /// @@ -337,10 +339,10 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { field: 1 } } - /// fn set_field(&mut self, new_val: i64) -> Result> { + /// fn new() -> Self { TestStruct { field: 1 } } + /// fn set_field(&mut self, new_val: i64) -> Result<(), Box> { /// self.field = new_val; - /// Ok(().into()) + /// Ok(()) /// } /// } /// @@ -367,13 +369,16 @@ impl Engine { pub fn register_set_result( &mut self, name: &str, - callback: impl Fn(&mut T, U) -> Result> + SendSync + 'static, + callback: impl Fn(&mut T, U) -> Result<(), Box> + SendSync + 'static, ) -> &mut Self where T: Variant + Clone, U: Variant + Clone, { - self.register_result_fn(&make_setter(name), callback) + self.register_result_fn(&make_setter(name), move |obj: &mut T, value: U| { + callback(obj, value)?; + Ok(().into()) + }) } /// Short-hand for registering both getter and setter functions @@ -391,8 +396,8 @@ impl Engine { /// /// impl TestStruct { /// fn new() -> Self { TestStruct { field: 1 } } - /// fn get_field(&mut self) -> i64 { self.field } /// // Even a getter must start with `&mut self` and not `&self`. + /// fn get_field(&mut self) -> i64 { self.field } /// fn set_field(&mut self, new_val: i64) { self.field = new_val; } /// } /// @@ -432,6 +437,11 @@ impl Engine { /// /// The function signature must start with `&mut self` and not `&self`. /// + /// # Panics + /// + /// Panics if the type is `Array` or `Map`. + /// Indexers for arrays, object maps and strings cannot be registered. + /// /// # Example /// /// ``` @@ -441,8 +451,7 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } - /// + /// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } /// } @@ -475,6 +484,20 @@ impl Engine { U: Variant + Clone, X: Variant + Clone, { + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for arrays."); + } + #[cfg(not(feature = "no_object"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for object maps."); + } + if TypeId::of::() == TypeId::of::() + || TypeId::of::() == TypeId::of::<&str>() + || TypeId::of::() == TypeId::of::() + { + panic!("Cannot register indexer for strings."); + } + self.register_fn(FN_IDX_GET, callback) } @@ -483,6 +506,11 @@ impl Engine { /// /// The function signature must start with `&mut self` and not `&self`. /// + /// # Panics + /// + /// Panics if the type is `Array` or `Map`. + /// Indexers for arrays, object maps and strings cannot be registered. + /// /// # Example /// /// ``` @@ -494,8 +522,7 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } - /// + /// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self, index: i64) -> Result> { /// Ok(self.fields[index as usize].into()) @@ -527,11 +554,30 @@ impl Engine { T: Variant + Clone, X: Variant + Clone, { + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for arrays."); + } + #[cfg(not(feature = "no_object"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for object maps."); + } + if TypeId::of::() == TypeId::of::() + || TypeId::of::() == TypeId::of::<&str>() + || TypeId::of::() == TypeId::of::() + { + panic!("Cannot register indexer for strings."); + } + self.register_result_fn(FN_IDX_GET, callback) } /// Register an index setter for a custom type with the `Engine`. /// + /// # Panics + /// + /// Panics if the type is `Array` or `Map`. + /// Indexers for arrays, object maps and strings cannot be registered. + /// /// # Example /// /// ``` @@ -541,7 +587,7 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } + /// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } /// } /// @@ -569,18 +615,37 @@ impl Engine { #[cfg(not(feature = "no_index"))] pub fn register_indexer_set( &mut self, - callback: impl Fn(&mut T, X, U) -> () + SendSync + 'static, + callback: impl Fn(&mut T, X, U) + SendSync + 'static, ) -> &mut Self where T: Variant + Clone, U: Variant + Clone, X: Variant + Clone, { + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for arrays."); + } + #[cfg(not(feature = "no_object"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for object maps."); + } + if TypeId::of::() == TypeId::of::() + || TypeId::of::() == TypeId::of::<&str>() + || TypeId::of::() == TypeId::of::() + { + panic!("Cannot register indexer for strings."); + } + self.register_fn(FN_IDX_SET, callback) } /// Register an index setter for a custom type with the `Engine`. - /// Returns `Result>`. + /// Returns `Result<(), Box>`. + /// + /// # Panics + /// + /// Panics if the type is `Array` or `Map`. + /// Indexers for arrays, object maps and strings cannot be registered. /// /// # Example /// @@ -593,10 +658,10 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } - /// fn set_field(&mut self, index: i64, value: i64) -> Result> { + /// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } + /// fn set_field(&mut self, index: i64, value: i64) -> Result<(), Box> { /// self.fields[index as usize] = value; - /// Ok(().into()) + /// Ok(()) /// } /// } /// @@ -622,18 +687,40 @@ impl Engine { #[cfg(not(feature = "no_index"))] pub fn register_indexer_set_result( &mut self, - callback: impl Fn(&mut T, X, U) -> Result> + SendSync + 'static, + callback: impl Fn(&mut T, X, U) -> Result<(), Box> + SendSync + 'static, ) -> &mut Self where T: Variant + Clone, U: Variant + Clone, X: Variant + Clone, { - self.register_result_fn(FN_IDX_SET, callback) + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for arrays."); + } + #[cfg(not(feature = "no_object"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for object maps."); + } + if TypeId::of::() == TypeId::of::() + || TypeId::of::() == TypeId::of::<&str>() + || TypeId::of::() == TypeId::of::() + { + panic!("Cannot register indexer for strings."); + } + + self.register_result_fn(FN_IDX_SET, move |obj: &mut T, index: X, value: U| { + callback(obj, index, value)?; + Ok(().into()) + }) } /// Short-hand for register both index getter and setter functions for a custom type with the `Engine`. /// + /// # Panics + /// + /// Panics if the type is `Array` or `Map`. + /// Indexers for arrays, object maps and strings cannot be registered. + /// /// # Example /// /// ``` @@ -643,7 +730,8 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } + /// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } + /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } /// } diff --git a/src/engine.rs b/src/engine.rs index ad5b1c0a..d4715eca 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,7 +3,7 @@ use crate::any::{map_std_type_name, Dynamic, Union}; use crate::calc_fn_hash; use crate::fn_call::run_builtin_op_assignment; -use crate::fn_native::{CallableFunction, Callback, FnPtr}; +use crate::fn_native::{Callback, FnPtr}; use crate::module::{Module, ModuleRef}; use crate::optimize::OptimizationLevel; use crate::packages::{Package, PackagesCollection, StandardPackage}; @@ -756,10 +756,7 @@ impl Engine { Err(err) => match *err { // No index getter - try to call an index setter #[cfg(not(feature = "no_index"))] - EvalAltResult::ErrorIndexingType(_, _) => { - // Raise error if there is no index getter nor setter - Some(new_val.unwrap()) - } + EvalAltResult::ErrorIndexingType(_, _) => Some(new_val.unwrap()), // Any other error - return err => return Err(Box::new(err)), }, @@ -1168,7 +1165,7 @@ impl Engine { .take_immutable_string() .map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_pos))?; - map.entry(index).or_insert(Default::default()).into() + map.entry(index).or_insert_with(Default::default).into() } else { let index = idx .read_lock::() @@ -1360,40 +1357,56 @@ impl Engine { let arg_types = once(lhs_ptr.type_id()).chain(once(rhs_val.type_id())); let hash_fn = calc_fn_hash(empty(), op, 2, arg_types); - if let Some(CallableFunction::Method(func)) = self + match self .global_module .get_fn(hash_fn, false) .or_else(|| self.packages.get_fn(hash_fn, false)) { - if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { - let mut lock_guard = lhs_ptr.write_lock::().unwrap(); - let lhs_ptr_inner = lock_guard.deref_mut(); + // op= function registered as method + Some(func) if func.is_method() => { + let mut lock_guard; + let lhs_ptr_inner; + + if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { + lock_guard = lhs_ptr.write_lock::().unwrap(); + lhs_ptr_inner = lock_guard.deref_mut(); + } else { + lhs_ptr_inner = lhs_ptr; + } + + let args = &mut [lhs_ptr_inner, &mut rhs_val]; // Overriding exact implementation - func(self, lib, &mut [lhs_ptr_inner, &mut rhs_val])?; - } else { - // Overriding exact implementation - func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; + if func.is_plugin_fn() { + func.get_plugin_fn().call(args)?; + } else { + func.get_native_fn()(self, lib, args)?; + } } - } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { - // Not built in, map to `var = var op rhs` - let op = &op[..op.len() - 1]; // extract operator without = + // Built-in op-assignment function + _ if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_some() => {} + // Not built-in: expand to `var = var op rhs` + _ => { + let op = &op[..op.len() - 1]; // extract operator without = - // Clone the LHS value - let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val]; + // Clone the LHS value + let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val]; - // Run function - let (value, _) = self - .exec_fn_call( - state, lib, op, 0, args, false, false, false, None, None, level, - ) - .map_err(|err| err.new_position(*op_pos))?; + // Run function + let (value, _) = self + .exec_fn_call( + state, lib, op, 0, args, false, false, false, None, None, + level, + ) + .map_err(|err| err.new_position(*op_pos))?; - let value = value.flatten(); - if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { - *lhs_ptr.write_lock::().unwrap() = value; - } else { - *lhs_ptr = value; + let value = value.flatten(); + + if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { + *lhs_ptr.write_lock::().unwrap() = value; + } else { + *lhs_ptr = value; + } } } Ok(Default::default()) @@ -2014,6 +2027,6 @@ impl Engine { self.type_names .as_ref() .and_then(|t| t.get(name).map(String::as_str)) - .unwrap_or(map_std_type_name(name)) + .unwrap_or_else(|| map_std_type_name(name)) } } diff --git a/src/module.rs b/src/module.rs index 51bfc3bf..e03c11fa 100644 --- a/src/module.rs +++ b/src/module.rs @@ -21,11 +21,10 @@ use crate::{ }; #[cfg(not(feature = "no_index"))] -#[cfg(not(feature = "no_object"))] -use crate::engine::{FN_IDX_GET, FN_IDX_SET}; +use crate::engine::{Array, FN_IDX_GET, FN_IDX_SET}; #[cfg(not(feature = "no_object"))] -use crate::engine::{make_getter, make_setter}; +use crate::engine::{make_getter, make_setter, Map}; use crate::stdlib::{ any::TypeId, @@ -675,6 +674,11 @@ impl Module { /// /// If there is a similar existing setter Rust function, it is replaced. /// + /// # Panics + /// + /// Panics if the type is `Array` or `Map`. + /// Indexers for arrays, object maps and strings cannot be registered. + /// /// # Examples /// /// ``` @@ -686,12 +690,25 @@ impl Module { /// }); /// assert!(module.contains_fn(hash, true)); /// ``` - #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] pub fn set_indexer_get_fn( &mut self, func: impl Fn(&mut A, B) -> FuncReturn + SendSync + 'static, ) -> u64 { + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for arrays."); + } + #[cfg(not(feature = "no_object"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for object maps."); + } + if TypeId::of::() == TypeId::of::() + || TypeId::of::() == TypeId::of::<&str>() + || TypeId::of::() == TypeId::of::() + { + panic!("Cannot register indexer for strings."); + } + self.set_fn_2_mut(FN_IDX_GET, func) } @@ -773,6 +790,11 @@ impl Module { /// /// If there is a similar existing Rust function, it is replaced. /// + /// # Panics + /// + /// Panics if the type is `Array` or `Map`. + /// Indexers for arrays, object maps and strings cannot be registered. + /// /// # Examples /// /// ``` @@ -785,12 +807,25 @@ impl Module { /// }); /// assert!(module.contains_fn(hash, true)); /// ``` - #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] pub fn set_indexer_set_fn( &mut self, func: impl Fn(&mut A, B, C) -> FuncReturn<()> + SendSync + 'static, ) -> u64 { + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for arrays."); + } + #[cfg(not(feature = "no_object"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for object maps."); + } + if TypeId::of::() == TypeId::of::() + || TypeId::of::() == TypeId::of::<&str>() + || TypeId::of::() == TypeId::of::() + { + panic!("Cannot register indexer for strings."); + } + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = cast_arg::(&mut args[1]); let c = cast_arg::(&mut args[2]); @@ -812,6 +847,11 @@ impl Module { /// /// If there are similar existing Rust functions, they are replaced. /// + /// # Panics + /// + /// Panics if the type is `Array` or `Map`. + /// Indexers for arrays, object maps and strings cannot be registered. + /// /// # Examples /// /// ``` @@ -830,7 +870,6 @@ impl Module { /// assert!(module.contains_fn(hash_get, true)); /// assert!(module.contains_fn(hash_set, true)); /// ``` - #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] pub fn set_indexer_get_set_fn( &mut self, diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 05599eac..8b56be54 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -23,23 +23,25 @@ pub type Unit = (); macro_rules! gen_array_functions { ($root:ident => $($arg_type:ident),+ ) => { - pub mod $root { $(pub mod $arg_type { + pub mod $root { $( pub mod $arg_type { use super::super::*; - #[export_fn] - #[inline(always)] - pub fn push(list: &mut Array, item: $arg_type) { - list.push(Dynamic::from(item)); - } + #[export_module] + pub mod functions { + #[rhai_fn(name = "push", name = "+=")] + #[inline(always)] + pub fn push(list: &mut Array, item: $arg_type) { + list.push(Dynamic::from(item)); + } - #[export_fn] - pub fn insert(list: &mut Array, position: INT, item: $arg_type) { - if position <= 0 { - list.insert(0, Dynamic::from(item)); - } else if (position as usize) >= list.len() - 1 { - push(list, item); - } else { - list.insert(position as usize, Dynamic::from(item)); + pub fn insert(list: &mut Array, position: INT, item: $arg_type) { + if position <= 0 { + list.insert(0, Dynamic::from(item)); + } else if (position as usize) >= list.len() - 1 { + push(list, item); + } else { + list.insert(position as usize, Dynamic::from(item)); + } } } })* } @@ -48,8 +50,7 @@ macro_rules! gen_array_functions { macro_rules! reg_functions { ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( - set_exported_fn!($mod_name, "push", $root::$arg_type::push); - set_exported_fn!($mod_name, "insert", $root::$arg_type::insert); + combine_with_exported_module!($mod_name, "array_functions", $root::$arg_type::functions); $mod_name.set_raw_fn("pad", &[TypeId::of::(), TypeId::of::(), TypeId::of::<$arg_type>()], @@ -58,8 +59,6 @@ macro_rules! reg_functions { } def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { - combine_with_exported_module!(lib, "array", array_functions); - reg_functions!(lib += basic; INT, bool, char, ImmutableString, FnPtr, Array, Unit); #[cfg(not(feature = "only_i32"))] @@ -77,6 +76,9 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { #[cfg(not(feature = "no_object"))] reg_functions!(lib += map; Map); + // Merge in the module at the end to override `+=` for arrays + combine_with_exported_module!(lib, "array", array_functions); + // Register array iterator lib.set_iter( TypeId::of::(), diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index fde57db7..61e3e330 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -18,7 +18,6 @@ mod map_functions { pub fn has(map: &mut Map, prop: ImmutableString) -> bool { map.contains_key(&prop) } - #[rhai_fn(name = "len", get = "len")] #[inline(always)] pub fn len(map: &mut Map) -> INT { map.len() as INT @@ -46,9 +45,7 @@ mod map_functions { } pub fn fill_with(map1: &mut Map, map2: Map) { map2.into_iter().for_each(|(key, value)| { - if !map1.contains_key(&key) { - map1.insert(key, value); - } + map1.entry(key).or_insert(value); }); } diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 2fd2a9c4..45ce2fe3 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -3,12 +3,13 @@ use crate::def_package; use crate::parser::INT; use crate::plugin::*; +use crate::token::Position; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; #[cfg(not(feature = "no_float"))] -use crate::{result::EvalAltResult, token::Position}; +use crate::result::EvalAltResult; #[cfg(feature = "no_std")] #[cfg(not(feature = "no_float"))] @@ -67,6 +68,9 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { reg_functions!(lib += basic_to_int::to_int(char)); + set_exported_fn!(lib, "parse_int", parse_int); + set_exported_fn!(lib, "parse_int", parse_int_radix); + #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { @@ -223,6 +227,21 @@ mod float_functions { Ok((x.trunc() as INT).into()) } } + + #[rhai_fn(return_raw)] + #[inline] + pub fn parse_float(s: &str) -> Result> { + s.trim() + .parse::() + .map(Into::::into) + .map_err(|err| { + EvalAltResult::ErrorArithmetic( + format!("Error parsing floating-point number '{}': {}", s, err), + Position::none(), + ) + .into() + }) + } } #[cfg(not(feature = "no_float"))] @@ -249,3 +268,30 @@ gen_conversion_functions!(numbers_to_int => to_int (i8, u8, i16, u16, i32, u32, #[cfg(not(feature = "only_i64"))] #[cfg(not(target_arch = "wasm32"))] gen_conversion_functions!(num_128_to_int => to_int (i128, u128) -> INT); + +#[export_fn(return_raw)] +fn parse_int_radix(s: &str, radix: INT) -> Result> { + if radix < 2 || radix > 36 { + return EvalAltResult::ErrorArithmetic( + format!("Invalid radix: '{}'", radix), + Position::none(), + ) + .into(); + } + + INT::from_str_radix(s.trim(), radix as u32) + .map(Into::::into) + .map_err(|err| { + EvalAltResult::ErrorArithmetic( + format!("Error parsing integer number '{}': {}", s, err), + Position::none(), + ) + .into() + }) +} + +#[export_fn(return_raw)] +#[inline(always)] +fn parse_int(s: &str) -> Result> { + parse_int_radix(s, 10) +} diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index d6f57c02..de7a0484 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -151,6 +151,7 @@ fn to_string(x: &mut T) -> ImmutableString { fn to_debug(x: &mut T) -> ImmutableString { format!("{:?}", x).into() } + #[cfg(not(feature = "no_object"))] mod format_map { use super::*; diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 55fa4be0..c6ac9bc6 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -17,28 +17,30 @@ use crate::stdlib::{ macro_rules! gen_concat_functions { ($root:ident => $($arg_type:ident),+ ) => { - pub mod $root { $(pub mod $arg_type { + pub mod $root { $( pub mod $arg_type { use super::super::*; - #[export_fn] - #[inline] - pub fn append_func(x: &mut ImmutableString, y: $arg_type) -> String { - format!("{}{}", x, y) - } + #[export_module] + pub mod functions { + #[rhai_fn(name = "+")] + #[inline] + pub fn append_func(x: &str, y: $arg_type) -> String { + format!("{}{}", x, y) + } - #[export_fn] - #[inline] - pub fn prepend_func(x: &mut $arg_type, y: ImmutableString) -> String { - format!("{}{}", x, y) + #[rhai_fn(name = "+")] + #[inline] + pub fn prepend_func(x: &mut $arg_type, y: &str) -> String { + format!("{}{}", x, y) + } } - })* } + } )* } } } macro_rules! reg_functions { ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( - set_exported_fn!($mod_name, "+", $root::$arg_type::append_func); - set_exported_fn!($mod_name, "+", $root::$arg_type::prepend_func); + combine_with_exported_module!($mod_name, "strings_concat", $root::$arg_type::functions); )* } } @@ -154,7 +156,7 @@ mod string_functions { #[rhai_fn(name = "len", get = "len")] #[inline(always)] - pub fn len(s: &mut ImmutableString) -> INT { + pub fn len(s: &str) -> INT { s.chars().count() as INT } @@ -182,17 +184,17 @@ mod string_functions { #[rhai_fn(name = "contains")] #[inline(always)] - pub fn contains_char(s: &mut ImmutableString, ch: char) -> bool { + pub fn contains_char(s: &str, ch: char) -> bool { s.contains(ch) } #[rhai_fn(name = "contains")] #[inline(always)] - pub fn contains_string(s: &mut ImmutableString, find: ImmutableString) -> bool { + pub fn contains_string(s: &str, find: ImmutableString) -> bool { s.contains(find.as_str()) } #[rhai_fn(name = "index_of")] - pub fn index_of_char_starting_from(s: &mut ImmutableString, ch: char, start: INT) -> INT { + pub fn index_of_char_starting_from(s: &str, ch: char, start: INT) -> INT { let start = if start < 0 { 0 } else if (start as usize) >= s.chars().count() { @@ -207,17 +209,13 @@ mod string_functions { .unwrap_or(-1 as INT) } #[rhai_fn(name = "index_of")] - pub fn index_of_char(s: &mut ImmutableString, ch: char) -> INT { + pub fn index_of_char(s: &str, ch: char) -> INT { s.find(ch) .map(|index| s[0..index].chars().count() as INT) .unwrap_or(-1 as INT) } #[rhai_fn(name = "index_of")] - pub fn index_of_string_starting_from( - s: &mut ImmutableString, - find: ImmutableString, - start: INT, - ) -> INT { + pub fn index_of_string_starting_from(s: &str, find: ImmutableString, start: INT) -> INT { let start = if start < 0 { 0 } else if (start as usize) >= s.chars().count() { @@ -232,14 +230,14 @@ mod string_functions { .unwrap_or(-1 as INT) } #[rhai_fn(name = "index_of")] - pub fn index_of_string(s: &mut ImmutableString, find: ImmutableString) -> INT { + pub fn index_of_string(s: &str, find: ImmutableString) -> INT { s.find(find.as_str()) .map(|index| s[0..index].chars().count() as INT) .unwrap_or(-1 as INT) } #[rhai_fn(name = "sub_string")] - pub fn sub_string(s: ImmutableString, start: INT, len: INT) -> ImmutableString { + pub fn sub_string(s: &str, start: INT, len: INT) -> ImmutableString { let offset = if s.is_empty() || len <= 0 { return "".to_string().into(); } else if start < 0 { @@ -268,7 +266,7 @@ mod string_functions { } #[rhai_fn(name = "sub_string")] #[inline(always)] - pub fn sub_string_starting_from(s: ImmutableString, start: INT) -> ImmutableString { + pub fn sub_string_starting_from(s: &str, start: INT) -> ImmutableString { let len = s.len() as INT; sub_string(s, start, len) } @@ -332,13 +330,29 @@ mod string_functions { #[rhai_fn(name = "+")] #[inline] - pub fn append(x: &mut ImmutableString, y: Array) -> String { + pub fn append(x: &str, y: Array) -> String { format!("{}{:?}", x, y) } #[rhai_fn(name = "+")] #[inline] - pub fn prepend(x: &mut Array, y: ImmutableString) -> String { + pub fn prepend(x: &mut Array, y: &str) -> String { format!("{:?}{}", x, y) } } + + #[cfg(not(feature = "no_object"))] + pub mod maps { + use crate::engine::Map; + + #[rhai_fn(name = "+")] + #[inline] + pub fn append(x: &str, y: Map) -> String { + format!("{}#{:?}", x, y) + } + #[rhai_fn(name = "+")] + #[inline] + pub fn prepend(x: &mut Map, y: &str) -> String { + format!("#{:?}{}", x, y) + } + } } diff --git a/src/parser.rs b/src/parser.rs index d598c7a6..2c0076df 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3479,7 +3479,7 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { Union::Unit(_) => Some(Expr::Unit(pos)), Union::Int(value) => Some(Expr::IntegerConstant(Box::new((value, pos)))), Union::Char(value) => Some(Expr::CharConstant(Box::new((value, pos)))), - Union::Str(value) => Some(Expr::StringConstant(Box::new((value.clone(), pos)))), + Union::Str(value) => Some(Expr::StringConstant(Box::new((value, pos)))), Union::Bool(true) => Some(Expr::True(pos)), Union::Bool(false) => Some(Expr::False(pos)), #[cfg(not(feature = "no_index"))] diff --git a/src/token.rs b/src/token.rs index c1968d23..ad0ef340 100644 --- a/src/token.rs +++ b/src/token.rs @@ -752,8 +752,10 @@ pub fn parse_string_literal( let mut result: StaticVec = Default::default(); let mut escape: StaticVec = Default::default(); + let start = *pos; + loop { - let next_char = stream.get_next().ok_or((LERR::UnterminatedString, *pos))?; + let next_char = stream.get_next().ok_or((LERR::UnterminatedString, start))?; pos.advance(); @@ -838,17 +840,19 @@ pub fn parse_string_literal( ch if enclosing_char == ch && escape.is_empty() => break, // Unknown escape sequence - _ if !escape.is_empty() => { + ch if !escape.is_empty() => { + escape.push(ch); + return Err(( LERR::MalformedEscapeSequence(escape.into_iter().collect()), *pos, - )) + )); } // Cannot have new-lines inside string literals '\n' => { pos.rewind(); - return Err((LERR::UnterminatedString, *pos)); + return Err((LERR::UnterminatedString, start)); } // All other characters diff --git a/tests/arrays.rs b/tests/arrays.rs index 8ea61cdf..62d9de0b 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -13,6 +13,10 @@ fn test_arrays() -> Result<(), Box> { '3' ); assert!(engine.eval::("let y = [1, 2, 3]; 2 in y")?); + assert_eq!(engine.eval::("let y = [1, 2, 3]; y += 4; y[3]")?, 4); + + #[cfg(not(feature = "no_object"))] + assert_eq!(engine.eval::("let y = [1, 2, 3]; y.push(4); y[3]")?, 4); #[cfg(not(feature = "no_object"))] assert_eq!( diff --git a/tests/float.rs b/tests/float.rs index 211828d0..ea87ece3 100644 --- a/tests/float.rs +++ b/tests/float.rs @@ -20,6 +20,15 @@ fn test_float() -> Result<(), Box> { Ok(()) } +#[test] +fn test_float_parse() -> Result<(), Box> { + let engine = Engine::new(); + + assert!((engine.eval::(r#"parse_float("9.9999")"#)? - 9.9999 as FLOAT).abs() < EPSILON); + + Ok(()) +} + #[test] #[cfg(not(feature = "no_object"))] fn test_struct_with_float() -> Result<(), Box> { diff --git a/tests/math.rs b/tests/math.rs index c12e3e34..fa398c83 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -108,3 +108,14 @@ fn test_math() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_math_parse() -> Result<(), Box> { + let engine = Engine::new(); + + assert_eq!(engine.eval::(r#"parse_int("42")"#)?, 42); + assert_eq!(engine.eval::(r#"parse_int("42", 16)"#)?, 0x42); + assert_eq!(engine.eval::(r#"parse_int("abcdef", 16)"#)?, 0xabcdef); + + Ok(()) +}