diff --git a/RELEASES.md b/RELEASES.md index cb155381..ccba5394 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -9,6 +9,8 @@ Bug fixes * `if` statement with an empty `true` block would not evaluate the `false` block. This is now fixed. * 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`. New features ------------ @@ -72,7 +74,7 @@ New features * Currying of function pointers is supported via the new `curry` keyword. * Automatic currying of anonymous functions to capture shared variables from the external scope. * Capturing of the calling scope for function call via the `func!(...)` syntax. -* `Module::set_indexer_get_set_fn` is added as a shorthand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`. +* `Module::set_indexer_get_set_fn` is added as a short-hand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`. * New `unicode-xid-ident` feature to allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) for identifiers. * `Scope::iter_raw` returns an iterator with a reference to the underlying `Dynamic` value (which may be shared). @@ -198,7 +200,7 @@ Breaking changes New features ------------ -* Indexers are now split into getters and setters (which now support updates). The API is split into `Engine::register_indexer_get` and `Engine::register_indexer_set` with `Engine::register_indexer_get_set` being a shorthand. Similarly, `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn` are added. +* Indexers are now split into getters and setters (which now support updates). The API is split into `Engine::register_indexer_get` and `Engine::register_indexer_set` with `Engine::register_indexer_get_set` being a short-hand. Similarly, `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn` are added. * `Engine:register_fn` and `Engine:register_result_fn` accepts functions that take parameters of type `&str` (immutable string slice), which maps directly to `ImmutableString`. This is to avoid needing wrappers for functions taking string parameters. * Set maximum limit on data sizes: `Engine::set_max_string_size`, `Engine::set_max_array_size` and `Engine::set_max_map_size`. * Supports trailing commas on array literals, object map literals, function definitions and function calls. diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 75812dbd..2fca6d96 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -84,71 +84,91 @@ impl ExportedParams for ExportedFnParams { let mut skip = false; let mut special = FnSpecialAccess::None; for attr in attrs { - let crate::attrs::AttrItem { key, value, span: item_span } = attr; + let crate::attrs::AttrItem { + key, + value, + span: item_span, + } = attr; match (key.to_string().as_ref(), value) { ("get", None) | ("set", None) | ("name", None) => { return Err(syn::Error::new(key.span(), "requires value")) - }, + } ("name", Some(s)) if &s.value() == FN_IDX_GET => { - return Err(syn::Error::new(item_span, - "use attribute 'index_get' instead")) - }, + return Err(syn::Error::new( + item_span, + "use attribute 'index_get' instead", + )) + } ("name", Some(s)) if &s.value() == FN_IDX_SET => { - return Err(syn::Error::new(item_span, - "use attribute 'index_set' instead")) - }, + return Err(syn::Error::new( + item_span, + "use attribute 'index_set' instead", + )) + } ("name", Some(s)) if s.value().starts_with("get$") => { - return Err(syn::Error::new(item_span, - format!("use attribute 'getter = \"{}\"' instead", - &s.value()["get$".len()..]))) - }, + return Err(syn::Error::new( + item_span, + format!( + "use attribute 'getter = \"{}\"' instead", + &s.value()["get$".len()..] + ), + )) + } ("name", Some(s)) if s.value().starts_with("set$") => { - return Err(syn::Error::new(item_span, - format!("use attribute 'setter = \"{}\"' instead", - &s.value()["set$".len()..]))) - }, + return Err(syn::Error::new( + item_span, + format!( + "use attribute 'setter = \"{}\"' instead", + &s.value()["set$".len()..] + ), + )) + } ("name", Some(s)) if s.value().contains('$') => { - return Err(syn::Error::new(s.span(), - "Rhai function names may not contain dollar sign")) - }, + return Err(syn::Error::new( + s.span(), + "Rhai function names may not contain dollar sign", + )) + } ("name", Some(s)) if s.value().contains('.') => { - return Err(syn::Error::new(s.span(), - "Rhai function names may not contain dot")) - }, - ("name", Some(s)) => { - name.push(s.value()) - }, - ("set", Some(s)) => special = match special { - FnSpecialAccess::None => - FnSpecialAccess::Property(Property::Set(syn::Ident::new(&s.value(), - s.span()))), - _ => { - return Err(syn::Error::new(item_span.span(), "conflicting setter")) + return Err(syn::Error::new( + s.span(), + "Rhai function names may not contain dot", + )) + } + ("name", Some(s)) => name.push(s.value()), + ("set", Some(s)) => { + special = match special { + FnSpecialAccess::None => FnSpecialAccess::Property(Property::Set( + syn::Ident::new(&s.value(), s.span()), + )), + _ => return Err(syn::Error::new(item_span.span(), "conflicting setter")), } - }, - ("get", Some(s)) => special = match special { - FnSpecialAccess::None => - FnSpecialAccess::Property(Property::Get(syn::Ident::new(&s.value(), - s.span()))), - _ => { - return Err(syn::Error::new(item_span.span(), "conflicting getter")) + } + ("get", Some(s)) => { + special = match special { + FnSpecialAccess::None => FnSpecialAccess::Property(Property::Get( + syn::Ident::new(&s.value(), s.span()), + )), + _ => return Err(syn::Error::new(item_span.span(), "conflicting getter")), } - }, - ("index_get", None) => special = match special { - FnSpecialAccess::None => - FnSpecialAccess::Index(Index::Get), - _ => { - return Err(syn::Error::new(item_span.span(), "conflicting index_get")) + } + ("index_get", None) => { + special = match special { + FnSpecialAccess::None => FnSpecialAccess::Index(Index::Get), + _ => { + return Err(syn::Error::new(item_span.span(), "conflicting index_get")) + } } - }, + } - ("index_set", None) => special = match special { - FnSpecialAccess::None => - FnSpecialAccess::Index(Index::Set), - _ => { - return Err(syn::Error::new(item_span.span(), "conflicting index_set")) + ("index_set", None) => { + special = match special { + FnSpecialAccess::None => FnSpecialAccess::Index(Index::Set), + _ => { + return Err(syn::Error::new(item_span.span(), "conflicting index_set")) + } } - }, + } ("return_raw", None) => return_raw = true, ("index_get", Some(s)) | ("index_set", Some(s)) | ("return_raw", Some(s)) => { return Err(syn::Error::new(s.span(), "extraneous value")) @@ -327,26 +347,40 @@ impl ExportedFn { } pub(crate) fn exported_names(&self) -> Vec { - let mut literals = self.params.name.as_ref() - .map(|v| v.iter() - .map(|s| syn::LitStr::new(s, proc_macro2::Span::call_site())).collect()) + let mut literals = self + .params + .name + .as_ref() + .map(|v| { + v.iter() + .map(|s| syn::LitStr::new(s, proc_macro2::Span::call_site())) + .collect() + }) .unwrap_or_else(|| Vec::new()); match self.params.special { - FnSpecialAccess::None => {}, - FnSpecialAccess::Property(Property::Get(ref g)) => - literals.push(syn::LitStr::new(&format!("get${}", g.to_string()), g.span())), - FnSpecialAccess::Property(Property::Set(ref s)) => - literals.push(syn::LitStr::new(&format!("set${}", s.to_string()), s.span())), - FnSpecialAccess::Index(Index::Get) => - literals.push(syn::LitStr::new(FN_IDX_GET, proc_macro2::Span::call_site())), - FnSpecialAccess::Index(Index::Set) => - literals.push(syn::LitStr::new(FN_IDX_SET, proc_macro2::Span::call_site())), + FnSpecialAccess::None => {} + FnSpecialAccess::Property(Property::Get(ref g)) => literals.push(syn::LitStr::new( + &format!("get${}", g.to_string()), + g.span(), + )), + FnSpecialAccess::Property(Property::Set(ref s)) => literals.push(syn::LitStr::new( + &format!("set${}", s.to_string()), + s.span(), + )), + FnSpecialAccess::Index(Index::Get) => { + literals.push(syn::LitStr::new(FN_IDX_GET, proc_macro2::Span::call_site())) + } + FnSpecialAccess::Index(Index::Set) => { + literals.push(syn::LitStr::new(FN_IDX_SET, proc_macro2::Span::call_site())) + } } if literals.is_empty() { - literals.push(syn::LitStr::new(&self.signature.ident.to_string(), - self.signature.ident.span())); + literals.push(syn::LitStr::new( + &self.signature.ident.to_string(), + self.signature.ident.span(), + )); } literals @@ -394,53 +428,61 @@ impl ExportedFn { match params.special { // 2a. Property getters must take only the subject as an argument. - FnSpecialAccess::Property(Property::Get(_)) if self.arg_count() != 1 => + FnSpecialAccess::Property(Property::Get(_)) if self.arg_count() != 1 => { return Err(syn::Error::new( self.signature.span(), "property getter requires exactly 1 argument", - )), + )) + } // 2b. Property getters must return a value. - FnSpecialAccess::Property(Property::Get(_)) if self.return_type().is_none() => + FnSpecialAccess::Property(Property::Get(_)) if self.return_type().is_none() => { return Err(syn::Error::new( self.signature.span(), - "property getter must return a value" - )), + "property getter must return a value", + )) + } // 3a. Property setters must take the subject and a new value as arguments. - FnSpecialAccess::Property(Property::Set(_)) if self.arg_count() != 2 => + FnSpecialAccess::Property(Property::Set(_)) if self.arg_count() != 2 => { return Err(syn::Error::new( self.signature.span(), "property setter requires exactly 2 arguments", - )), + )) + } // 3b. Property setters must return nothing. - FnSpecialAccess::Property(Property::Set(_)) if self.return_type().is_some() => + FnSpecialAccess::Property(Property::Set(_)) if self.return_type().is_some() => { return Err(syn::Error::new( self.signature.span(), - "property setter must return no value" - )), + "property setter must return no value", + )) + } // 4a. Index getters must take the subject and the accessed "index" as arguments. - FnSpecialAccess::Index(Index::Get) if self.arg_count() != 2 => + FnSpecialAccess::Index(Index::Get) if self.arg_count() != 2 => { return Err(syn::Error::new( self.signature.span(), "index getter requires exactly 2 arguments", - )), + )) + } // 4b. Index getters must return a value. - FnSpecialAccess::Index(Index::Get) if self.return_type().is_none() => + FnSpecialAccess::Index(Index::Get) if self.return_type().is_none() => { return Err(syn::Error::new( self.signature.span(), - "index getter must return a value" - )), + "index getter must return a value", + )) + } // 5a. Index setters must take the subject, "index", and new value as arguments. - FnSpecialAccess::Index(Index::Set) if self.arg_count() != 3 => + FnSpecialAccess::Index(Index::Set) if self.arg_count() != 3 => { return Err(syn::Error::new( self.signature.span(), "index setter requires exactly 3 arguments", - )), + )) + } // 5b. Index setters must return nothing. - FnSpecialAccess::Index(Index::Set) if self.return_type().is_some() => + FnSpecialAccess::Index(Index::Set) if self.return_type().is_some() => { return Err(syn::Error::new( self.signature.span(), - "index setter must return no value" - )), + "index setter must return no value", + )) + } _ => {} } @@ -596,11 +638,13 @@ impl ExportedFn { // Handle the rest of the arguments, which all are passed by value. // // The only exception is strings, which need to be downcast to ImmutableString to enable a - // zero-copy conversion to &str by reference. + // zero-copy conversion to &str by reference, or a cloned String. let str_type_path = syn::parse2::(quote! { str }).unwrap(); + let string_type_path = syn::parse2::(quote! { String }).unwrap(); for (i, arg) in self.arg_list().enumerate().skip(skip_first_arg as usize) { let var = syn::Ident::new(&format!("arg{}", i), proc_macro2::Span::call_site()); - let is_str_ref; + let is_string; + let is_ref; match arg { syn::FnArg::Typed(pattern) => { let arg_type: &syn::Type = pattern.ty.as_ref(); @@ -611,15 +655,22 @@ impl ExportedFn { .. }) => match elem.as_ref() { &syn::Type::Path(ref p) if p.path == str_type_path => { - is_str_ref = true; + is_string = true; + is_ref = true; quote_spanned!(arg_type.span()=> - mem::take(args[#i]) - .clone().cast::()) + mem::take(args[#i]).take_immutable_string().unwrap()) } _ => panic!("internal error: why wasn't this found earlier!?"), }, + &syn::Type::Path(ref p) if p.path == string_type_path => { + is_string = true; + is_ref = false; + quote_spanned!(arg_type.span()=> + mem::take(args[#i]).take_string().unwrap()) + } _ => { - is_str_ref = false; + is_string = false; + is_ref = false; quote_spanned!(arg_type.span()=> mem::take(args[#i]).clone().cast::<#arg_type>()) } @@ -631,7 +682,7 @@ impl ExportedFn { }) .unwrap(), ); - if !is_str_ref { + if !is_string { input_type_exprs.push( syn::parse2::(quote_spanned!( arg_type.span()=> TypeId::of::<#arg_type>() @@ -649,7 +700,7 @@ impl ExportedFn { } syn::FnArg::Receiver(_) => panic!("internal error: how did this happen!?"), } - if !is_str_ref { + if !is_ref { unpack_exprs.push(syn::parse2::(quote! { #var }).unwrap()); } else { unpack_exprs.push(syn::parse2::(quote! { &#var }).unwrap()); @@ -685,7 +736,7 @@ impl ExportedFn { quote! { impl PluginFunction for #type_name { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), #arg_count, "wrong arg count: {} != {}", diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index b9d8880d..5da8e1b4 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -152,6 +152,19 @@ pub fn exported_module(module_path: proc_macro::TokenStream) -> proc_macro::Toke proc_macro::TokenStream::from(tokens) } +#[proc_macro] +pub fn combine_with_exported_module(args: proc_macro::TokenStream) -> proc_macro::TokenStream { + let (module_expr, _export_name, module_path) = match crate::register::parse_register_macro(args) + { + Ok(triple) => triple, + Err(e) => return e.to_compile_error().into(), + }; + let tokens = quote! { + #module_path::rhai_generate_into_module(#module_expr, true); + }; + proc_macro::TokenStream::from(tokens) +} + #[proc_macro] pub fn register_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenStream { let (engine_expr, export_name, rust_modpath) = match crate::register::parse_register_macro(args) diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 1ebc25b8..85b1278c 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -140,12 +140,13 @@ impl Parse for Module { ref expr, ident, attrs, + ty, .. }) => { // #[cfg] attributes are not allowed on const declarations crate::attrs::deny_cfg_attr(&attrs)?; if let syn::Visibility::Public(_) = vis { - consts.push((ident.to_string(), expr.as_ref().clone())); + consts.push((ident.to_string(), ty.clone(), expr.as_ref().clone())); } } _ => {} @@ -191,6 +192,7 @@ impl Parse for Module { } } +#[allow(dead_code)] impl Module { pub fn attrs(&self) -> Option<&Vec> { self.mod_all.as_ref().map(|m| &m.attrs) diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index d318f351..c25c9484 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -6,7 +6,7 @@ use crate::attrs::ExportScope; use crate::function::ExportedFn; use crate::module::Module; -pub(crate) type ExportedConst = (String, syn::Expr); +pub(crate) type ExportedConst = (String, Box, syn::Expr); pub(crate) fn generate_body( fns: &mut [ExportedFn], @@ -17,13 +17,16 @@ pub(crate) fn generate_body( let mut set_fn_stmts: Vec = Vec::new(); let mut set_const_stmts: Vec = Vec::new(); let mut add_mod_blocks: Vec = Vec::new(); + let mut set_flattened_mod_blocks: Vec = Vec::new(); let str_type_path = syn::parse2::(quote! { str }).unwrap(); + let string_type_path = syn::parse2::(quote! { String }).unwrap(); - for (const_name, const_expr) in consts { + for (const_name, _, _) in consts { let const_literal = syn::LitStr::new(&const_name, proc_macro2::Span::call_site()); + let const_ref = syn::Ident::new(&const_name, proc_macro2::Span::call_site()); set_const_stmts.push( syn::parse2::(quote! { - m.set_var(#const_literal, #const_expr); + m.set_var(#const_literal, #const_ref); }) .unwrap(), ); @@ -54,6 +57,14 @@ pub(crate) fn generate_body( }) .unwrap(), ); + set_flattened_mod_blocks.push( + syn::parse2::(quote! { + #(#cfg_attrs)* { + self::#module_name::rhai_generate_into_module(m, flatten); + } + }) + .unwrap(), + ); } // NB: these are token streams, because reparsing messes up "> >" vs ">>" @@ -87,6 +98,11 @@ pub(crate) fn generate_body( } _ => panic!("internal error: non-string shared reference!?"), }, + syn::Type::Path(ref p) if p.path == string_type_path => { + syn::parse2::(quote! { + ImmutableString }) + .unwrap() + } syn::Type::Reference(syn::TypeReference { mutability: Some(_), ref elem, @@ -129,13 +145,22 @@ pub(crate) fn generate_body( pub mod generate_info { #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { #(#set_fn_stmts)* #(#set_const_stmts)* - #(#add_mod_blocks)* - m + + if flatten { + #(#set_flattened_mod_blocks)* + } else { + #(#add_mod_blocks)* + } } } }) diff --git a/codegen/src/test/function.rs b/codegen/src/test/function.rs index 5430b311..f22f1fe2 100644 --- a/codegen/src/test/function.rs +++ b/codegen/src/test/function.rs @@ -237,21 +237,27 @@ mod generate_tests { let expected = expected.to_string(); if &actual != &expected { let mut counter = 0; - let iter = actual - .chars() - .zip(expected.chars()) - .inspect(|_| counter += 1) - .skip_while(|(a, e)| *a == *e); - let (actual_diff, expected_diff) = { + let _iter = actual.chars().zip(expected.chars()).skip_while(|(a, e)| { + if *a == *e { + counter += 1; + true + } else { + false + } + }); + let (_actual_diff, _expected_diff) = { let mut actual_diff = String::new(); let mut expected_diff = String::new(); - for (a, e) in iter.take(50) { + for (a, e) in _iter.take(50) { actual_diff.push(a); expected_diff.push(e); } (actual_diff, expected_diff) }; eprintln!("actual != expected, diverge at char {}", counter); + // eprintln!(" actual: {}", _actual_diff); + // eprintln!("expected: {}", _expected_diff); + // assert!(false); } assert_eq!(actual, expected); } @@ -269,7 +275,7 @@ mod generate_tests { struct Token(); impl PluginFunction for Token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 0usize, "wrong arg count: {} != {}", args.len(), 0usize); @@ -313,7 +319,7 @@ mod generate_tests { struct Token(); impl PluginFunction for Token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); @@ -354,7 +360,7 @@ mod generate_tests { let expected_tokens = quote! { impl PluginFunction for MyType { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); @@ -388,7 +394,7 @@ mod generate_tests { struct Token(); impl PluginFunction for Token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); @@ -435,7 +441,7 @@ mod generate_tests { struct Token(); impl PluginFunction for Token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); @@ -483,11 +489,11 @@ mod generate_tests { struct Token(); impl PluginFunction for Token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> 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]).take_immutable_string().unwrap(); Ok(Dynamic::from(special_print(&arg0))) } diff --git a/codegen/src/test/module.rs b/codegen/src/test/module.rs index 2bf48752..80a93f28 100644 --- a/codegen/src/test/module.rs +++ b/codegen/src/test/module.rs @@ -109,7 +109,7 @@ mod module_tests { assert_eq!(item_mod.submodules().len(), 1); assert_eq!(&item_mod.submodules()[0].consts()[0].0, "MYSTIC_NUMBER"); assert_eq!( - item_mod.submodules()[0].consts()[0].1, + item_mod.submodules()[0].consts()[0].2, syn::parse2::(quote! { 42 }).unwrap() ); } @@ -170,7 +170,7 @@ mod module_tests { assert_eq!(item_mod.consts().len(), 1); assert_eq!(&item_mod.consts()[0].0, "MYSTIC_NUMBER"); assert_eq!( - item_mod.consts()[0].1, + item_mod.consts()[0].2, syn::parse2::(quote! { 42 }).unwrap() ); } @@ -218,21 +218,27 @@ mod generate_tests { let expected = expected.to_string(); if &actual != &expected { let mut counter = 0; - let iter = actual - .chars() - .zip(expected.chars()) - .inspect(|_| counter += 1) - .skip_while(|(a, e)| *a == *e); + let _iter = actual.chars().zip(expected.chars()).skip_while(|(a, e)| { + if *a == *e { + counter += 1; + true + } else { + false + } + }); let (_actual_diff, _expected_diff) = { let mut actual_diff = String::new(); let mut expected_diff = String::new(); - for (a, e) in iter.take(50) { + for (a, e) in _iter.take(50) { actual_diff.push(a); expected_diff.push(e); } (actual_diff, expected_diff) }; eprintln!("actual != expected, diverge at char {}", counter); + // eprintln!(" actual: {}", _actual_diff); + // eprintln!("expected: {}", _expected_diff); + // assert!(false); } assert_eq!(actual, expected); } @@ -247,11 +253,16 @@ mod generate_tests { pub mod empty { #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + if flatten {} else {} + } } }; @@ -276,18 +287,23 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("get_mystic_number", FnAccess::Public, &[], CallableFunction::from_plugin(get_mystic_number_token())); - m + if flatten {} else {} } #[allow(non_camel_case_types)] struct get_mystic_number_token(); impl PluginFunction for get_mystic_number_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 0usize, "wrong arg count: {} != {}", args.len(), 0usize); @@ -333,18 +349,23 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("add_one_to", FnAccess::Public, &[core::any::TypeId::of::()], CallableFunction::from_plugin(add_one_to_token())); - m + if flatten {} else {} } #[allow(non_camel_case_types)] struct add_one_to_token(); impl PluginFunction for add_one_to_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); @@ -402,22 +423,26 @@ mod generate_tests { #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("add_n", FnAccess::Public, &[core::any::TypeId::of::()], CallableFunction::from_plugin(add_one_to_token())); m.set_fn("add_n", FnAccess::Public, &[core::any::TypeId::of::(), core::any::TypeId::of::()], CallableFunction::from_plugin(add_n_to_token())); - m + if flatten {} else {} } - #[allow(non_camel_case_types)] struct add_one_to_token(); impl PluginFunction for add_one_to_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); @@ -445,7 +470,7 @@ mod generate_tests { struct add_n_to_token(); impl PluginFunction for add_n_to_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); @@ -494,19 +519,24 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("add_together", FnAccess::Public, &[core::any::TypeId::of::(), core::any::TypeId::of::()], CallableFunction::from_plugin(add_together_token())); - m + if flatten {} else {} } #[allow(non_camel_case_types)] struct add_together_token(); impl PluginFunction for add_together_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); @@ -556,9 +586,14 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("add", FnAccess::Public, &[core::any::TypeId::of::(), core::any::TypeId::of::()], CallableFunction::from_plugin(add_together_token())); @@ -568,13 +603,13 @@ mod generate_tests { m.set_fn("add_together", FnAccess::Public, &[core::any::TypeId::of::(), core::any::TypeId::of::()], CallableFunction::from_plugin(add_together_token())); - m + if flatten {} else {} } #[allow(non_camel_case_types)] struct add_together_token(); impl PluginFunction for add_together_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); @@ -606,6 +641,43 @@ mod generate_tests { assert_streams_eq(item_mod.generate(), expected_tokens); } + #[test] + fn one_constant_type_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + #[derive(Debug, Clone)] + pub struct Foo(pub INT); + + pub const MYSTIC_NUMBER: Foo = Foo(42); + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + #[derive(Debug, Clone)] + pub struct Foo(pub INT); + + pub const MYSTIC_NUMBER: Foo = Foo(42); + #[allow(unused_imports)] + use super::*; + + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_var("MYSTIC_NUMBER", MYSTIC_NUMBER); + if flatten {} else {} + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + #[test] fn one_constant_module() { let input_tokens: TokenStream = quote! { @@ -619,12 +691,17 @@ mod generate_tests { pub const MYSTIC_NUMBER: INT = 42; #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - m.set_var("MYSTIC_NUMBER", 42); + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_var("MYSTIC_NUMBER", MYSTIC_NUMBER); + if flatten {} else {} + } } }; @@ -647,12 +724,17 @@ mod generate_tests { pub const MYSTIC_NUMBER: INT = 42; #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - m.set_var("MYSTIC_NUMBER", 42); + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_var("MYSTIC_NUMBER", MYSTIC_NUMBER); + if flatten {} else {} + } } }; @@ -677,11 +759,16 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + if flatten {} else {} + } } }; @@ -707,11 +794,16 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + if flatten {} else {} + } } }; @@ -743,18 +835,23 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("get_mystic_number", FnAccess::Public, &[], CallableFunction::from_plugin(get_mystic_number_token())); - m + if flatten {} else {} } #[allow(non_camel_case_types)] struct get_mystic_number_token(); impl PluginFunction for get_mystic_number_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 0usize, "wrong arg count: {} != {}", args.len(), 0usize); @@ -796,11 +893,16 @@ mod generate_tests { const MYSTIC_NUMBER: INT = 42; #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + if flatten {} else {} + } } }; @@ -825,23 +927,28 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("print_out_to", FnAccess::Public, &[core::any::TypeId::of::()], CallableFunction::from_plugin(print_out_to_token())); - m + if flatten {} else {} } #[allow(non_camel_case_types)] struct print_out_to_token(); impl PluginFunction for print_out_to_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> 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]).take_immutable_string().unwrap(); Ok(Dynamic::from(print_out_to(&arg0))) } @@ -867,6 +974,70 @@ mod generate_tests { assert_streams_eq(item_mod.generate(), expected_tokens); } + #[test] + fn one_string_arg_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod str_fn { + pub fn print_out_to(x: String) { + x + 1 + } + } + }; + + let expected_tokens = quote! { + pub mod str_fn { + pub fn print_out_to(x: String) { + x + 1 + } + #[allow(unused_imports)] + use super::*; + + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_fn("print_out_to", FnAccess::Public, + &[core::any::TypeId::of::()], + CallableFunction::from_plugin(print_out_to_token())); + if flatten {} else {} + } + #[allow(non_camel_case_types)] + struct print_out_to_token(); + impl PluginFunction for print_out_to_token { + fn call(&self, + args: &mut [&mut Dynamic] + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).take_string().unwrap(); + Ok(Dynamic::from(print_out_to(arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(print_out_to_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn print_out_to_token_callable() -> CallableFunction { + CallableFunction::from_plugin(print_out_to_token()) + } + pub fn print_out_to_token_input_types() -> Box<[TypeId]> { + print_out_to_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + #[test] fn one_mut_ref_fn_module() { let input_tokens: TokenStream = quote! { @@ -884,19 +1055,24 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("increment", FnAccess::Public, &[core::any::TypeId::of::()], CallableFunction::from_plugin(increment_token())); - m + if flatten {} else {} } #[allow(non_camel_case_types)] struct increment_token(); impl PluginFunction for increment_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); @@ -946,19 +1122,24 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("increment", FnAccess::Public, &[core::any::TypeId::of::()], CallableFunction::from_plugin(increment_token())); - m + if flatten {} else {} } #[allow(non_camel_case_types)] struct increment_token(); impl PluginFunction for increment_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); @@ -984,12 +1165,20 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - { m.set_sub_module("it_is", self::it_is::rhai_module_generate()); } + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + if flatten { + { self::it_is::rhai_generate_into_module(m, flatten); } + } else { + { m.set_sub_module("it_is", self::it_is::rhai_module_generate()); } + } + } } }; @@ -1013,25 +1202,31 @@ mod generate_tests { let expected_tokens = quote! { pub mod one_fn { #[cfg(not(feature = "no_float"))] + pub mod it_is { pub fn increment(x: &mut FLOAT) { *x += 1.0 as FLOAT; } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("increment", FnAccess::Public, &[core::any::TypeId::of::()], CallableFunction::from_plugin(increment_token())); - m + if flatten {} else {} } #[allow(non_camel_case_types)] struct increment_token(); impl PluginFunction for increment_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); @@ -1057,14 +1252,24 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - #[cfg(not(feature = "no_float"))] { - m.set_sub_module("it_is", self::it_is::rhai_module_generate()); - } + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + if flatten { + #[cfg(not(feature = "no_float"))] { + self::it_is::rhai_generate_into_module(m, flatten); + } + } else { + #[cfg(not(feature = "no_float"))] { + m.set_sub_module("it_is", self::it_is::rhai_module_generate()); + } + } + } } }; @@ -1090,18 +1295,23 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("get$square", FnAccess::Public, &[core::any::TypeId::of::()], CallableFunction::from_plugin(int_foo_token())); - m + if flatten {} else {} } #[allow(non_camel_case_types)] struct int_foo_token(); impl PluginFunction for int_foo_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); @@ -1149,20 +1359,25 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("square", FnAccess::Public, &[core::any::TypeId::of::()], CallableFunction::from_plugin(int_foo_token())); m.set_fn("get$square", FnAccess::Public, &[core::any::TypeId::of::()], CallableFunction::from_plugin(int_foo_token())); - m + if flatten {} else {} } #[allow(non_camel_case_types)] struct int_foo_token(); impl PluginFunction for int_foo_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); @@ -1210,20 +1425,25 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("set$squared", FnAccess::Public, &[core::any::TypeId::of::(), core::any::TypeId::of::()], CallableFunction::from_plugin(int_foo_token())); - m + if flatten {} else {} } #[allow(non_camel_case_types)] struct int_foo_token(); impl PluginFunction for int_foo_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); @@ -1272,9 +1492,14 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("set_sq", FnAccess::Public, &[core::any::TypeId::of::(), core::any::TypeId::of::()], @@ -1283,13 +1508,13 @@ mod generate_tests { &[core::any::TypeId::of::(), core::any::TypeId::of::()], CallableFunction::from_plugin(int_foo_token())); - m + if flatten {} else {} } #[allow(non_camel_case_types)] struct int_foo_token(); impl PluginFunction for int_foo_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); @@ -1338,20 +1563,25 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("index$get$", FnAccess::Public, &[core::any::TypeId::of::(), core::any::TypeId::of::()], CallableFunction::from_plugin(get_by_index_token())); - m + if flatten {} else {} } #[allow(non_camel_case_types)] struct get_by_index_token(); impl PluginFunction for get_by_index_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); @@ -1401,9 +1631,14 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("get", FnAccess::Public, &[core::any::TypeId::of::(), core::any::TypeId::of::()], @@ -1412,13 +1647,13 @@ mod generate_tests { &[core::any::TypeId::of::(), core::any::TypeId::of::()], CallableFunction::from_plugin(get_by_index_token())); - m + if flatten {} else {} } #[allow(non_camel_case_types)] struct get_by_index_token(); impl PluginFunction for get_by_index_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); @@ -1468,21 +1703,26 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("index$set$", FnAccess::Public, &[core::any::TypeId::of::(), core::any::TypeId::of::(), core::any::TypeId::of::()], CallableFunction::from_plugin(set_by_index_token())); - m + if flatten {} else {} } #[allow(non_camel_case_types)] struct set_by_index_token(); impl PluginFunction for set_by_index_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 3usize, "wrong arg count: {} != {}", args.len(), 3usize); @@ -1534,9 +1774,14 @@ mod generate_tests { } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { m.set_fn("set", FnAccess::Public, &[core::any::TypeId::of::(), core::any::TypeId::of::(), @@ -1547,13 +1792,13 @@ mod generate_tests { core::any::TypeId::of::(), core::any::TypeId::of::()], CallableFunction::from_plugin(set_by_index_token())); - m + if flatten {} else {} } #[allow(non_camel_case_types)] struct set_by_index_token(); impl PluginFunction for set_by_index_token { fn call(&self, - args: &mut [&mut Dynamic], pos: Position + args: &mut [&mut Dynamic] ) -> Result> { debug_assert_eq!(args.len(), 3usize, "wrong arg count: {} != {}", args.len(), 3usize); @@ -1603,21 +1848,34 @@ mod generate_tests { pub const MYSTIC_NUMBER: INT = 42; #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - m.set_var("MYSTIC_NUMBER", 42); + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_var("MYSTIC_NUMBER", MYSTIC_NUMBER); + if flatten {} else {} + } } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - { m.set_sub_module("it_is", self::it_is::rhai_module_generate()); } + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + if flatten { + { self::it_is::rhai_generate_into_module(m, flatten); } + } else { + { m.set_sub_module("it_is", self::it_is::rhai_module_generate()); } + } + } } }; @@ -1644,33 +1902,52 @@ mod generate_tests { pub const MYSTIC_NUMBER: INT = 42; #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - m.set_var("MYSTIC_NUMBER", 42); + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_var("MYSTIC_NUMBER", MYSTIC_NUMBER); + if flatten {} else {} + } } pub mod second_is { pub const SPECIAL_CPU_NUMBER: INT = 68000; #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - m.set_var("SPECIAL_CPU_NUMBER", 68000); + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_var("SPECIAL_CPU_NUMBER", SPECIAL_CPU_NUMBER); + if flatten {} else {} + } } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - { m.set_sub_module("first_is", self::first_is::rhai_module_generate()); } - { m.set_sub_module("second_is", self::second_is::rhai_module_generate()); } + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + if flatten { + { self::first_is::rhai_generate_into_module(m, flatten); } + { self::second_is::rhai_generate_into_module(m, flatten); } + } else { + { m.set_sub_module("first_is", self::first_is::rhai_module_generate()); } + { m.set_sub_module("second_is", self::second_is::rhai_module_generate()); } + } + } } }; @@ -1721,56 +1998,91 @@ mod generate_tests { pub const VALUE: INT = 2; #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - m.set_var("VALUE", 2); + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_var("VALUE", VALUE); + if flatten {} else {} + } } pub mod right { pub const VALUE: INT = 7; #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - m.set_var("VALUE", 7); + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_var("VALUE", VALUE); + if flatten {} else {} + } } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - m.set_var("VALUE", 17); - { m.set_sub_module("left", self::left::rhai_module_generate()); } - { m.set_sub_module("right", self::right::rhai_module_generate()); } + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_var("VALUE", VALUE); + + if flatten { + { self::left::rhai_generate_into_module(m, flatten); } + { self::right::rhai_generate_into_module(m, flatten); } + } else { + { m.set_sub_module("left", self::left::rhai_module_generate()); } + { m.set_sub_module("right", self::right::rhai_module_generate()); } + } + } } pub mod right { pub const VALUE: INT = 3; #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - m.set_var("VALUE", 3); + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_var("VALUE", VALUE); + if flatten {} else {} + } } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - m.set_var("VALUE", 19); - { m.set_sub_module("left", self::left::rhai_module_generate()); } - { m.set_sub_module("right", self::right::rhai_module_generate()); } + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_var("VALUE", VALUE); + + if flatten { + { self::left::rhai_generate_into_module(m, flatten); } + { self::right::rhai_generate_into_module(m, flatten); } + } else { + { m.set_sub_module("left", self::left::rhai_module_generate()); } + { m.set_sub_module("right", self::right::rhai_module_generate()); } + } + } } pub mod right { pub const VALUE: INT = 36; @@ -1778,45 +2090,75 @@ mod generate_tests { pub const VALUE: INT = 25; #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - m.set_var("VALUE", 25); + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_var("VALUE", VALUE); + if flatten {} else {} + } } pub mod right { pub const VALUE: INT = 1; #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - m.set_var("VALUE", 1); + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_var("VALUE", VALUE); + if flatten {} else {} + } } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - m.set_var("VALUE", 36); - { m.set_sub_module("left", self::left::rhai_module_generate()); } - { m.set_sub_module("right", self::right::rhai_module_generate()); } + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_var("VALUE", VALUE); + + if flatten { + { self::left::rhai_generate_into_module(m, flatten); } + { self::right::rhai_generate_into_module(m, flatten); } + } else { + { m.set_sub_module("left", self::left::rhai_module_generate()); } + { m.set_sub_module("right", self::right::rhai_module_generate()); } + } + } } #[allow(unused_imports)] use super::*; - #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { let mut m = Module::new(); - m.set_var("VALUE", 100); - { m.set_sub_module("left", self::left::rhai_module_generate()); } - { m.set_sub_module("right", self::right::rhai_module_generate()); } + rhai_generate_into_module(&mut m, false); m } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_var("VALUE", VALUE); + + if flatten { + { self::left::rhai_generate_into_module(m, flatten); } + { self::right::rhai_generate_into_module(m, flatten); } + } else { + { m.set_sub_module("left", self::left::rhai_module_generate()); } + { m.set_sub_module("right", self::right::rhai_module_generate()); } + } + } } }; diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index db96844b..23f39757 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -52,7 +52,7 @@ The Rhai Scripting Language 1. [Comments](language/comments.md) 2. [Values and Types](language/values-and-types.md) 1. [Dynamic Values](language/dynamic.md) - 2. [type-of()](language/type-of.md) + 2. [type_of()](language/type-of.md) 3. [Numbers](language/numbers.md) 1. [Operators](language/num-op.md) 2. [Functions](language/num-fn.md) diff --git a/doc/src/engine/dsl.md b/doc/src/engine/dsl.md index 8f326ed1..593a7eb0 100644 --- a/doc/src/engine/dsl.md +++ b/doc/src/engine/dsl.md @@ -69,12 +69,12 @@ For example, the following is a SQL-like syntax for some obscure DSL operation: ```rust let table = [..., ..., ..., ...]; -// Syntax = calculate $ident$ $ident$ from $expr$ -> $ident$ : $expr$ -let total = calculate sum price from table -> row : row.weight > 50; +// Syntax = calculate $ident$ ( $expr$ -> $ident$ ) => $ident$ : $expr$ +let total = calculate sum(table->price) => row : row.weight > 50; // Note: There is nothing special about those symbols; to make it look exactly like SQL: -// Syntax = SELECT $ident$ ( $ident$ ) FROM $expr$ AS $ident$ WHERE $expr$ -let total = SELECT sum(price) FROM table AS row WHERE row.weight > 50; +// Syntax = SELECT $ident$ ( $ident$ ) AS $ident$ FROM $expr$ WHERE $expr$ +let total = SELECT sum(price) AS row FROM table WHERE row.weight > 50; ``` After registering this custom syntax with Rhai, it can be used anywhere inside a script as @@ -84,9 +84,9 @@ For its evaluation, the callback function will receive the following list of inp * `inputs[0] = "sum"` - math operator * `inputs[1] = "price"` - field name -* `inputs[2] = Expression(table)` - data source -* `inputs[3] = "row"` - loop variable name +* `inputs[2] = "row"` - loop variable name +* `inputs[3] = Expression(table)` - data source * `inputs[4] = Expression(row.wright > 50)` - filter predicate -Other identifiers, such as `"calculate"`, `"from"`, as well as symbols such as `->` and `:`, +Other identifiers, such as `"calculate"`, `"FROM"`, as well as symbols such as `->` and `:` etc., are parsed in the order defined within the custom syntax. diff --git a/doc/src/language/arrays.md b/doc/src/language/arrays.md index 69f24103..5c0ce9e9 100644 --- a/doc/src/language/arrays.md +++ b/doc/src/language/arrays.md @@ -45,6 +45,15 @@ The following methods (mostly defined in the [`BasicArrayPackage`][packages] but | `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | +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). + +See the section on [custom types] for more details. + + Examples -------- @@ -123,9 +132,3 @@ y.clear(); // empty the array y.len == 0; ``` - -`push` and `pad` 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: MyType| list.push(Box::new(item)) ); -``` diff --git a/doc/src/language/dynamic.md b/doc/src/language/dynamic.md index 9a3c27b7..7909d162 100644 --- a/doc/src/language/dynamic.md +++ b/doc/src/language/dynamic.md @@ -6,8 +6,8 @@ Dynamic Values A `Dynamic` value can be _any_ type. However, under [`sync`], all types must be `Send + Sync`. -Use [`type_of()`] to Get Value Type ----------------------------------- +Use `type_of()` to Get Value Type +-------------------------------- Because [`type_of()`] a `Dynamic` value returns the type of the actual value, it is usually used to perform type-specific actions based on the actual value's type. diff --git a/doc/src/language/eval.md b/doc/src/language/eval.md index 0af40142..fec9569a 100644 --- a/doc/src/language/eval.md +++ b/doc/src/language/eval.md @@ -65,17 +65,15 @@ disable `eval` by overloading it, probably with something that throws. ```rust fn eval(script) { throw "eval is evil! I refuse to run " + script } -let x = eval("40 + 2"); // 'eval' here throws "eval is evil! I refuse to run 40 + 2" +let x = eval("40 + 2"); // throws "eval is evil! I refuse to run 40 + 2" ``` Or overload it from Rust: ```rust -fn alt_eval(script: String) -> Result<(), Box> { +engine.register_result_fn("eval", |script: String| -> Result<(), Box> { Err(format!("eval is evil! I refuse to run {}", script).into()) -} - -engine.register_result_fn("eval", alt_eval); +}); ``` diff --git a/doc/src/language/for.md b/doc/src/language/for.md index 15ea22e2..493d8759 100644 --- a/doc/src/language/for.md +++ b/doc/src/language/for.md @@ -9,6 +9,9 @@ is provided by the `for` ... `in` loop. Like C, `continue` can be used to skip to the next iteration, by-passing all following statements; `break` can be used to break out of the loop unconditionally. +To loop through a number sequence (with or without steps), use the `range` function to +return a numeric iterator. + ```rust // Iterate through string, yielding characters let s = "hello, world!"; diff --git a/doc/src/language/modules/export.md b/doc/src/language/modules/export.md index 2152dc74..546b5952 100644 --- a/doc/src/language/modules/export.md +++ b/doc/src/language/modules/export.md @@ -20,14 +20,15 @@ Global Variables The `export` statement, which can only be at global level, exposes selected variables as members of a module. -Variables not exported are _private_ and hidden to the outside. +Variables not exported are _private_ and hidden. They are merely used to initialize the module, +but cannot be accessed from outside. Everything exported from a module is **constant** (**read-only**). ```rust // This is a module script. -let private = 123; // variable not exported - default hidden +let hidden = 123; // variable not exported - default hidden let x = 42; // this will be exported below export x; // the variable 'x' is exported under its own name @@ -43,9 +44,6 @@ export x as answer; // the variable 'x' is exported under the alias 'answer' } ``` -[`private`] variables are used to initialize the module. -They cannot be used apart from this. - Functions --------- diff --git a/doc/src/language/num-op.md b/doc/src/language/num-op.md index fa96f1e9..f1db4d46 100644 --- a/doc/src/language/num-op.md +++ b/doc/src/language/num-op.md @@ -10,7 +10,7 @@ Unary Operators | Operator | Description | | -------- | ----------- | -| `+` | Plus | +| `+` | Positive | | `-` | Negative | ```rust diff --git a/doc/src/language/timestamps.md b/doc/src/language/timestamps.md index 47ba302b..8fef7cdd 100644 --- a/doc/src/language/timestamps.md +++ b/doc/src/language/timestamps.md @@ -1,5 +1,5 @@ -`timestamp`'s -============= +`timestamp` +=========== {{#include ../links.md}} diff --git a/doc/src/language/type-of.md b/doc/src/language/type-of.md index a2869539..02ddbc28 100644 --- a/doc/src/language/type-of.md +++ b/doc/src/language/type-of.md @@ -1,5 +1,5 @@ -`type_of` -========= +`type_of()` +=========== {{#include ../links.md}} diff --git a/doc/src/patterns/config.md b/doc/src/patterns/config.md index e2d0db54..c87a0fb3 100644 --- a/doc/src/patterns/config.md +++ b/doc/src/patterns/config.md @@ -137,7 +137,7 @@ For example, the above configuration example may be expressed by this custom syn id "hello"; // Add to list -list +"foo" +list + "foo"; // Add to map map "bar" => true; diff --git a/doc/src/patterns/control.md b/doc/src/patterns/control.md index af6021e6..52b6b23c 100644 --- a/doc/src/patterns/control.md +++ b/doc/src/patterns/control.md @@ -68,19 +68,19 @@ let bunny: Rc> = Rc::new(RefCell::(EnergizerBunny::new() // Notice 'move' is used to move the shared API object into the closure. let b = bunny.clone(); engine.register_fn("bunny_power", move |on: bool| { - if on { - if b.borrow().is_going() { - println!("Still going..."); - } else { - b.borrow_mut().go(); - } + if on { + if b.borrow().is_going() { + println!("Still going..."); } else { - if b.borrow().is_going() { - b.borrow_mut().stop(); - } else { - println!("Already out of battery!"); - } + b.borrow_mut().go(); } + } else { + if b.borrow().is_going() { + b.borrow_mut().stop(); + } else { + println!("Already out of battery!"); + } + } }); let b = bunny.clone(); @@ -88,24 +88,24 @@ engine.register_fn("bunny_is_going", move || b.borrow().is_going()); let b = bunny.clone(); engine.register_fn("bunny_get_speed", move || - if b.borrow().is_going() { b.borrow().get_speed() } else { 0 } + if b.borrow().is_going() { b.borrow().get_speed() } else { 0 } ); let b = bunny.clone(); engine.register_result_fn("bunny_set_speed", move |speed: i64| - if speed <= 0 { - return Err("Speed must be positive!".into()); - } else if speed > 100 { - return Err("Bunny will be going too fast!".into()); - } + if speed <= 0 { + return Err("Speed must be positive!".into()); + } else if speed > 100 { + return Err("Bunny will be going too fast!".into()); + } - if b.borrow().is_going() { - b.borrow_mut().set_speed(speed) - } else { - return Err("Bunny is not yet going!".into()); - } + if b.borrow().is_going() { + b.borrow_mut().set_speed(speed) + } else { + return Err("Bunny is not yet going!".into()); + } - Ok(().into()) + Ok(().into()) ); ``` diff --git a/doc/src/patterns/oop.md b/doc/src/patterns/oop.md index 823df31e..b0e454c6 100644 --- a/doc/src/patterns/oop.md +++ b/doc/src/patterns/oop.md @@ -6,8 +6,8 @@ Object-Oriented Programming (OOP) Rhai does not have _objects_ per se, but it is possible to _simulate_ object-oriented programming. -Use [Object Maps] to Simulate OOP --------------------------------- +Use Object Maps to Simulate OOP +------------------------------ Rhai's [object maps] has [special support for OOP]({{rootUrl}}/language/object-maps-oop.md). @@ -22,8 +22,8 @@ a valid [function pointer] (perhaps defined via an [anonymous function] or more then the call will be dispatched to the actual function with `this` binding to the [object map] itself. -Use Anonymous Functions to Define Methods ----------------------------------------- +Use Closures to Define Methods +----------------------------- [Anonymous functions] or [closures] defined as values for [object map] properties take on a syntactic shape that resembles very closely that of class methods in an OOP language. diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md index 40232140..65bb0014 100644 --- a/doc/src/plugins/module.md +++ b/doc/src/plugins/module.md @@ -5,9 +5,11 @@ Export a Rust Module to Rhai When applied to a Rust module, the `#[export_module]` attribute generates the necessary -code and metadata to allow Rhai access to its public (i.e. marked `pub`) functions. This code -is exactly what would need to be written by hand to achieve the same goal, and is custom fit -to each exported item. +code and metadata to allow Rhai access to its public (i.e. marked `pub`) functions, constants +and sub-modules. + +This code is exactly what would need to be written by hand to achieve the same goal, +and is custom fit to each exported item. This Rust module can then either be loaded into an [`Engine`] as a normal [module] or registered as a [custom package]. This is done by using the `exported_module!` macro. @@ -16,14 +18,21 @@ registered as a [custom package]. This is done by using the `exported_module!` m `#[export_module]` and `exported_module!` ---------------------------------------- -Apply `#[export_module]` onto a Rust module to convert all `pub` functions into Rhai plugin -functions. +Apply `#[export_module]` onto a Rust module to register automatically construct a Rhai [module], +which can then be loaded into an [`Engine`]. + +All `pub` functions become registered functions, all `pub` constants become [module] constant variables, +and all sub-modules become Rhai sub-modules. ```rust use rhai::plugins::*; // a "prelude" import for macros #[export_module] mod my_module { + // This constant will be registered as the constant variable 'SOME_NUMBER'. + // Ignored when loaded as a package. + pub const SOME_NUMBER: i64 = 42; + // This function will be registered as 'greet'. pub fn greet(name: &str) -> String { format!("hello, {}!", name) @@ -36,14 +45,23 @@ mod my_module { pub fn increment(num: &mut i64) { *num += 1; } - // This function is NOT registered. + // This function is not 'pub', so NOT registered. fn mystic_number() -> i64 { 42 } + + // This sub-module is ignored when loaded as a package. + pub mod my_sub_module { + // This function is ignored when loaded as a package. + // Otherwise it is a valid registered function under a sub-module. + pub fn get_info() -> String { + "hello".to_string() + } + } } ``` -In order to load this into an [`Engine`], use the `load_package` method on the exported module: +The simplest way to load this into an [`Engine`] is to use the `load_package` method on the exported module: ```rust fn main() { @@ -52,7 +70,7 @@ fn main() { // The macro call creates the Rhai module. let module = exported_module!(my_module); - // A module can simply be loaded, registering all public its contents. + // A module can simply be loaded, registering all public functions. engine.load_package(module); } ``` @@ -74,9 +92,12 @@ increment(x); x == 43; ``` -Registering this as a custom package is almost the same, except that a module resolver must -point to the module, rather than being loaded directly. See the [module] section for more -information. +Notice that, when using a [module] as a [package], only functions registered at the _top level_ +can be accessed. Variables as well as sub-modules are ignored. + +Using this directly as a Rhai module is almost the same, except that a [module resolver] must +be used to serve the module, and the module is loaded via `import` statements. +See the [module] section for more information. Function Overloading and Operators @@ -88,7 +109,8 @@ attribute to individual functions. The text string given as the `name` parameter to `#[rhai_fn]` is used to register the function with the [`Engine`], disregarding the actual name of the function. -With `#[rhai_fn(name = "...")]`, multiple functions may be registered under the same name in Rhai, so long as they have different parameters. +With `#[rhai_fn(name = "...")]`, multiple functions may be registered under the same name in Rhai, +so long as they have different parameters. Operators (which require function names that are not valid for Rust) can also be registered this way. diff --git a/doc/src/rust/custom.md b/doc/src/rust/custom.md index b2325204..22bea701 100644 --- a/doc/src/rust/custom.md +++ b/doc/src/rust/custom.md @@ -38,12 +38,14 @@ let result = engine.eval::("let x = new_ts(); x.update(); x")?; println!("result: {}", result.field); // prints 42 ``` + Register a Custom Type --------------------- A custom type must implement `Clone` as this allows the [`Engine`] to pass by value. -Notice that the custom type needs to be _registered_ using `Engine::register_type`. +Notice that the custom type needs to be _registered_ using `Engine::register_type` +or `Engine::register_type_with_name`. ```rust #[derive(Clone)] @@ -66,7 +68,8 @@ let mut engine = Engine::new(); engine.register_type::(); ``` -Methods on The Custom Type + +Methods on the Custom Type ------------------------- To use native custom types, methods and functions in Rhai scripts, simply register them @@ -85,6 +88,7 @@ so that invoking methods can update the types. All other parameters in Rhai are **IMPORTANT: Rhai does NOT support normal references (i.e. `&T`) as parameters.** + Use the Custom Type in Scripts ----------------------------- @@ -97,6 +101,7 @@ let result = engine.eval::("let x = new_ts(); x.update(); x")?; println!("result: {}", result.field); // prints 42 ``` + Method-Call Style vs. Function-Call Style ---------------------------------------- @@ -127,6 +132,7 @@ Under [`no_object`], however, the _method_ style of function calls let result = engine.eval::<()>("let x = [1, 2, 3]; x.clear()")?; ``` + `type_of()` a Custom Type ------------------------- @@ -150,3 +156,33 @@ engine let x = new_ts(); 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: + +```rust +engine + .register_fn("push", |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); + } + }); +``` + +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: + +```rust +// Assume 'TestStruct' implements `PartialEq` +engine.register_fn("==", |item1: &mut TestStruct, item2: TestStruct| item1 == item2); + +// Then this works in Rhai: +let item = new_ts(); // construct a new 'TestStruct' +item in array; // 'in' operator uses '==' +``` diff --git a/doc/src/rust/disable-custom.md b/doc/src/rust/disable-custom.md index a2c69c95..e5ad8a82 100644 --- a/doc/src/rust/disable-custom.md +++ b/doc/src/rust/disable-custom.md @@ -3,8 +3,8 @@ Disable Custom Types {{#include ../links.md}} -The custom types API `register_type`, `register_type_with_name`, `register_get`, `register_set`, `register_get_set`, -`register_indexer_get`, `register_indexer_set` and `register_indexer_get_set` are not available under [`no_object`]. +The custom types API `register_type`, `register_type_with_name`, `register_get`, `register_get_result`, +`register_set`, `register_set_result` and `register_get_set` are not available under [`no_object`]. -The indexers API `register_indexer_get`, `register_indexer_set` and `register_indexer_get_set` are also -not available under [`no_index`]. +The indexers API `register_indexer_get`, `register_indexer_get_result`, `register_indexer_set`, +`register_indexer_set_result`, and `register_indexer_get_set` are also not available under [`no_index`]. diff --git a/doc/src/rust/getters-setters.md b/doc/src/rust/getters-setters.md index 7597e8d5..6f6184ce 100644 --- a/doc/src/rust/getters-setters.md +++ b/doc/src/rust/getters-setters.md @@ -7,6 +7,20 @@ A custom type can also expose members by registering `get` and/or `set` function Getters and setters each take a `&mut` reference to the first parameter. +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_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>` | + + +Examples +-------- + ```rust #[derive(Clone)] struct TestStruct { diff --git a/doc/src/rust/indexers.md b/doc/src/rust/indexers.md index 94a6b5f1..107ed5c9 100644 --- a/doc/src/rust/indexers.md +++ b/doc/src/rust/indexers.md @@ -16,6 +16,18 @@ 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_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>` | + + +Examples +-------- + ```rust #[derive(Clone)] struct TestStruct { @@ -40,7 +52,7 @@ let mut engine = Engine::new(); engine .register_type::() .register_fn("new_ts", TestStruct::new) - // Shorthand: .register_indexer_get_set(TestStruct::get_field, TestStruct::set_field); + // Short-hand: .register_indexer_get_set(TestStruct::get_field, TestStruct::set_field); .register_indexer_get(TestStruct::get_field) .register_indexer_set(TestStruct::set_field); diff --git a/doc/src/rust/modules/create.md b/doc/src/rust/modules/create.md index 26441067..d2f68e54 100644 --- a/doc/src/rust/modules/create.md +++ b/doc/src/rust/modules/create.md @@ -7,7 +7,8 @@ Create a Module from Rust Create via Plugin ----------------- -By far the simplest way to create a [module] is via a [plugin module]. +By far the simplest way to create a [module] is via a [plugin module] +which converts a normal Rust module into a Rhai [module] via procedural macros. Create via `Module` API @@ -21,7 +22,8 @@ For the complete `Module` API, refer to the [documentation](https://docs.rs/rhai Make the `Module` Available to the `Engine` ------------------------------------------ -In order to _use_ a custom module, there must be a [module resolver]. +In order to _use_ a custom module, there must be a [module resolver], which serves the module when +loaded via `import` statements. The easiest way is to use, for example, the [`StaticModuleResolver`][module resolver] to hold such a custom module. diff --git a/doc/src/rust/packages/create.md b/doc/src/rust/packages/create.md index d212d38f..01d11b12 100644 --- a/doc/src/rust/packages/create.md +++ b/doc/src/rust/packages/create.md @@ -58,13 +58,16 @@ def_package!(rhai:MyPackage:"My own personal super package", module, { Create a Custom Package from a Plugin Module ------------------------------------------- -By far the easiest way to create a custom module is to call `Module::merge_flatten` from within -`rhai::def_package!` which simply merges in all the functions defined within a [plugin module]. +By far the easiest way to create a custom module is to call `rhai::plugins::combine_with_exported_module!` +from within `rhai::def_package!` which simply merges in all the functions defined within a [plugin module]. In fact, this exactly is how Rhai's built-in packages, such as `BasicMathPackage`, are implemented. -`rhai::plugins::exported_module!` generates a module from the [plugins][plugin module] definition, -and `Module::merge_flatten` consumes its, adding all its registered functions into the package itself. +Because of the specific requirements of a [package], all sub-modules are _flattened_ +(i.e. all functions defined within sub-modules are pulled up and registered at the top level instead) +and so there will not be any sub-modules added to the package. + +Variables in the [plugin module] are ignored. ```rust // Import necessary types and traits. @@ -78,12 +81,22 @@ use rhai::plugin::*; // Define plugin module. #[export_module] mod my_module { + pub const MY_NUMBER: i64 = 42; + pub fn greet(name: &str) -> String { format!("hello, {}!", name) } pub fn get_num() -> i64 { 42 } + + // This is a sub-module, but if using combine_with_exported_module!, it will + // be flattened and all functions registered at the top level. + pub mod my_sub_module { + pub fn get_sub_num() -> i64 { + 0 + } + } } // Define the package 'MyPackage'. @@ -94,7 +107,20 @@ def_package!(rhai:MyPackage:"My own personal super package", module, { BasicArrayPackage::init(module); BasicMapPackage::init(module); - // Merge the plugin module into the custom package. - module.merge_flatten(exported_module!(my_module)); + // Merge all registered functions and constants from the plugin module into the custom package. + // + // The sub-module 'my_sub_module' is flattened and its functions registered at the top level. + // + // The text string name in the middle parameter can be anything and is reserved for future use; + // it is recommended to be an ID string that uniquely identifies the module. + // + // The constant variable, 'MY_NUMBER', is ignored. + // + // This call ends up registering three functions at the top level of the package: + // 1) greet + // 2) get_num + // 3) get_sub_num (pulled up from 'my_sub_module') + // + combine_with_exported_module!(module, "my-functions", my_module)); }); ``` diff --git a/doc/src/rust/packages/index.md b/doc/src/rust/packages/index.md index ee0daa37..f2e6b598 100644 --- a/doc/src/rust/packages/index.md +++ b/doc/src/rust/packages/index.md @@ -29,7 +29,7 @@ engine.load_package(package.get()); // load the package manually. 'get' returns Difference Between a Package and a Module ---------------------------------------- -Packages are actually implemented as [modules], so they share a lot of behavior and characteristics. +Packages are internally implemented as [modules], so they share a lot of behavior and characteristics. The main difference is that a package loads under the _global_ namespace, while a module loads under its own namespace alias specified in an [`import`] statement (see also [modules]). @@ -37,6 +37,11 @@ namespace alias specified in an [`import`] statement (see also [modules]). A package is _static_ (i.e. pre-loaded into an [`Engine`]), while a module is _dynamic_ (i.e. loaded with the `import` statement). +Sub-modules in a package are _flattened_, meaning that functions from them must be pulled up to the root level. +In other words, there can be no sub-modules in a package, and everything should reside in one, flat namespace. + +Only functions matter for a package. Constant variables registered in a package are ignored. + Load a Module as a Package -------------------------- diff --git a/doc/src/start/builds/performance.md b/doc/src/start/builds/performance.md index a0c65fe5..78ca762e 100644 --- a/doc/src/start/builds/performance.md +++ b/doc/src/start/builds/performance.md @@ -13,23 +13,26 @@ If only a single integer type is needed in scripts - most of the time this is th lots of functions related to other integer types that will never be used. As a result, [`Engine`] creation will be faster because fewer functions need to be loaded. +The [`only_i32`] and [`only_i64`] features disable all integer types except `i32` or `i64` respectively. + Use Only 32-Bit Numbers ---------------------- -If only 32-bit integers are needed - again, most of the time this is the case - using [`only_i32`] disables also `i64`. +If only 32-bit integers are needed - again, most of the time this is the case - turn on [`only_i32`]. +Under this feature, only `i32` is supported as a built-in integer type and no others. -On 64-bit targets this may not gain much, but on some 32-bit targets this improves performance due to 64-bit arithmetic -requiring more CPU cycles to complete. +On 64-bit targets this may not gain much, but on certain 32-bit targets this improves performance +due to 64-bit arithmetic requiring more CPU cycles to complete. Minimize Size of `Dynamic` ------------------------- -Turning on [`no_float`], and [`only_i32`] makes the key [`Dynamic`] data type only 8 bytes small on 32-bit targets -while normally it can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`. +Turning on [`no_float`] and [`only_i32`] on 32-bit targets makes the critical [`Dynamic`] data type only 8 bytes long. +Normally [`Dynamic`] can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`. -Making [`Dynamic`] small helps performance due to better cache efficiency. +A small [`Dynamic`] helps performance due to better cache efficiency. Use `ImmutableString` @@ -41,17 +44,17 @@ cloning when passing function arguments. Rhai's internal string type is `ImmutableString` (basically `Rc` or `Arc` depending on the [`sync`] feature). It is cheap to clone, but expensive to modify (a new copy of the string must be made in order to change it). -Therefore, functions taking `String` parameters should use `ImmutableString` or `&str` (which maps to `ImmutableString`) +Therefore, functions taking `String` parameters should use `ImmutableString` or `&str` (both map to `ImmutableString`) for the best performance with Rhai. Disable Closures ---------------- -Support for [closures], including capturing shared values, adds material overhead to script evaluation. +Support for [closures] that capture shared variables adds material overhead to script evaluation. -This is because every data access must be checked whether it is a shared value, and if so, take a read -or write lock before reading it. +This is because every data access must be checked whether it is a shared value and, if so, take a read +lock before reading it. -Use [`no_closure`] to disable closure and capturing support and optimize the hot path +Use [`no_closure`] to disable closure and capturing support to optimize the hot path because there is no need to take locks for shared data. diff --git a/doc/src/start/features.md b/doc/src/start/features.md index 3e8067fa..9cd86f52 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -11,23 +11,23 @@ Notice that this deviates from Rust norm where features are _additive_. Excluding unneeded functionalities can result in smaller, faster builds as well as more control over what a script can (or cannot) do. -| Feature | Description | -| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `unchecked` | Disable arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.
Beware that a bad script may panic the entire system! | -| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync`. | -| `no_optimize` | Disable [script optimization]. | -| `no_float` | Disable floating-point numbers and math. | -| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | -| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | -| `no_index` | Disable [arrays] and indexing features. | -| `no_object` | Disable support for [custom types] and [object maps]. | -| `no_function` | Disable script-defined [functions]. | -| `no_module` | Disable loading external [modules]. | -| `no_closure` | Disable [capturing][automatic currying] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. | -| `no_std` | Build for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features. | -| `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | -| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | -| `unicode-xid-ident` | Allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers. | +| Feature | Additive? | Description | +| ------------------- | :-------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `unchecked` | No | Disable arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.
Beware that a bad script may panic the entire system! | +| `sync` | No | Restrict all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync`. | +| `no_optimize` | No | Disable [script optimization]. | +| `no_float` | No | Disable floating-point numbers and math. | +| `only_i32` | No | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | +| `only_i64` | No | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | +| `no_index` | No | Disable [arrays] and indexing features. | +| `no_object` | No | Disable support for [custom types] and [object maps]. | +| `no_function` | No | Disable script-defined [functions]. | +| `no_module` | No | Disable loading external [modules]. | +| `no_closure` | No | Disable [capturing][automatic currying] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. | +| `no_std` | No | Build for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features. | +| `serde` | Yes | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | +| `internals` | Yes | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | +| `unicode-xid-ident` | No | Allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers. | Example @@ -57,7 +57,7 @@ This configuration is perfect for an expression parser in a 32-bit embedded syst Caveat - Features Are Not Additive --------------------------------- -Rhai features are not strictly _additive_ - i.e. they do not only add optional functionalities. +Most Rhai features are not strictly _additive_ - i.e. they do not only add optional functionalities. In fact, most features are _subtractive_ - i.e. they _remove_ functionalities. diff --git a/src/any.rs b/src/any.rs index 7b1fa184..a1e532aa 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1136,6 +1136,7 @@ impl Dynamic { } /// Convert the `Dynamic` into `String` and return it. + /// If there are other references to the same string, a cloned copy is returned. /// Returns the name of the actual type if the cast fails. #[inline(always)] pub fn take_string(self) -> Result { @@ -1145,7 +1146,8 @@ impl Dynamic { /// Convert the `Dynamic` into `ImmutableString` and return it. /// Returns the name of the actual type if the cast fails. - pub(crate) fn take_immutable_string(self) -> Result { + #[inline] + pub fn take_immutable_string(self) -> Result { match self.0 { Union::Str(s) => Ok(s), Union::FnPtr(f) => Ok(f.take_data().0), diff --git a/src/api.rs b/src/api.rs index 899b1ab6..b2cc4b31 100644 --- a/src/api.rs +++ b/src/api.rs @@ -12,17 +12,18 @@ use crate::scope::Scope; use crate::token::{lex, Position}; #[cfg(not(feature = "no_index"))] -#[cfg(not(feature = "no_object"))] use crate::engine::{FN_IDX_GET, FN_IDX_SET}; #[cfg(not(feature = "no_object"))] use crate::{ engine::{make_getter, make_setter, Map}, error::ParseErrorType, - fn_register::{RegisterFn, RegisterResultFn}, token::Token, }; +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +use crate::fn_register::{RegisterFn, RegisterResultFn}; + #[cfg(not(feature = "no_function"))] use crate::{ engine::get_script_function_by_signature, fn_args::FuncArgs, fn_call::ensure_no_data_race, @@ -375,7 +376,7 @@ impl Engine { self.register_result_fn(&make_setter(name), callback) } - /// Shorthand for registering both getter and setter functions + /// Short-hand for registering both getter and setter functions /// of a registered type with the `Engine`. /// /// All function signatures must start with `&mut self` and not `&self`. @@ -427,7 +428,7 @@ impl Engine { self.register_get(name, get_fn).register_set(name, set_fn) } - /// Register an index getter for a registered type with the `Engine`. + /// Register an index getter for a custom type with the `Engine`. /// /// The function signature must start with `&mut self` and not `&self`. /// @@ -452,6 +453,7 @@ impl Engine { /// let mut engine = Engine::new(); /// /// // Register the custom type. + /// # #[cfg(not(feature = "no_object"))] /// engine.register_type::(); /// /// engine.register_fn("new_ts", TestStruct::new); @@ -463,7 +465,6 @@ impl Engine { /// # Ok(()) /// # } /// ``` - #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] pub fn register_indexer_get( &mut self, @@ -477,7 +478,7 @@ impl Engine { self.register_fn(FN_IDX_GET, callback) } - /// Register an index getter for a registered type with the `Engine`. + /// Register an index getter for a custom type with the `Engine`. /// Returns `Result>`. /// /// The function signature must start with `&mut self` and not `&self`. @@ -505,6 +506,7 @@ impl Engine { /// let mut engine = Engine::new(); /// /// // Register the custom type. + /// # #[cfg(not(feature = "no_object"))] /// engine.register_type::(); /// /// engine.register_fn("new_ts", TestStruct::new); @@ -516,7 +518,6 @@ impl Engine { /// # Ok(()) /// # } /// ``` - #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] pub fn register_indexer_get_result( &mut self, @@ -529,7 +530,7 @@ impl Engine { self.register_result_fn(FN_IDX_GET, callback) } - /// Register an index setter for a registered type with the `Engine`. + /// Register an index setter for a custom type with the `Engine`. /// /// # Example /// @@ -550,6 +551,7 @@ impl Engine { /// let mut engine = Engine::new(); /// /// // Register the custom type. + /// # #[cfg(not(feature = "no_object"))] /// engine.register_type::(); /// /// engine.register_fn("new_ts", TestStruct::new); @@ -564,7 +566,6 @@ impl Engine { /// # Ok(()) /// # } /// ``` - #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] pub fn register_indexer_set( &mut self, @@ -578,7 +579,7 @@ impl Engine { self.register_fn(FN_IDX_SET, callback) } - /// Register an index setter for a registered type with the `Engine`. + /// Register an index setter for a custom type with the `Engine`. /// Returns `Result>`. /// /// # Example @@ -603,6 +604,7 @@ impl Engine { /// let mut engine = Engine::new(); /// /// // Register the custom type. + /// # #[cfg(not(feature = "no_object"))] /// engine.register_type::(); /// /// engine.register_fn("new_ts", TestStruct::new); @@ -617,7 +619,6 @@ impl Engine { /// # Ok(()) /// # } /// ``` - #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] pub fn register_indexer_set_result( &mut self, @@ -631,7 +632,7 @@ impl Engine { self.register_result_fn(FN_IDX_SET, callback) } - /// Shorthand for register both index getter and setter functions for a registered type with the `Engine`. + /// Short-hand for register both index getter and setter functions for a custom type with the `Engine`. /// /// # Example /// @@ -653,6 +654,7 @@ impl Engine { /// let mut engine = Engine::new(); /// /// // Register the custom type. + /// # #[cfg(not(feature = "no_object"))] /// engine.register_type::(); /// /// engine.register_fn("new_ts", TestStruct::new); @@ -664,7 +666,6 @@ impl Engine { /// # Ok(()) /// # } /// ``` - #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] pub fn register_indexer_get_set( &mut self, diff --git a/src/engine.rs b/src/engine.rs index 66c4de23..ad5b1c0a 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1134,8 +1134,7 @@ impl Engine { ) -> Result, Box> { self.inc_operations(state)?; - #[cfg(not(feature = "no_index"))] - #[cfg(not(feature = "no_object"))] + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] let is_ref = target.is_ref(); let val = target.as_mut(); @@ -1200,7 +1199,6 @@ impl Engine { } } - #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] _ if _indexers => { let type_name = val.type_name(); diff --git a/src/fn_call.rs b/src/fn_call.rs index 3728d4e8..c40d352f 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -232,7 +232,7 @@ impl Engine { // Run external function let result = if func.is_plugin_fn() { - func.get_plugin_fn().call(args, Position::none()) + func.get_plugin_fn().call(args) } else { func.get_native_fn()(self, lib, args) }; @@ -1099,7 +1099,7 @@ impl Engine { self.call_script_fn(scope, mods, state, lib, &mut None, name, func, args, level) } - Some(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut(), Position::none()), + Some(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut()), Some(f) if f.is_native() => { if !f.is_method() { // Clone first argument diff --git a/src/fn_register.rs b/src/fn_register.rs index 28d33198..12aedad2 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -46,7 +46,7 @@ pub trait RegisterPlugin { /// fn is_method_call(&self) -> bool { false } /// fn is_varadic(&self) -> bool { false } /// - /// fn call(&self, args: &mut[&mut Dynamic], pos: Position) -> Result> { + /// fn call(&self, args: &mut[&mut Dynamic]) -> Result> { /// let x1: NUMBER = std::mem::take(args[0]).clone().cast::(); /// let y1: NUMBER = std::mem::take(args[1]).clone().cast::(); /// let x2: NUMBER = std::mem::take(args[2]).clone().cast::(); diff --git a/src/module.rs b/src/module.rs index 4d70b169..51bfc3bf 100644 --- a/src/module.rs +++ b/src/module.rs @@ -4,10 +4,11 @@ use crate::any::{Dynamic, Variant}; use crate::calc_fn_hash; use crate::engine::Engine; use crate::fn_native::{CallableFunction as Func, FnCallArgs, IteratorFn, SendSync}; +use crate::fn_register::by_value as cast_arg; use crate::parser::{FnAccess, FnAccess::Public, ScriptFnDef}; use crate::result::EvalAltResult; use crate::token::{Position, Token}; -use crate::utils::{StaticVec, StraightHasherBuilder}; +use crate::utils::{ImmutableString, StaticVec, StraightHasherBuilder}; #[cfg(not(feature = "no_function"))] use crate::fn_native::Shared; @@ -32,7 +33,6 @@ use crate::stdlib::{ collections::HashMap, fmt, format, iter::empty, - mem, num::NonZeroUsize, ops::{Deref, DerefMut}, string::{String, ToString}, @@ -396,9 +396,21 @@ impl Module { arg_types.len() }; - let hash_fn = calc_fn_hash(empty(), &name, args_len, arg_types.iter().cloned()); + let params = arg_types + .into_iter() + .cloned() + .map(|id| { + if id == TypeId::of::<&str>() { + TypeId::of::() + } else if id == TypeId::of::() { + TypeId::of::() + } else { + id + } + }) + .collect(); - let params = arg_types.into_iter().cloned().collect(); + let hash_fn = calc_fn_hash(empty(), &name, args_len, arg_types.iter().cloned()); self.functions .insert(hash_fn, (name, access, params, func.into())); @@ -518,7 +530,7 @@ impl Module { func: impl Fn(A) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { - func(mem::take(args[0]).cast::()).map(Dynamic::from) + func(cast_arg::(&mut args[0])).map(Dynamic::from) }; let arg_types = [TypeId::of::()]; self.set_fn(name, Public, &arg_types, Func::from_pure(Box::new(f))) @@ -592,8 +604,8 @@ impl Module { func: impl Fn(A, B) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { - let a = mem::take(args[0]).cast::(); - let b = mem::take(args[1]).cast::(); + let a = cast_arg::(&mut args[0]); + let b = cast_arg::(&mut args[1]); func(a, b).map(Dynamic::from) }; @@ -623,7 +635,7 @@ impl Module { func: impl Fn(&mut A, B) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { - let b = mem::take(args[1]).cast::(); + let b = cast_arg::(&mut args[1]); let a = &mut args[0].write_lock::().unwrap(); func(a, b).map(Dynamic::from) @@ -709,9 +721,9 @@ impl Module { func: impl Fn(A, B, C) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { - let a = mem::take(args[0]).cast::(); - let b = mem::take(args[1]).cast::(); - let c = mem::take(args[2]).cast::(); + let a = cast_arg::(&mut args[0]); + let b = cast_arg::(&mut args[1]); + let c = cast_arg::(&mut args[2]); func(a, b, c).map(Dynamic::from) }; @@ -746,8 +758,8 @@ impl Module { func: impl Fn(&mut A, B, C) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { - let b = mem::take(args[1]).cast::(); - let c = mem::take(args[2]).cast::(); + let b = cast_arg::(&mut args[2]); + let c = cast_arg::(&mut args[3]); let a = &mut args[0].write_lock::().unwrap(); func(a, b, c).map(Dynamic::from) @@ -780,8 +792,8 @@ impl Module { func: impl Fn(&mut A, B, C) -> FuncReturn<()> + SendSync + 'static, ) -> u64 { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { - let b = mem::take(args[1]).cast::(); - let c = mem::take(args[2]).cast::(); + let b = cast_arg::(&mut args[1]); + let c = cast_arg::(&mut args[2]); let a = &mut args[0].write_lock::().unwrap(); func(a, b, c).map(Dynamic::from) @@ -796,7 +808,7 @@ impl Module { } /// Set a pair of Rust index getter and setter functions, returning both hash keys. - /// This is a shorthand for `set_indexer_get_fn` and `set_indexer_set_fn`. + /// This is a short-hand for `set_indexer_get_fn` and `set_indexer_set_fn`. /// /// If there are similar existing Rust functions, they are replaced. /// @@ -858,10 +870,10 @@ impl Module { func: impl Fn(A, B, C, D) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { - let a = mem::take(args[0]).cast::(); - let b = mem::take(args[1]).cast::(); - let c = mem::take(args[2]).cast::(); - let d = mem::take(args[3]).cast::(); + let a = cast_arg::(&mut args[0]); + let b = cast_arg::(&mut args[1]); + let c = cast_arg::(&mut args[2]); + let d = cast_arg::(&mut args[3]); func(a, b, c, d).map(Dynamic::from) }; @@ -902,9 +914,9 @@ impl Module { func: impl Fn(&mut A, B, C, D) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { - let b = mem::take(args[1]).cast::(); - let c = mem::take(args[2]).cast::(); - let d = mem::take(args[3]).cast::(); + let b = cast_arg::(&mut args[1]); + let c = cast_arg::(&mut args[2]); + let d = cast_arg::(&mut args[3]); let a = &mut args[0].write_lock::().unwrap(); func(a, b, c, d).map(Dynamic::from) diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index d72e2e8b..f963e778 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -185,7 +185,7 @@ macro_rules! gen_signed_functions { macro_rules! reg_functions { ($mod_name:ident += $root:ident ; $($arg_type:ident),+ ) => { $( - $mod_name.combine_flatten(exported_module!($root::$arg_type::functions)); + combine_with_exported_module!($mod_name, "arithmetic", $root::$arg_type::functions); )* } } @@ -208,8 +208,8 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { // Basic arithmetic for floating-point #[cfg(not(feature = "no_float"))] { - lib.combine_flatten(exported_module!(f32_functions)); - lib.combine_flatten(exported_module!(f64_functions)); + combine_with_exported_module!(lib, "f32", f32_functions); + combine_with_exported_module!(lib, "f64", f64_functions); } }); diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 767c4c5f..05599eac 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -58,7 +58,7 @@ macro_rules! reg_functions { } def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { - lib.combine_flatten(exported_module!(array_functions)); + combine_with_exported_module!(lib, "array", array_functions); reg_functions!(lib += basic; INT, bool, char, ImmutableString, FnPtr, Array, Unit); diff --git a/src/packages/eval.rs b/src/packages/eval.rs index acebf216..bb5716d8 100644 --- a/src/packages/eval.rs +++ b/src/packages/eval.rs @@ -5,7 +5,7 @@ use crate::plugin::*; use crate::result::EvalAltResult; def_package!(crate:EvalPackage:"Disable 'eval'.", lib, { - lib.combine_flatten(exported_module!(eval_override)); + combine_with_exported_module!(lib, "eval", eval_override); }); #[export_module] diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 61d18371..5ffb6cb1 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -3,7 +3,7 @@ use crate::fn_native::FnPtr; use crate::plugin::*; def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, { - lib.combine_flatten(exported_module!(fn_ptr_functions)); + combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions); }); #[export_module] diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 48e46047..554f30e3 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -45,7 +45,7 @@ macro_rules! gen_cmp_functions { macro_rules! reg_functions { ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( - $mod_name.combine_flatten(exported_module!($root::$arg_type::functions)); + combine_with_exported_module!($mod_name, "logic", $root::$arg_type::functions); )* } } diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index a5983268..fde57db7 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -9,7 +9,7 @@ use crate::plugin::*; use crate::stdlib::vec::Vec; def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { - lib.combine_flatten(exported_module!(map_functions)); + combine_with_exported_module!(lib, "map", map_functions); }); #[export_module] diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index ddbb640c..2fd2a9c4 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -48,10 +48,10 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { #[cfg(not(feature = "no_float"))] { // Floating point functions - lib.combine_flatten(exported_module!(float_functions)); + combine_with_exported_module!(lib, "float", float_functions); // Trig functions - lib.combine_flatten(exported_module!(trig_functions)); + combine_with_exported_module!(lib, "trig", trig_functions); reg_functions!(lib += basic_to_float::to_float(INT)); diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 5dfd4897..55fa4be0 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -57,7 +57,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str #[cfg(not(feature = "no_float"))] reg_functions!(lib += float; f32, f64); - lib.combine_flatten(exported_module!(string_functions)); + combine_with_exported_module!(lib, "string", string_functions); lib.set_raw_fn( "pad", diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 80ba2637..caa5fc47 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -21,7 +21,7 @@ use instant::Instant; def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { // Register date/time functions - lib.combine_flatten(exported_module!(time_functions)); + combine_with_exported_module!(lib, "time", time_functions); }); #[export_module] diff --git a/src/plugin.rs b/src/plugin.rs index ef4a6590..e6e4e956 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -3,7 +3,7 @@ pub use crate::{ fn_native::CallableFunction, stdlib::any::TypeId, stdlib::boxed::Box, stdlib::format, stdlib::mem, stdlib::string::ToString, stdlib::vec as new_vec, stdlib::vec::Vec, Dynamic, - Engine, EvalAltResult, FnAccess, ImmutableString, Module, Position, RegisterResultFn, + Engine, EvalAltResult, FnAccess, ImmutableString, Module, RegisterResultFn, }; #[cfg(not(features = "no_module"))] @@ -34,8 +34,7 @@ pub trait PluginFunction { fn is_method_call(&self) -> bool; fn is_varadic(&self) -> bool; - fn call(&self, args: &mut [&mut Dynamic], pos: Position) - -> Result>; + fn call(&self, args: &mut [&mut Dynamic]) -> Result>; fn clone_boxed(&self) -> Box; diff --git a/tests/modules.rs b/tests/modules.rs index 0744fd8c..e3475584 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -1,7 +1,7 @@ #![cfg(not(feature = "no_module"))] use rhai::{ - module_resolvers::StaticModuleResolver, Dynamic, Engine, EvalAltResult, Module, ParseError, - ParseErrorType, Scope, INT, + module_resolvers::StaticModuleResolver, Dynamic, Engine, EvalAltResult, ImmutableString, + Module, ParseError, ParseErrorType, Scope, INT, }; #[test] @@ -79,12 +79,12 @@ fn test_module_resolver() -> Result<(), Box> { }); #[cfg(not(feature = "no_float"))] - module.set_fn_4_mut( + module.set_fn_4_mut( "sum_of_three_args".to_string(), |target: &mut INT, a: INT, b: INT, c: f64| { *target = a + b + c as INT; Ok(()) - } + }, ); resolver.insert("hello", module); @@ -316,3 +316,41 @@ fn test_module_export() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_module_str() -> Result<(), Box> { + fn test_fn(_input: ImmutableString) -> Result> { + Ok(42) + } + fn test_fn2(_input: &str) -> Result> { + Ok(42) + } + fn test_fn3(_input: String) -> Result> { + Ok(42) + } + + let mut engine = rhai::Engine::new(); + let mut module = Module::new(); + module.set_fn_1("test", test_fn); + module.set_fn_1("test2", test_fn2); + module.set_fn_1("test3", test_fn3); + + let mut static_modules = rhai::module_resolvers::StaticModuleResolver::new(); + static_modules.insert("test", module); + engine.set_module_resolver(Some(static_modules)); + + assert_eq!( + engine.eval::(r#"import "test" as test; test::test("test");"#)?, + 42 + ); + assert_eq!( + engine.eval::(r#"import "test" as test; test::test2("test");"#)?, + 42 + ); + assert_eq!( + engine.eval::(r#"import "test" as test; test::test3("test");"#)?, + 42 + ); + + Ok(()) +} diff --git a/tests/plugins.rs b/tests/plugins.rs index cac8718f..909ad51b 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -1,5 +1,6 @@ #![cfg(not(any(feature = "no_index", feature = "no_module")))] +use rhai::module_resolvers::StaticModuleResolver; use rhai::plugin::*; use rhai::{Engine, EvalAltResult, INT}; @@ -10,6 +11,8 @@ mod test { pub mod special_array_package { use rhai::{Array, INT}; + pub const MYSTIC_NUMBER: INT = 42; + #[cfg(not(feature = "no_object"))] pub mod feature { use rhai::{Array, Dynamic, EvalAltResult}; @@ -21,6 +24,13 @@ mod test { } } + pub fn hash(_text: String) -> INT { + 42 + } + pub fn hash2(_text: &str) -> INT { + 42 + } + #[rhai_fn(name = "test", name = "hi")] #[inline(always)] pub fn len(array: &mut Array, mul: INT) -> INT { @@ -66,7 +76,7 @@ fn test_plugins_package() -> Result<(), Box> { let mut engine = Engine::new(); let mut m = Module::new(); - m.combine_flatten(exported_module!(test::special_array_package)); + combine_with_exported_module!(&mut m, "test", test::special_array_package); engine.load_package(m); reg_functions!(engine += greet::single(INT, bool, char)); @@ -74,6 +84,8 @@ fn test_plugins_package() -> Result<(), Box> { #[cfg(not(feature = "no_object"))] assert_eq!(engine.eval::("let a = [1, 2, 3]; a.foo")?, 1); + assert_eq!(engine.eval::(r#"hash("hello")"#)?, 42); + assert_eq!(engine.eval::(r#"hash2("hello")"#)?, 42); assert_eq!(engine.eval::("let a = [1, 2, 3]; test(a, 2)")?, 6); assert_eq!(engine.eval::("let a = [1, 2, 3]; hi(a, 2)")?, 6); assert_eq!(engine.eval::("let a = [1, 2, 3]; test(a, 2)")?, 6); @@ -83,5 +95,14 @@ fn test_plugins_package() -> Result<(), Box> { "6 kitties" ); + let mut resolver = StaticModuleResolver::new(); + resolver.insert("test", exported_module!(test::special_array_package)); + + engine.set_module_resolver(Some(resolver)); + assert_eq!( + engine.eval::(r#"import "test" as test; test::MYSTIC_NUMBER"#)?, + 42 + ); + Ok(()) }