diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c5295c74..b5465c05 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - plugins pull_request: {} jobs: @@ -76,3 +77,25 @@ jobs: with: command: build args: --manifest-path=no_std/no_std_test/Cargo.toml ${{matrix.flags}} + codegen_build: + name: Codegen Build + runs-on: ${{matrix.os}} + continue-on-error: ${{matrix.experimental}} + strategy: + matrix: + include: + - {toolchain: nightly, os: ubuntu-latest, experimental: false, flags: ""} + - {toolchain: nightly, os: windows-latest, experimental: false, flags: ""} + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{matrix.toolchain}} + override: true + - name: Build Project + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path=codegen/Cargo.toml ${{matrix.flags}} diff --git a/Cargo.toml b/Cargo.toml index 25305837..83a2d021 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,8 @@ keywords = [ "scripting" ] categories = [ "no-std", "embedded", "wasm", "parser-implementations" ] [dependencies] -num-traits = { version = "0.2.11", default-features = false } smallvec = { version = "1.4.1", default-features = false } +rhai_codegen = { version = "0.1", path = "codegen" } [features] #default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"] @@ -51,6 +51,11 @@ version = "0.2.1" default_features = false optional = true +[dependencies.num-traits] +version = "0.2.11" +default-features = false +optional = true + [dependencies.core-error] version = "0.0.0" default_features = false diff --git a/README.md b/README.md index bfd3fc0c..fbd2f3d2 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Supported targets and builds * All common CPU targets for Windows, Linux and MacOS. * WebAssembly (WASM) * `no-std` +* Minimum Rust version 1.45 Standard features ----------------- @@ -37,6 +38,7 @@ Standard features * Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature). * Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations. * Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html). +* Easy custom API development via [plugins](https://schungx.github.io/rhai/plugins/index.html) system powered by procedural macros. Protection against attacks -------------------------- diff --git a/RELEASES.md b/RELEASES.md index 7e9bb8b2..da91ff68 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,6 +1,15 @@ Rhai Release Notes ================== +Version 0.19.0 +============== + +New features +------------ + +* Plugins support via procedural macros. + + Version 0.18.3 ============== @@ -12,6 +21,15 @@ Bug fixes * Closures that capture now work under `no_object`. +Version 0.18.2 +============== + +New features +------------ + +* Adds `Module::combine_flatten` to combine two modules while flattening to the root level. + + Version 0.18.2 ============== diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml new file mode 100644 index 00000000..89a6f66f --- /dev/null +++ b/codegen/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "rhai_codegen" +version = "0.1.0" +edition = "2018" +authors = ["jhwgh1968", "Stephen Chung"] +description = "Proceducral macro support package for Rhai, a scripting language for Rust" +homepage = "https://github.com/jonathandturner/rhai" +repository = "https://github.com/jonathandturner/rhai" +license = "MIT OR Apache-2.0" + +[lib] +proc-macro = true + +[dev-dependencies] +rhai = { path = ".." } +trybuild = "1" + +[dependencies] +proc-macro2 = "1" +syn = { version = "1", features = ["full", "parsing", "printing", "proc-macro", "extra-traits"] } +quote = "1" diff --git a/codegen/src/attrs.rs b/codegen/src/attrs.rs new file mode 100644 index 00000000..1850d294 --- /dev/null +++ b/codegen/src/attrs.rs @@ -0,0 +1,117 @@ +use syn::{parse::ParseStream, parse::Parser, spanned::Spanned}; + +pub trait ExportedParams: Sized { + fn parse_stream(args: ParseStream) -> syn::Result; + fn no_attrs() -> Self; + fn from_info(info: ExportInfo) -> syn::Result; +} + +pub struct AttrItem { + pub key: proc_macro2::Ident, + pub value: Option, +} + +pub struct ExportInfo { + pub item_span: proc_macro2::Span, + pub items: Vec, +} + +pub fn parse_attr_items(args: ParseStream) -> syn::Result { + if args.is_empty() { + return Ok(ExportInfo { item_span: args.span(), items: Vec::new()}); + } + let arg_list = args + .call(syn::punctuated::Punctuated::::parse_separated_nonempty)?; + + parse_punctuated_items(arg_list) +} + +pub fn parse_punctuated_items( + arg_list: syn::punctuated::Punctuated, +) -> syn::Result { + let list_span = arg_list.span(); + + let mut attrs: Vec = Vec::new(); + for arg in arg_list { + let (key, value) = match arg { + syn::Expr::Assign(syn::ExprAssign { + ref left, + ref right, + .. + }) => { + let attr_name: syn::Ident = match left.as_ref() { + syn::Expr::Path(syn::ExprPath { + path: attr_path, .. + }) => attr_path.get_ident().cloned().ok_or_else(|| { + syn::Error::new(attr_path.span(), "expecting attribute name") + })?, + x => return Err(syn::Error::new(x.span(), "expecting attribute name")), + }; + let attr_value = match right.as_ref() { + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(string), + .. + }) => string.clone(), + x => return Err(syn::Error::new(x.span(), "expecting string literal")), + }; + (attr_name, Some(attr_value)) + } + syn::Expr::Path(syn::ExprPath { + path: attr_path, .. + }) => attr_path + .get_ident() + .cloned() + .map(|a| (a, None)) + .ok_or_else(|| syn::Error::new(attr_path.span(), "expecting attribute name"))?, + x => return Err(syn::Error::new(x.span(), "expecting identifier")), + }; + attrs.push(AttrItem { key, value }); + } + + Ok(ExportInfo { item_span: list_span, items: attrs }) +} + +pub(crate) fn outer_item_attributes( + args: proc_macro2::TokenStream, + _attr_name: &str, +) -> syn::Result { + if args.is_empty() { + return Ok(T::no_attrs()); + } + + let parser = syn::punctuated::Punctuated::::parse_separated_nonempty; + let arg_list = parser.parse2(args)?; + + let export_info = parse_punctuated_items(arg_list)?; + T::from_info(export_info) +} + +pub(crate) fn inner_item_attributes( + attrs: &mut Vec, + attr_name: &str, +) -> syn::Result { + // Find the #[rhai_fn] attribute which will turn be read for the function parameters. + if let Some(rhai_fn_idx) = attrs + .iter() + .position(|a| a.path.get_ident().map(|i| *i == attr_name).unwrap_or(false)) + { + let rhai_fn_attr = attrs.remove(rhai_fn_idx); + rhai_fn_attr.parse_args_with(T::parse_stream) + } else { + Ok(T::no_attrs()) + } +} + +pub(crate) fn deny_cfg_attr(attrs: &Vec) -> syn::Result<()> { + if let Some(cfg_attr) = attrs + .iter() + .find(|a| a.path.get_ident().map(|i| *i == "cfg").unwrap_or(false)) + { + Err(syn::Error::new( + cfg_attr.span(), + "cfg attributes not allowed on this item", + )) + } else { + Ok(()) + } +} diff --git a/codegen/src/function.rs b/codegen/src/function.rs new file mode 100644 index 00000000..337eccab --- /dev/null +++ b/codegen/src/function.rs @@ -0,0 +1,1050 @@ +#![allow(unused)] + +#[cfg(no_std)] +use core::mem; +#[cfg(not(no_std))] +use std::mem; + +#[cfg(no_std)] +use alloc::format; +#[cfg(not(no_std))] +use std::format; + +use quote::{quote, quote_spanned}; +use syn::{parse::Parse, parse::ParseStream, parse::Parser, spanned::Spanned}; + +use crate::attrs::{ExportInfo, ExportedParams}; + +#[derive(Debug, Default)] +pub(crate) struct ExportedFnParams { + pub name: Option, + pub return_raw: bool, + pub skip: bool, + pub span: Option, +} + +pub const FN_IDX_GET: &str = "index$get$"; +pub const FN_IDX_SET: &str = "index$set$"; + +pub fn make_getter(id: &str) -> String { + format!("get${}", id) +} +pub fn make_setter(id: &str) -> String { + format!("set${}", id) +} + +impl Parse for ExportedFnParams { + fn parse(args: ParseStream) -> syn::Result { + if args.is_empty() { + return Ok(ExportedFnParams::default()); + } + + let info = crate::attrs::parse_attr_items(args)?; + Self::from_info(info) + } +} + +impl ExportedParams for ExportedFnParams { + fn parse_stream(args: ParseStream) -> syn::Result { + Self::parse(args) + } + + fn no_attrs() -> Self { + Default::default() + } + + fn from_info( + info: crate::attrs::ExportInfo, + ) -> syn::Result { + let ExportInfo { item_span: span, items: attrs } = info; + let mut name = None; + let mut return_raw = false; + let mut skip = false; + for attr in attrs { + let crate::attrs::AttrItem { key, value } = attr; + match (key.to_string().as_ref(), value) { + ("name", Some(s)) => { + // check validity of name + if s.value().contains('.') { + return Err(syn::Error::new( + s.span(), + "Rhai function names may not contain dot", + )); + } + name = Some(s.value()) + } + ("get", Some(s)) => name = Some(make_getter(&s.value())), + ("set", Some(s)) => name = Some(make_setter(&s.value())), + ("get", None) | ("set", None) | ("name", None) => { + return Err(syn::Error::new(key.span(), "requires value")) + } + ("index_get", None) => name = Some(FN_IDX_GET.to_string()), + ("index_set", None) => name = Some(FN_IDX_SET.to_string()), + ("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")) + } + ("skip", None) => skip = true, + ("skip", Some(s)) => return Err(syn::Error::new(s.span(), "extraneous value")), + (attr, _) => { + return Err(syn::Error::new( + key.span(), + format!("unknown attribute '{}'", attr), + )) + } + } + } + + Ok(ExportedFnParams { + name, + return_raw, + skip, + span: Some(span), + ..Default::default() + }) + } +} + +#[derive(Debug)] +pub(crate) struct ExportedFn { + entire_span: proc_macro2::Span, + signature: syn::Signature, + is_public: bool, + mut_receiver: bool, + params: ExportedFnParams, +} + +impl Parse for ExportedFn { + fn parse(input: ParseStream) -> syn::Result { + let fn_all: syn::ItemFn = input.parse()?; + let entire_span = fn_all.span(); + let str_type_path = syn::parse2::(quote! { str }).unwrap(); + + // #[cfg] attributes are not allowed on functions due to what is generated for them + crate::attrs::deny_cfg_attr(&fn_all.attrs)?; + + // Determine if the function is public. + let is_public = matches!(fn_all.vis, syn::Visibility::Public(_)); + // Determine whether function generates a special calling convention for a mutable + // reciever. + let mut_receiver = { + if let Some(first_arg) = fn_all.sig.inputs.first() { + match first_arg { + syn::FnArg::Receiver(syn::Receiver { + reference: Some(_), .. + }) => true, + syn::FnArg::Typed(syn::PatType { ref ty, .. }) => match ty.as_ref() { + &syn::Type::Reference(syn::TypeReference { + mutability: Some(_), + .. + }) => true, + &syn::Type::Reference(syn::TypeReference { + mutability: None, + ref elem, + .. + }) => match elem.as_ref() { + &syn::Type::Path(ref p) if p.path == str_type_path => false, + _ => { + return Err(syn::Error::new( + ty.span(), + "references from Rhai in this position \ + must be mutable", + )) + } + }, + _ => false, + }, + _ => false, + } + } else { + false + } + }; + + // All arguments after the first must be moved except for &str. + for arg in fn_all.sig.inputs.iter().skip(1) { + let ty = match arg { + syn::FnArg::Typed(syn::PatType { ref ty, .. }) => ty, + _ => panic!("internal error: receiver argument outside of first position!?"), + }; + let is_ok = match ty.as_ref() { + &syn::Type::Reference(syn::TypeReference { + mutability: Some(_), + .. + }) => false, + &syn::Type::Reference(syn::TypeReference { + mutability: None, + ref elem, + .. + }) => matches!(elem.as_ref(), &syn::Type::Path(ref p) if p.path == str_type_path), + &syn::Type::Verbatim(_) => false, + _ => true, + }; + if !is_ok { + return Err(syn::Error::new( + ty.span(), + "this type in this position passes from \ + Rhai by value", + )); + } + } + + // No returning references or pointers. + if let syn::ReturnType::Type(_, ref rtype) = fn_all.sig.output { + match rtype.as_ref() { + &syn::Type::Ptr(_) => { + return Err(syn::Error::new( + fn_all.sig.output.span(), + "cannot return a pointer to Rhai", + )) + } + &syn::Type::Reference(_) => { + return Err(syn::Error::new( + fn_all.sig.output.span(), + "cannot return a reference to Rhai", + )) + } + _ => {} + } + } + Ok(ExportedFn { + entire_span, + signature: fn_all.sig, + is_public, + mut_receiver, + params: ExportedFnParams::default(), + }) + } +} + +impl ExportedFn { + pub(crate) fn params(&self) -> &ExportedFnParams { + &self.params + } + + pub(crate) fn skipped(&self) -> bool { + self.params.skip + } + + pub(crate) fn mutable_receiver(&self) -> bool { + self.mut_receiver + } + + pub(crate) fn is_public(&self) -> bool { + self.is_public + } + + pub(crate) fn span(&self) -> &proc_macro2::Span { + &self.entire_span + } + + pub(crate) fn name(&self) -> &syn::Ident { + &self.signature.ident + } + + pub(crate) fn arg_list(&self) -> impl Iterator { + self.signature.inputs.iter() + } + + pub(crate) fn arg_count(&self) -> usize { + self.signature.inputs.len() + } + + pub(crate) fn return_type(&self) -> Option<&syn::Type> { + if let syn::ReturnType::Type(_, ref rtype) = self.signature.output { + Some(rtype) + } else { + None + } + } + + pub fn set_params(&mut self, mut params: ExportedFnParams) -> syn::Result<()> { + // Do not allow non-returning raw functions. + // + // This is caught now to avoid issues with diagnostics later. + if params.return_raw + && mem::discriminant(&self.signature.output) + == mem::discriminant(&syn::ReturnType::Default) + { + return Err(syn::Error::new( + self.signature.span(), + "return_raw functions must return Result", + )); + } + + self.params = params; + Ok(()) + } + + pub fn generate(self) -> proc_macro2::TokenStream { + let name: syn::Ident = + syn::Ident::new(&format!("rhai_fn_{}", self.name()), self.name().span()); + let impl_block = self.generate_impl("Token"); + let callable_block = self.generate_callable("Token"); + let input_types_block = self.generate_input_types("Token"); + let dyn_result_fn_block = self.generate_dynamic_fn(); + quote! { + #[allow(unused)] + pub mod #name { + use super::*; + struct Token(); + #impl_block + #callable_block + #input_types_block + #dyn_result_fn_block + } + } + } + + pub fn generate_dynamic_fn(&self) -> proc_macro2::TokenStream { + let name = self.name().clone(); + + let mut dynamic_signature = self.signature.clone(); + dynamic_signature.ident = + syn::Ident::new("dynamic_result_fn", proc_macro2::Span::call_site()); + dynamic_signature.output = syn::parse2::(quote! { + -> Result + }) + .unwrap(); + let arguments: Vec = dynamic_signature + .inputs + .iter() + .filter_map(|fnarg| { + if let syn::FnArg::Typed(syn::PatType { ref pat, .. }) = fnarg { + if let syn::Pat::Ident(ref ident) = pat.as_ref() { + Some(ident.ident.clone()) + } else { + None + } + } else { + None + } + }) + .collect(); + + let return_span = self.return_type().map(|r| r.span()) + .unwrap_or_else(|| proc_macro2::Span::call_site()); + if !self.params.return_raw { + quote_spanned! { return_span=> + type EvalBox = Box; + pub #dynamic_signature { + Ok(Dynamic::from(super::#name(#(#arguments),*))) + } + } + } else { + quote_spanned! { return_span=> + type EvalBox = Box; + pub #dynamic_signature { + super::#name(#(#arguments),*) + } + } + } + } + + pub fn generate_callable(&self, on_type_name: &str) -> proc_macro2::TokenStream { + let token_name: syn::Ident = syn::Ident::new(on_type_name, self.name().span()); + let callable_fn_name: syn::Ident = syn::Ident::new( + format!("{}_callable", on_type_name.to_lowercase()).as_str(), + self.name().span(), + ); + quote! { + pub fn #callable_fn_name() -> CallableFunction { + CallableFunction::from_plugin(#token_name()) + } + } + } + + pub fn generate_input_types(&self, on_type_name: &str) -> proc_macro2::TokenStream { + let token_name: syn::Ident = syn::Ident::new(on_type_name, self.name().span()); + let input_types_fn_name: syn::Ident = syn::Ident::new( + format!("{}_input_types", on_type_name.to_lowercase()).as_str(), + self.name().span(), + ); + quote! { + pub fn #input_types_fn_name() -> Box<[TypeId]> { + #token_name().input_types() + } + } + } + + pub fn generate_impl(&self, on_type_name: &str) -> proc_macro2::TokenStream { + let sig_name = self.name().clone(); + let name = self + .params + .name + .clone() + .unwrap_or_else(|| self.name().to_string()); + + let arg_count = self.arg_count(); + let is_method_call = self.mutable_receiver(); + + let mut unpack_stmts: Vec = Vec::new(); + let mut unpack_exprs: Vec = Vec::new(); + let mut input_type_exprs: Vec = Vec::new(); + let skip_first_arg; + + // Handle the first argument separately if the function has a "method like" receiver + if is_method_call { + skip_first_arg = true; + let first_arg = self.arg_list().next().unwrap(); + let var = syn::Ident::new("arg0", proc_macro2::Span::call_site()); + match first_arg { + syn::FnArg::Typed(pattern) => { + let arg_type: &syn::Type = { + match pattern.ty.as_ref() { + &syn::Type::Reference(syn::TypeReference { ref elem, .. }) => { + elem.as_ref() + } + ref p => p, + } + }; + let downcast_span = quote_spanned!( + arg_type.span()=> &mut args[0usize].write_lock::<#arg_type>().unwrap()); + unpack_stmts.push( + syn::parse2::(quote! { + let #var: &mut _ = #downcast_span; + }) + .unwrap(), + ); + input_type_exprs.push( + syn::parse2::(quote_spanned!( + arg_type.span()=> TypeId::of::<#arg_type>() + )) + .unwrap(), + ); + } + syn::FnArg::Receiver(_) => todo!("true self parameters not implemented yet"), + } + unpack_exprs.push(syn::parse2::(quote! { #var }).unwrap()); + } else { + skip_first_arg = false; + } + + // 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. + let str_type_path = syn::parse2::(quote! { str }).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; + match arg { + syn::FnArg::Typed(pattern) => { + let arg_type: &syn::Type = pattern.ty.as_ref(); + let downcast_span = match pattern.ty.as_ref() { + &syn::Type::Reference(syn::TypeReference { + mutability: None, + ref elem, + .. + }) => match elem.as_ref() { + &syn::Type::Path(ref p) if p.path == str_type_path => { + is_str_ref = true; + quote_spanned!(arg_type.span()=> + mem::take(args[#i]) + .clone().cast::()) + } + _ => panic!("internal error: why wasn't this found earlier!?"), + }, + _ => { + is_str_ref = false; + quote_spanned!(arg_type.span()=> + mem::take(args[#i]).clone().cast::<#arg_type>()) + } + }; + + unpack_stmts.push( + syn::parse2::(quote! { + let #var = #downcast_span; + }) + .unwrap(), + ); + if !is_str_ref { + input_type_exprs.push( + syn::parse2::(quote_spanned!( + arg_type.span()=> TypeId::of::<#arg_type>() + )) + .unwrap(), + ); + } else { + input_type_exprs.push( + syn::parse2::(quote_spanned!( + arg_type.span()=> TypeId::of::() + )) + .unwrap(), + ); + } + } + syn::FnArg::Receiver(_) => panic!("internal error: how did this happen!?"), + } + if !is_str_ref { + unpack_exprs.push(syn::parse2::(quote! { #var }).unwrap()); + } else { + unpack_exprs.push(syn::parse2::(quote! { &#var }).unwrap()); + } + } + + // In method calls, the first argument will need to be mutably borrowed. Because Rust marks + // that as needing to borrow the entire array, all of the previous argument unpacking via + // clone needs to happen first. + if is_method_call { + let arg0 = unpack_stmts.remove(0); + unpack_stmts.push(arg0); + } + + // Handle "raw returns", aka cases where the result is a dynamic or an error. + // + // This allows skipping the Dynamic::from wrap. + let return_span = self.return_type().map(|r| r.span()) + .unwrap_or_else(|| proc_macro2::Span::call_site()); + let return_expr = if !self.params.return_raw { + quote_spanned! { return_span=> + Ok(Dynamic::from(#sig_name(#(#unpack_exprs),*))) + } + } else { + quote_spanned! { return_span=> + #sig_name(#(#unpack_exprs),*) + } + }; + + let type_name = syn::Ident::new(on_type_name, proc_macro2::Span::call_site()); + quote! { + impl PluginFunction for #type_name { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), #arg_count, + "wrong arg count: {} != {}", + args.len(), #arg_count); + #(#unpack_stmts)* + #return_expr + } + + fn is_method_call(&self) -> bool { #is_method_call } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(#type_name()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![#(#input_type_exprs),*].into_boxed_slice() + } + } + } + } +} + +#[cfg(test)] +mod function_tests { + use super::ExportedFn; + + use proc_macro2::TokenStream; + use quote::quote; + + #[test] + fn minimal_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_nothing() { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "do_nothing"); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + assert_eq!(item_fn.arg_list().count(), 0); + } + + #[test] + fn one_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_something(x: usize) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "do_something"); + assert_eq!(item_fn.arg_list().count(), 1); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + + assert_eq!( + item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { x: usize }).unwrap() + ); + } + + #[test] + fn two_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_something(x: usize, y: f32) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "do_something"); + assert_eq!(item_fn.arg_list().count(), 2); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + + assert_eq!( + item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { x: usize }).unwrap() + ); + assert_eq!( + item_fn.arg_list().nth(1).unwrap(), + &syn::parse2::(quote! { y: f32 }).unwrap() + ); + } + + #[test] + fn usize_returning_fn() { + let input_tokens: TokenStream = quote! { + pub fn get_magic_number() -> usize { 42 } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "get_magic_number"); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert_eq!(item_fn.arg_list().count(), 0); + assert_eq!( + item_fn.return_type().unwrap(), + &syn::Type::Path(syn::TypePath { + qself: None, + path: syn::parse2::(quote! { usize }).unwrap() + }) + ); + } + + #[test] + fn ref_returning_fn() { + let input_tokens: TokenStream = quote! { + pub fn get_magic_phrase() -> &'static str { "open sesame" } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!(format!("{}", err), "cannot return a reference to Rhai"); + } + + #[test] + fn ptr_returning_fn() { + let input_tokens: TokenStream = quote! { + pub fn get_magic_phrase() -> *const str { "open sesame" } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!(format!("{}", err), "cannot return a pointer to Rhai"); + } + + #[test] + fn ref_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn greet(who: &Person) { } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!( + format!("{}", err), + "references from Rhai in this position must be mutable" + ); + } + + #[test] + fn ref_second_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn greet(count: usize, who: &Person) { } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!( + format!("{}", err), + "this type in this position passes from Rhai by value" + ); + } + + #[test] + fn mut_ref_second_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn give(item_name: &str, who: &mut Person) { } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!( + format!("{}", err), + "this type in this position passes from Rhai by value" + ); + } + + #[test] + fn str_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn log(message: &str) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "log"); + assert_eq!(item_fn.arg_list().count(), 1); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + + assert_eq!( + item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { message: &str }).unwrap() + ); + } + + #[test] + fn str_second_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn log(level: usize, message: &str) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "log"); + assert_eq!(item_fn.arg_list().count(), 2); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + + assert_eq!( + item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { level: usize }).unwrap() + ); + assert_eq!( + item_fn.arg_list().nth(1).unwrap(), + &syn::parse2::(quote! { message: &str }).unwrap() + ); + } + + #[test] + fn private_fn() { + let input_tokens: TokenStream = quote! { + fn do_nothing() { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "do_nothing"); + assert!(!item_fn.mutable_receiver()); + assert!(!item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + assert_eq!(item_fn.arg_list().count(), 0); + } + + #[test] + fn receiver_fn() { + let input_tokens: TokenStream = quote! { + pub fn act_upon(&mut self) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "act_upon"); + assert!(item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + assert_eq!(item_fn.arg_list().count(), 1); + } + + #[test] + fn immutable_receiver_fn() { + let input_tokens: TokenStream = quote! { + pub fn act_upon(&self) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "act_upon"); + assert!(item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + assert_eq!(item_fn.arg_list().count(), 1); + } +} + +#[cfg(test)] +mod generate_tests { + use super::ExportedFn; + + use proc_macro2::TokenStream; + use quote::quote; + + fn assert_streams_eq(actual: TokenStream, expected: TokenStream) { + let actual = actual.to_string(); + 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 mut actual_diff = String::new(); + let mut expected_diff = String::new(); + 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); + } + assert_eq!(actual, expected); + } + + #[test] + fn minimal_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_nothing() { } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_do_nothing { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 0usize, + "wrong arg count: {} != {}", args.len(), 0usize); + Ok(Dynamic::from(do_nothing())) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![].into_boxed_slice() + } + } + pub fn token_callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + type EvalBox = Box; + pub fn dynamic_result_fn() -> Result { + Ok(Dynamic::from(super::do_nothing())) + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + + #[test] + fn one_arg_usize_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_something(x: usize) { } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_do_something { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(do_something(arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn token_callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + type EvalBox = Box; + pub fn dynamic_result_fn(x: usize) -> Result { + Ok(Dynamic::from(super::do_something(x))) + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + + #[test] + fn one_arg_usize_fn_impl() { + let input_tokens: TokenStream = quote! { + pub fn do_something(x: usize) { } + }; + + let expected_tokens = quote! { + impl PluginFunction for MyType { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(do_something(arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(MyType()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_fn.generate_impl("MyType"), expected_tokens); + } + + #[test] + fn two_arg_returning_usize_fn() { + let input_tokens: TokenStream = quote! { + pub fn add_together(x: usize, y: usize) -> usize { x + y } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_add_together { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 2usize, + "wrong arg count: {} != {}", args.len(), 2usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).clone().cast::(); + Ok(Dynamic::from(add_together(arg0, arg1))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::(), + TypeId::of::()].into_boxed_slice() + } + } + pub fn token_callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + type EvalBox = Box; + pub fn dynamic_result_fn(x: usize, y: usize) -> Result { + Ok(Dynamic::from(super::add_together(x, y))) + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + + #[test] + fn mut_arg_usize_fn() { + let input_tokens: TokenStream = quote! { + pub fn increment(x: &mut usize, y: usize) { *x += y; } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_increment { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 2usize, + "wrong arg count: {} != {}", args.len(), 2usize); + let arg1 = mem::take(args[1usize]).clone().cast::(); + let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(increment(arg0, arg1))) + } + + fn is_method_call(&self) -> bool { true } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::(), + TypeId::of::()].into_boxed_slice() + } + } + pub fn token_callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + type EvalBox = Box; + pub fn dynamic_result_fn(x: &mut usize, y: usize) -> Result { + Ok(Dynamic::from(super::increment(x, y))) + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert!(item_fn.mutable_receiver()); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + + #[test] + fn str_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn special_print(message: &str) { eprintln!("----{}----", message); } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_special_print { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(special_print(&arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn token_callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + type EvalBox = Box; + pub fn dynamic_result_fn(message: &str) -> Result { + Ok(Dynamic::from(super::special_print(message))) + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert!(!item_fn.mutable_receiver()); + assert_streams_eq(item_fn.generate(), expected_tokens); + } +} diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs new file mode 100644 index 00000000..7054cbe5 --- /dev/null +++ b/codegen/src/lib.rs @@ -0,0 +1,172 @@ +//! +//! This crate contains procedural macros to make creating Rhai modules much easier. +//! +//! # Exporting a Macro to Rhai +//! +//! ``` +//! use rhai::{EvalAltResult, FLOAT}; +//! use rhai::plugin::*; +//! use rhai::module_resolvers::*; +//! +//! #[rhai::export_module] +//! pub mod advanced_math { +//! use rhai::FLOAT; +//! +//! pub const MYSTIC_NUMBER: FLOAT = 42.0 as FLOAT; +//! +//! pub fn euclidean_distance(x1: FLOAT, y1: FLOAT, x2: FLOAT, y2: FLOAT) -> FLOAT { +//! ((y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0)).sqrt() +//! } +//! } +//! +//! fn main() -> Result<(), Box> { +//! let mut engine = Engine::new(); +//! let m = rhai::exported_module!(advanced_math); +//! let mut r = StaticModuleResolver::new(); +//! r.insert("Math::Advanced".to_string(), m); +//! engine.set_module_resolver(Some(r)); +//! +//! assert_eq!(engine.eval::( +//! r#"import "Math::Advanced" as math; +//! let m = math::MYSTIC_NUMBER; +//! let x = math::euclidean_distance(0.0, 1.0, 0.0, m); +//! x"#)?, 41.0); +//! Ok(()) +//! } +//! ``` +//! +//! # Exporting a Function to a Rhai Module +//! +//! ``` +//! use rhai::{EvalAltResult, FLOAT, Module, RegisterFn}; +//! use rhai::plugin::*; +//! use rhai::module_resolvers::*; +//! +//! #[rhai::export_fn] +//! pub fn distance_function(x1: FLOAT, y1: FLOAT, x2: FLOAT, y2: FLOAT) -> FLOAT { +//! ((y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0)).sqrt() +//! } +//! +//! fn main() -> Result<(), Box> { +//! +//! let mut engine = Engine::new(); +//! engine.register_fn("get_mystic_number", || { 42 as FLOAT }); +//! let mut m = Module::new(); +//! rhai::set_exported_fn!(m, "euclidean_distance", distance_function); +//! let mut r = StaticModuleResolver::new(); +//! r.insert("Math::Advanced".to_string(), m); +//! engine.set_module_resolver(Some(r)); +//! +//! assert_eq!(engine.eval::( +//! r#"import "Math::Advanced" as math; +//! let m = get_mystic_number(); +//! let x = math::euclidean_distance(0.0, 1.0, 0.0, m); +//! x"#)?, 41.0); +//! Ok(()) +//! } +//! ``` +//! +//! # Exporting a Function to an Engine +//! +//! ``` +//! use rhai::{EvalAltResult, FLOAT, Module, RegisterFn}; +//! use rhai::plugin::*; +//! use rhai::module_resolvers::*; +//! +//! #[rhai::export_fn] +//! pub fn distance_function(x1: FLOAT, y1: FLOAT, x2: FLOAT, y2: FLOAT) -> FLOAT { +//! ((y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0)).sqrt() +//! } +//! +//! fn main() -> Result<(), Box> { +//! +//! let mut engine = Engine::new(); +//! engine.register_fn("get_mystic_number", || { 42 as FLOAT }); +//! rhai::register_exported_fn!(engine, "euclidean_distance", distance_function); +//! +//! assert_eq!(engine.eval::( +//! r#"let m = get_mystic_number(); +//! let x = euclidean_distance(0.0, 1.0, 0.0, m); +//! x"#)?, 41.0); +//! Ok(()) +//! } +//! ``` +//! + +use quote::quote; +use syn::parse_macro_input; + +mod attrs; +mod function; +mod module; +mod register; +mod rhai_module; + +#[proc_macro_attribute] +pub fn export_fn( + args: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let mut output = proc_macro2::TokenStream::from(input.clone()); + + let parsed_params = match crate::attrs::outer_item_attributes(args.into(), "export_fn") { + Ok(args) => args, + Err(err) => return proc_macro::TokenStream::from(err.to_compile_error()), + }; + let mut function_def = parse_macro_input!(input as function::ExportedFn); + if let Err(e) = function_def.set_params(parsed_params) { + return e.to_compile_error().into(); + } + + output.extend(function_def.generate()); + proc_macro::TokenStream::from(output) +} + +#[proc_macro_attribute] +pub fn export_module( + _args: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let module_def = parse_macro_input!(input as module::Module); + let tokens = module_def.generate(); + proc_macro::TokenStream::from(tokens) +} + +#[proc_macro] +pub fn exported_module(module_path: proc_macro::TokenStream) -> proc_macro::TokenStream { + let module_path = parse_macro_input!(module_path as syn::Path); + let tokens = quote::quote! { + #module_path::rhai_module_generate() + }; + 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) + { + Ok(triple) => triple, + Err(e) => return e.to_compile_error().into(), + }; + let gen_mod_path = crate::register::generated_module_path(&rust_modpath); + let tokens = quote! { + #engine_expr.register_result_fn(&(#export_name), #gen_mod_path::dynamic_result_fn); + }; + proc_macro::TokenStream::from(tokens) +} + +#[proc_macro] +pub fn set_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenStream { + let (module_expr, export_name, rust_modpath) = match crate::register::parse_register_macro(args) + { + Ok(triple) => triple, + Err(e) => return e.to_compile_error().into(), + }; + let gen_mod_path = crate::register::generated_module_path(&rust_modpath); + let tokens = quote! { + #module_expr.set_fn(#export_name, FnAccess::Public, + #gen_mod_path::token_input_types().as_ref(), + #gen_mod_path::token_callable()); + }; + proc_macro::TokenStream::from(tokens) +} diff --git a/codegen/src/module.rs b/codegen/src/module.rs new file mode 100644 index 00000000..b1a76f29 --- /dev/null +++ b/codegen/src/module.rs @@ -0,0 +1,1538 @@ +use quote::{quote, ToTokens}; +use syn::{parse::Parse, parse::ParseStream}; + +use crate::function::ExportedFn; +use crate::rhai_module::ExportedConst; + +#[cfg(no_std)] +use alloc::vec as new_vec; +#[cfg(not(no_std))] +use std::vec as new_vec; + +#[cfg(no_std)] +use core::mem; +#[cfg(not(no_std))] +use std::mem; + +use std::borrow::Cow; + +use crate::attrs::{AttrItem, ExportInfo, ExportedParams}; +use crate::function::{ExportedFnParams}; + +#[derive(Debug, Default)] +pub(crate) struct ExportedModParams { + pub name: Option, + pub skip: bool, +} + +impl Parse for ExportedModParams { + fn parse(args: ParseStream) -> syn::Result { + if args.is_empty() { + return Ok(ExportedModParams::default()); + } + + let info = crate::attrs::parse_attr_items(args)?; + + Self::from_info(info) + } +} + +impl ExportedParams for ExportedModParams { + fn parse_stream(args: ParseStream) -> syn::Result { + Self::parse(args) + } + + fn no_attrs() -> Self { + Default::default() + } + + fn from_info(info: ExportInfo) -> syn::Result { + let ExportInfo { items: attrs, .. } = info; + let mut name = None; + let mut skip = false; + for attr in attrs { + let AttrItem { key, value } = attr; + match (key.to_string().as_ref(), value) { + ("name", Some(s)) => name = Some(s.value()), + ("name", None) => return Err(syn::Error::new(key.span(), "requires value")), + ("skip", None) => skip = true, + ("skip", Some(s)) => return Err(syn::Error::new(s.span(), "extraneous value")), + (attr, _) => { + return Err(syn::Error::new( + key.span(), + format!("unknown attribute '{}'", attr), + )) + } + } + } + + Ok(ExportedModParams { + name, + skip, + ..Default::default() + }) + } +} + +#[derive(Debug)] +pub(crate) struct Module { + mod_all: Option, + fns: Vec, + consts: Vec, + submodules: Vec, + params: ExportedModParams, +} + +impl Parse for Module { + fn parse(input: ParseStream) -> syn::Result { + let mut mod_all: syn::ItemMod = input.parse()?; + let fns: Vec<_>; + let mut consts: Vec<_> = new_vec![]; + let mut submodules: Vec<_> = Vec::new(); + if let Some((_, ref mut content)) = mod_all.content { + // Gather and parse functions. + fns = content + .iter_mut() + .filter_map(|item| match item { + syn::Item::Fn(f) => Some(f), + _ => None, + }) + .try_fold(Vec::new(), |mut vec, itemfn| { + // #[cfg] attributes are not allowed on functions + crate::attrs::deny_cfg_attr(&itemfn.attrs)?; + + let mut params: ExportedFnParams = + match crate::attrs::inner_item_attributes(&mut itemfn.attrs, "rhai_fn") { + Ok(p) => p, + Err(e) => return Err(e), + }; + params.skip = if let syn::Visibility::Public(_) = itemfn.vis { + params.skip + } else { + true + }; + syn::parse2::(itemfn.to_token_stream()) + .and_then(|mut f| { + f.set_params(params)?; + Ok(f) + }) + .map(|f| vec.push(f)) + .map(|_| vec) + })?; + // Gather and parse constants definitions. + for item in content.iter() { + match item { + syn::Item::Const(syn::ItemConst { + vis, + ref expr, + ident, + attrs, + .. + }) => { + // #[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())); + } + } + _ => {} + } + } + // Gather and parse submodule definitions. + // + // They are actually removed from the module's body, because they will need + // re-generating later when generated code is added. + submodules.reserve(content.len() - fns.len() - consts.len()); + let mut i = 0; + while i < content.len() { + if let syn::Item::Mod(_) = &content[i] { + let mut itemmod = match content.remove(i) { + syn::Item::Mod(m) => m, + _ => unreachable!(), + }; + let mut params: ExportedModParams = + match crate::attrs::inner_item_attributes(&mut itemmod.attrs, "rhai_mod") { + Ok(p) => p, + Err(e) => return Err(e), + }; + params.skip = if let syn::Visibility::Public(_) = itemmod.vis { + params.skip + } else { + true + }; + let module = + syn::parse2::(itemmod.to_token_stream()).map(|mut f| { + f.params = params; + f + })?; + submodules.push(module); + } else { + i += 1; + } + } + } else { + fns = new_vec![]; + } + Ok(Module { + mod_all: Some(mod_all), + fns, + consts, + submodules, + params: ExportedModParams::default(), + }) + } +} + +impl Module { + pub fn attrs(&self) -> Option<&Vec> { + self.mod_all.as_ref().map(|m| &m.attrs) + } + + pub fn module_name(&self) -> Option<&syn::Ident> { + self.mod_all.as_ref().map(|m| &m.ident) + } + + pub fn exported_name(&self) -> Option> { + if let Some(ref s) = self.params.name { + Some(Cow::Borrowed(s)) + } else { + self.module_name().map(|m| Cow::Owned(m.to_string())) + } + } + + pub fn skipped(&self) -> bool { + self.params.skip + } + + pub fn generate(self) -> proc_macro2::TokenStream { + match self.generate_inner() { + Ok(tokens) => tokens, + Err(e) => e.to_compile_error(), + } + } + + fn generate_inner(self) -> Result { + // Check for collisions if the "name" attribute was used on inner functions. + crate::rhai_module::check_rename_collisions(&self.fns)?; + + // Extract the current structure of the module. + let Module { + mod_all, + fns, + consts, + mut submodules, + params, + .. + } = self; + let mut mod_all = mod_all.unwrap(); + let mod_name = mod_all.ident.clone(); + let (_, orig_content) = mod_all.content.take().unwrap(); + let mod_attrs = mem::replace(&mut mod_all.attrs, Vec::with_capacity(0)); + + if !params.skip { + // Generate new module items. + // + // This is done before inner module recursive generation, because that is destructive. + let mod_gen = crate::rhai_module::generate_body(&fns, &consts, &submodules); + + // NB: submodules must have their new items for exporting generated in depth-first order + // to avoid issues caused by re-parsing them + let inner_modules: Vec = submodules.drain(..) + .try_fold::, _, + Result, syn::Error>>( + Vec::new(), |mut acc, m| { acc.push(m.generate_inner()?); Ok(acc) })?; + + // Regenerate the module with the new content added. + Ok(quote! { + #(#mod_attrs)* + pub mod #mod_name { + #(#orig_content)* + #(#inner_modules)* + #mod_gen + } + }) + } else { + // Regenerate the original module as-is. + Ok(quote! { + #(#mod_attrs)* + pub mod #mod_name { + #(#orig_content)* + } + }) + } + } + + pub fn name(&self) -> Option<&syn::Ident> { + self.mod_all.as_ref().map(|m| &m.ident) + } + + pub fn content(&self) -> Option<&Vec> { + match self.mod_all { + Some(syn::ItemMod { + content: Some((_, ref vec)), + .. + }) => Some(vec), + _ => None, + } + } +} + +#[cfg(test)] +mod module_tests { + use super::Module; + + use proc_macro2::TokenStream; + use quote::quote; + + #[test] + fn empty_module() { + let input_tokens: TokenStream = quote! { + pub mod empty { } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns.is_empty()); + assert!(item_mod.consts.is_empty()); + } + + #[test] + fn one_factory_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.consts.is_empty()); + assert_eq!(item_mod.fns.len(), 1); + assert_eq!(item_mod.fns[0].name().to_string(), "get_mystic_number"); + assert_eq!(item_mod.fns[0].arg_count(), 0); + assert_eq!( + item_mod.fns[0].return_type().unwrap(), + &syn::parse2::(quote! { INT }).unwrap() + ); + } + + #[test] + fn one_single_arg_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.consts.is_empty()); + assert_eq!(item_mod.fns.len(), 1); + assert_eq!(item_mod.fns[0].name().to_string(), "add_one_to"); + assert_eq!(item_mod.fns[0].arg_count(), 1); + assert_eq!( + item_mod.fns[0].arg_list().next().unwrap(), + &syn::parse2::(quote! { x: INT }).unwrap() + ); + assert_eq!( + item_mod.fns[0].return_type().unwrap(), + &syn::parse2::(quote! { INT }).unwrap() + ); + } + + #[test] + fn one_double_arg_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn add_together(x: INT, y: INT) -> INT { + x + y + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + let mut args = item_mod.fns[0].arg_list(); + assert!(item_mod.consts.is_empty()); + assert_eq!(item_mod.fns.len(), 1); + assert_eq!(item_mod.fns[0].name().to_string(), "add_together"); + assert_eq!(item_mod.fns[0].arg_count(), 2); + assert_eq!( + args.next().unwrap(), + &syn::parse2::(quote! { x: INT }).unwrap() + ); + assert_eq!( + args.next().unwrap(), + &syn::parse2::(quote! { y: INT }).unwrap() + ); + assert!(args.next().is_none()); + assert_eq!( + item_mod.fns[0].return_type().unwrap(), + &syn::parse2::(quote! { INT }).unwrap() + ); + } + + #[test] + fn one_constant_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub mod it_is { + pub const MYSTIC_NUMBER: INT = 42; + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns.is_empty()); + assert!(item_mod.consts.is_empty()); + 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, + syn::parse2::(quote! { 42 }).unwrap() + ); + } + + #[test] + fn one_skipped_fn_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub mod skip_this { + #[rhai_fn(skip)] + pub fn get_mystic_number() -> INT { + 42 + } + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns.is_empty()); + assert!(item_mod.consts.is_empty()); + assert_eq!(item_mod.submodules.len(), 1); + assert_eq!(item_mod.submodules[0].fns.len(), 1); + assert!(item_mod.submodules[0].fns[0].skipped()); + assert!(item_mod.submodules[0].consts.is_empty()); + assert!(item_mod.submodules[0].submodules.is_empty()); + } + + #[test] + fn one_skipped_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + #[rhai_mod(skip)] + pub mod skip_this { + pub fn get_mystic_number() -> INT { + 42 + } + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns.is_empty()); + assert!(item_mod.consts.is_empty()); + assert_eq!(item_mod.submodules.len(), 1); + assert!(item_mod.submodules[0].skipped()); + } + + #[test] + fn one_constant_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub const MYSTIC_NUMBER: INT = 42; + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns.is_empty()); + assert_eq!(item_mod.consts.len(), 1); + assert_eq!(&item_mod.consts[0].0, "MYSTIC_NUMBER"); + assert_eq!( + item_mod.consts[0].1, + syn::parse2::(quote! { 42 }).unwrap() + ); + } + + #[test] + fn one_private_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + fn get_mystic_number() -> INT { + 42 + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_eq!(item_mod.fns.len(), 1); + assert!(item_mod.fns[0].skipped()); + assert!(item_mod.consts.is_empty()); + } + + #[test] + fn one_skipped_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + #[rhai_fn(skip)] + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_eq!(item_mod.fns.len(), 1); + assert!(item_mod.fns[0].skipped()); + assert!(item_mod.consts.is_empty()); + } + + #[test] + fn one_private_constant_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + const MYSTIC_NUMBER: INT = 42; + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns.is_empty()); + assert!(item_mod.consts.is_empty()); + } +} + +#[cfg(test)] +mod generate_tests { + use super::Module; + + use proc_macro2::TokenStream; + use quote::quote; + + fn assert_streams_eq(actual: TokenStream, expected: TokenStream) { + let actual = actual.to_string(); + 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 mut actual_diff = String::new(); + let mut expected_diff = String::new(); + 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); + } + assert_eq!(actual, expected); + } + + #[test] + fn empty_module() { + let input_tokens: TokenStream = quote! { + pub mod empty { } + }; + + let expected_tokens = quote! { + pub mod empty { + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_factory_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("get_mystic_number", FnAccess::Public, &[], + CallableFunction::from_plugin(get_mystic_number_token())); + m + } + #[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 + ) -> Result> { + debug_assert_eq!(args.len(), 0usize, + "wrong arg count: {} != {}", args.len(), 0usize); + Ok(Dynamic::from(get_mystic_number())) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(get_mystic_number_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![].into_boxed_slice() + } + } + pub fn get_mystic_number_token_callable() -> CallableFunction { + CallableFunction::from_plugin(get_mystic_number_token()) + } + pub fn get_mystic_number_token_input_types() -> Box<[TypeId]> { + get_mystic_number_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_single_arg_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("add_one_to", FnAccess::Public, &[core::any::TypeId::of::()], + CallableFunction::from_plugin(add_one_to_token())); + m + } + #[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 + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(add_one_to(arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(add_one_to_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn add_one_to_token_callable() -> CallableFunction { + CallableFunction::from_plugin(add_one_to_token()) + } + pub fn add_one_to_token_input_types() -> Box<[TypeId]> { + add_one_to_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn two_fn_overload_module() { + let input_tokens: TokenStream = quote! { + pub mod two_fns { + #[rhai_fn(name = "add_n")] + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + + #[rhai_fn(name = "add_n")] + pub fn add_n_to(x: INT, y: INT) -> INT { + x + y + } + } + }; + + let expected_tokens = quote! { + pub mod two_fns { + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + + pub fn add_n_to(x: INT, y: INT) -> INT { + x + y + } + + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + 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 + } + + #[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 + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(add_one_to(arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(add_one_to_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn add_one_to_token_callable() -> CallableFunction { + CallableFunction::from_plugin(add_one_to_token()) + } + pub fn add_one_to_token_input_types() -> Box<[TypeId]> { + add_one_to_token().input_types() + } + + #[allow(non_camel_case_types)] + struct add_n_to_token(); + impl PluginFunction for add_n_to_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 2usize, + "wrong arg count: {} != {}", args.len(), 2usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).clone().cast::(); + Ok(Dynamic::from(add_n_to(arg0, arg1))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(add_n_to_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::(), + TypeId::of::()].into_boxed_slice() + } + } + pub fn add_n_to_token_callable() -> CallableFunction { + CallableFunction::from_plugin(add_n_to_token()) + } + pub fn add_n_to_token_input_types() -> Box<[TypeId]> { + add_n_to_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_double_arg_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn add_together(x: INT, y: INT) -> INT { + x + y + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn add_together(x: INT, y: INT) -> INT { + x + y + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("add_together", FnAccess::Public, &[core::any::TypeId::of::(), + core::any::TypeId::of::()], + CallableFunction::from_plugin(add_together_token())); + m + } + #[allow(non_camel_case_types)] + struct add_together_token(); + impl PluginFunction for add_together_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 2usize, + "wrong arg count: {} != {}", args.len(), 2usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).clone().cast::(); + Ok(Dynamic::from(add_together(arg0, arg1))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(add_together_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::(), + TypeId::of::()].into_boxed_slice() + } + } + pub fn add_together_token_callable() -> CallableFunction { + CallableFunction::from_plugin(add_together_token()) + } + pub fn add_together_token_input_types() -> Box<[TypeId]> { + add_together_token().input_types() + } + } + }; + + 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! { + pub mod one_constant { + pub const MYSTIC_NUMBER: INT = 42; + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + 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); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_constant_module_imports_preserved() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub use rhai::INT; + pub const MYSTIC_NUMBER: INT = 42; + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + pub use rhai::INT; + 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); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_private_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + fn get_mystic_number() -> INT { + 42 + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + fn get_mystic_number() -> INT { + 42 + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_skipped_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + #[rhai_fn(skip)] + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_skipped_submodule() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + #[rhai_mod(skip)] + pub mod inner_secrets { + pub const SECRET_NUMBER: INT = 86; + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + pub mod inner_secrets { + pub const SECRET_NUMBER: INT = 86; + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("get_mystic_number", FnAccess::Public, &[], + CallableFunction::from_plugin(get_mystic_number_token())); + m + } + #[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 + ) -> Result> { + debug_assert_eq!(args.len(), 0usize, + "wrong arg count: {} != {}", args.len(), 0usize); + Ok(Dynamic::from(get_mystic_number())) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(get_mystic_number_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![].into_boxed_slice() + } + } + pub fn get_mystic_number_token_callable() -> CallableFunction { + CallableFunction::from_plugin(get_mystic_number_token()) + } + pub fn get_mystic_number_token_input_types() -> Box<[TypeId]> { + get_mystic_number_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_private_constant_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + const MYSTIC_NUMBER: INT = 42; + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + 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 + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_str_arg_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod str_fn { + pub fn print_out_to(x: &str) { + x + 1 + } + } + }; + + let expected_tokens = quote! { + pub mod str_fn { + pub fn print_out_to(x: &str) { + x + 1 + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("print_out_to", FnAccess::Public, + &[core::any::TypeId::of::()], + CallableFunction::from_plugin(print_out_to_token())); + m + } + #[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 + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + 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! { + pub mod ref_fn { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + } + }; + + let expected_tokens = quote! { + pub mod ref_fn { + 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(); + m.set_fn("increment", FnAccess::Public, + &[core::any::TypeId::of::()], + CallableFunction::from_plugin(increment_token())); + m + } + #[allow(non_camel_case_types)] + struct increment_token(); + impl PluginFunction for increment_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(increment(arg0))) + } + + fn is_method_call(&self) -> bool { true } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(increment_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn increment_token_callable() -> CallableFunction { + CallableFunction::from_plugin(increment_token()) + } + pub fn increment_token_input_types() -> Box<[TypeId]> { + increment_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_fn_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub mod it_is { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + 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(); + m.set_fn("increment", FnAccess::Public, + &[core::any::TypeId::of::()], + CallableFunction::from_plugin(increment_token())); + m + } + #[allow(non_camel_case_types)] + struct increment_token(); + impl PluginFunction for increment_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(increment(arg0))) + } + + fn is_method_call(&self) -> bool { true } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(increment_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn increment_token_callable() -> CallableFunction { + CallableFunction::from_plugin(increment_token()) + } + pub fn increment_token_input_types() -> Box<[TypeId]> { + increment_token().input_types() + } + } + #[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()); } + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_fn_with_cfg_module() { + let input_tokens: TokenStream = 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; + } + } + } + }; + + 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(); + m.set_fn("increment", FnAccess::Public, + &[core::any::TypeId::of::()], + CallableFunction::from_plugin(increment_token())); + m + } + #[allow(non_camel_case_types)] + struct increment_token(); + impl PluginFunction for increment_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(increment(arg0))) + } + + fn is_method_call(&self) -> bool { true } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(increment_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn increment_token_callable() -> CallableFunction { + CallableFunction::from_plugin(increment_token()) + } + pub fn increment_token_input_types() -> Box<[TypeId]> { + increment_token().input_types() + } + } + #[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()); + } + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_constant_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub mod it_is { + pub const MYSTIC_NUMBER: INT = 42; + } + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + pub mod it_is { + 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); + m + } + } + #[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()); } + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn dual_constant_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod two_constants { + pub mod first_is { + pub const MYSTIC_NUMBER: INT = 42; + } + pub mod second_is { + pub const SPECIAL_CPU_NUMBER: INT = 68000; + } + } + }; + + let expected_tokens = quote! { + pub mod two_constants { + pub mod first_is { + 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); + m + } + } + 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); + m + } + } + #[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()); } + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn deep_tree_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod heap_root { + pub const VALUE: INT = 100; + pub mod left { + pub const VALUE: INT = 19; + pub mod left { + pub const VALUE: INT = 17; + pub mod left { + pub const VALUE: INT = 2; + } + pub mod right { + pub const VALUE: INT = 7; + } + } + pub mod right { + pub const VALUE: INT = 3; + } + } + pub mod right { + pub const VALUE: INT = 36; + pub mod left { + pub const VALUE: INT = 25; + } + pub mod right { + pub const VALUE: INT = 1; + } + } + } + }; + + let expected_tokens = quote! { + pub mod heap_root { + pub const VALUE: INT = 100; + pub mod left { + pub const VALUE: INT = 19; + pub mod left { + pub const VALUE: INT = 17; + pub mod left { + 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); + m + } + } + 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); + m + } + } + #[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()); } + m + } + } + 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); + m + } + } + #[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()); } + m + } + } + pub mod right { + pub const VALUE: INT = 36; + pub mod left { + 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); + m + } + } + 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); + m + } + } + #[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()); } + m + } + } + #[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()); } + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } +} diff --git a/codegen/src/register.rs b/codegen/src/register.rs new file mode 100644 index 00000000..321769b7 --- /dev/null +++ b/codegen/src/register.rs @@ -0,0 +1,49 @@ +use quote::{quote, quote_spanned}; +use syn::{parse::Parser, spanned::Spanned}; + +pub(crate) fn generated_module_path( + fn_path: &syn::Path, +) -> syn::punctuated::Punctuated { + let mut g = fn_path.clone().segments; + g.pop(); + let ident = syn::Ident::new( + &format!("rhai_fn_{}", fn_path.segments.last().unwrap().ident), + fn_path.span(), + ); + g.push_value(syn::PathSegment { + ident, + arguments: syn::PathArguments::None, + }); + g +} + +type RegisterMacroInput = (syn::Expr, proc_macro2::TokenStream, syn::Path); +pub fn parse_register_macro( + args: proc_macro::TokenStream, +) -> Result { + let parser = syn::punctuated::Punctuated::::parse_separated_nonempty; + let args = parser.parse(args).unwrap(); + let arg_span = args.span(); + let mut items: Vec = args.into_iter().collect(); + if items.len() != 3 { + return Err(syn::Error::new( + arg_span, + "this macro requires three arguments", + )); + } + let export_name = match &items[1] { + syn::Expr::Lit(litstr) => quote_spanned!(items[1].span()=> + #litstr.to_string()), + expr => quote! { #expr }, + }; + let rust_modpath = if let syn::Expr::Path(ref path) = &items[2] { + path.path.clone() + } else { + return Err(syn::Error::new( + items[2].span(), + "third argument must be a function name", + )); + }; + let module = items.remove(0); + Ok((module, export_name, rust_modpath)) +} diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs new file mode 100644 index 00000000..7e1aa106 --- /dev/null +++ b/codegen/src/rhai_module.rs @@ -0,0 +1,208 @@ +use std::collections::HashMap; + +use quote::{quote, ToTokens}; + +use crate::function::ExportedFn; +use crate::module::Module; + +pub(crate) type ExportedConst = (String, syn::Expr); + +pub(crate) fn generate_body( + fns: &[ExportedFn], + consts: &[ExportedConst], + submodules: &[Module], +) -> proc_macro2::TokenStream { + 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 str_type_path = syn::parse2::(quote! { str }).unwrap(); + + for (const_name, const_expr) in consts { + let const_literal = syn::LitStr::new(&const_name, proc_macro2::Span::call_site()); + set_const_stmts.push( + syn::parse2::(quote! { + m.set_var(#const_literal, #const_expr); + }) + .unwrap(), + ); + } + + for itemmod in submodules { + if itemmod.skipped() { + continue; + } + let module_name: &syn::Ident = itemmod.module_name().unwrap(); + let exported_name: syn::LitStr = if let Some(name) = itemmod.exported_name() { + syn::LitStr::new(&name, proc_macro2::Span::call_site()) + } else { + syn::LitStr::new(&module_name.to_string(), proc_macro2::Span::call_site()) + }; + let cfg_attrs: Vec<&syn::Attribute> = itemmod + .attrs() + .unwrap() + .iter() + .filter(|&a| a.path.get_ident().map(|i| *i == "cfg").unwrap_or(false)) + .collect(); + add_mod_blocks.push( + syn::parse2::(quote! { + #(#cfg_attrs)* { + m.set_sub_module(#exported_name, self::#module_name::rhai_module_generate()); + } + }) + .unwrap(), + ); + } + + // NB: these are token streams, because reparsing messes up "> >" vs ">>" + let mut gen_fn_tokens: Vec = Vec::new(); + for function in fns { + if function.skipped() { + continue; + } + let fn_token_name = syn::Ident::new( + &format!("{}_token", function.name().to_string()), + function.name().span(), + ); + let reg_name = function + .params() + .name + .clone() + .unwrap_or_else(|| function.name().to_string()); + let fn_literal = syn::LitStr::new(®_name, proc_macro2::Span::call_site()); + let fn_input_types: Vec = function + .arg_list() + .map(|fnarg| match fnarg { + syn::FnArg::Receiver(_) => panic!("internal error: receiver fn outside impl!?"), + syn::FnArg::Typed(syn::PatType { ref ty, .. }) => { + let arg_type = match ty.as_ref() { + syn::Type::Reference(syn::TypeReference { + mutability: None, + ref elem, + .. + }) => match elem.as_ref() { + syn::Type::Path(ref p) if p.path == str_type_path => { + syn::parse2::(quote! { + ImmutableString }) + .unwrap() + } + _ => panic!("internal error: non-string shared reference!?"), + }, + syn::Type::Reference(syn::TypeReference { + mutability: Some(_), + ref elem, + .. + }) => match elem.as_ref() { + syn::Type::Path(ref p) => syn::parse2::(quote! { + #p }) + .unwrap(), + _ => panic!("internal error: non-string shared reference!?"), + }, + t => t.clone(), + }; + syn::parse2::(quote! { + core::any::TypeId::of::<#arg_type>()}) + .unwrap() + } + }) + .collect(); + + set_fn_stmts.push( + syn::parse2::(quote! { + m.set_fn(#fn_literal, FnAccess::Public, &[#(#fn_input_types),*], + CallableFunction::from_plugin(#fn_token_name())); + }) + .unwrap(), + ); + + gen_fn_tokens.push(quote! { + #[allow(non_camel_case_types)] + struct #fn_token_name(); + }); + gen_fn_tokens.push(function.generate_impl(&fn_token_name.to_string())); + gen_fn_tokens.push(function.generate_callable(&fn_token_name.to_string())); + gen_fn_tokens.push(function.generate_input_types(&fn_token_name.to_string())); + } + + let mut generate_fncall = syn::parse2::(quote! { + pub mod generate_info { + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + #(#set_fn_stmts)* + #(#set_const_stmts)* + #(#add_mod_blocks)* + m + } + } + }) + .unwrap(); + + let (_, generate_call_content) = generate_fncall.content.take().unwrap(); + + quote! { + #(#generate_call_content)* + #(#gen_fn_tokens)* + } +} + +pub(crate) fn check_rename_collisions(fns: &Vec) -> Result<(), syn::Error> { + let mut renames = HashMap::::new(); + let mut names = HashMap::::new(); + for itemfn in fns.iter() { + if let Some(ref name) = itemfn.params().name { + let current_span = itemfn.params().span.as_ref().unwrap(); + let key = itemfn.arg_list().fold(name.clone(), |mut argstr, fnarg| { + let type_string: String = match fnarg { + syn::FnArg::Receiver(_) => unimplemented!("receiver rhai_fns not implemented"), + syn::FnArg::Typed(syn::PatType { ref ty, .. }) => { + ty.as_ref().to_token_stream().to_string() + } + }; + argstr.push('.'); + argstr.push_str(&type_string); + argstr + }); + if let Some(other_span) = renames.insert(key, *current_span) { + let mut err = syn::Error::new( + *current_span, + format!("duplicate Rhai signature for '{}'", &name), + ); + err.combine(syn::Error::new( + other_span, + format!("duplicated function renamed '{}'", &name), + )); + return Err(err); + } + } else { + let ident = itemfn.name(); + if let Some(other_span) = names.insert(ident.to_string(), ident.span()) { + let mut err = syn::Error::new( + ident.span(), + format!("duplicate function '{}'", ident.to_string()), + ); + err.combine(syn::Error::new( + other_span, + format!("duplicated function '{}'", ident.to_string()), + )); + return Err(err); + } + } + } + for (new_name, attr_span) in renames.drain() { + let new_name = new_name.split('.').next().unwrap(); + if let Some(fn_span) = names.get(new_name) { + let mut err = syn::Error::new( + attr_span, + format!("duplicate Rhai signature for '{}'", &new_name), + ); + err.combine(syn::Error::new( + *fn_span, + format!("duplicated function '{}'", &new_name), + )); + return Err(err); + } + } + Ok(()) +} diff --git a/codegen/tests/test_functions.rs b/codegen/tests/test_functions.rs new file mode 100644 index 00000000..2f8a881e --- /dev/null +++ b/codegen/tests/test_functions.rs @@ -0,0 +1,202 @@ +use rhai::module_resolvers::*; +use rhai::plugin::*; +use rhai::{Engine, EvalAltResult, Module, RegisterFn, FLOAT}; + +pub mod raw_fn { + use rhai::plugin::*; + use rhai::FLOAT; + + #[export_fn] + pub fn distance_function(x1: FLOAT, y1: FLOAT, x2: FLOAT, y2: FLOAT) -> FLOAT { + ((y2 - y1).abs().powf(2.0) + (x2 - x1).abs().powf(2.0)).sqrt() + } +} + +#[test] +fn raw_fn_test() -> Result<(), Box> { + let mut engine = Engine::new(); + engine.register_fn("get_mystic_number", || 42 as FLOAT); + let mut m = Module::new(); + rhai::set_exported_fn!( + m, + "euclidean_distance".to_string(), + raw_fn::distance_function + ); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!( + engine.eval::( + r#"import "Math::Advanced" as math; + let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"# + )?, + 41.0 + ); + Ok(()) +} + +mod raw_fn_mut { + use rhai::plugin::*; + use rhai::FLOAT; + + #[export_fn] + pub fn add_in_place(f1: &mut FLOAT, f2: FLOAT) { + *f1 += f2; + } +} + +#[test] +fn raw_fn_mut_test() -> Result<(), Box> { + let mut engine = Engine::new(); + engine.register_fn("get_mystic_number", || 42 as FLOAT); + let mut m = Module::new(); + rhai::set_exported_fn!(m, "add_in_place", raw_fn_mut::add_in_place); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!( + engine.eval::( + r#"import "Math::Advanced" as math; + let x = get_mystic_number(); + math::add_in_place(x, 1.0); + x"# + )?, + 43.0 + ); + Ok(()) +} + +mod raw_fn_str { + use rhai::plugin::*; + + #[export_fn] + pub fn write_out_str(message: &str) -> bool { + eprintln!("{}", message); + true + } +} + +#[test] +fn raw_fn_str_test() -> Result<(), Box> { + let mut engine = Engine::new(); + engine.register_fn("get_mystic_number", || 42 as FLOAT); + let mut m = Module::new(); + rhai::set_exported_fn!(m, "write_out_str", raw_fn_str::write_out_str); + let mut r = StaticModuleResolver::new(); + r.insert("Host::IO".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!( + engine.eval::( + r#"import "Host::IO" as io; + let x = io::write_out_str("hello world!"); + x"# + )?, + true + ); + Ok(()) +} + +mod mut_opaque_ref { + use rhai::plugin::*; + use rhai::INT; + + #[derive(Clone)] + pub struct StatusMessage { + os_code: Option, + message: String, + is_ok: bool, + } + + #[export_fn] + pub fn new_message(is_ok: bool, message: &str) -> StatusMessage { + StatusMessage { + is_ok, + os_code: None, + message: message.to_string(), + } + } + + #[export_fn] + pub fn new_os_message(is_ok: bool, os_code: INT) -> StatusMessage { + StatusMessage { + is_ok, + os_code: Some(os_code), + message: format!("OS Code {}", os_code), + } + } + + #[export_fn] + pub fn write_out_message(message: &mut StatusMessage) -> bool { + eprintln!("{}", message.message); + true + } +} + +#[test] +fn mut_opaque_ref_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let mut m = Module::new(); + rhai::set_exported_fn!(m, "new_message", mut_opaque_ref::new_message); + rhai::set_exported_fn!(m, "new_os_message", mut_opaque_ref::new_os_message); + rhai::set_exported_fn!(m, "write_out_message", mut_opaque_ref::write_out_message); + let mut r = StaticModuleResolver::new(); + r.insert("Host::Msg".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!( + engine.eval::( + r#"import "Host::Msg" as msg; + let message1 = msg::new_message(true, "it worked"); + let ok1 = msg::write_out_message(message1); + let message2 = msg::new_os_message(true, 0); + let ok2 = msg::write_out_message(message2); + ok1 && ok2"# + )?, + true + ); + Ok(()) +} + +pub mod raw_returning_fn { + use rhai::plugin::*; + use rhai::FLOAT; + + #[export_fn(return_raw)] + pub fn distance_function( + x1: FLOAT, + y1: FLOAT, + x2: FLOAT, + y2: FLOAT, + ) -> Result> { + Ok(Dynamic::from( + ((y2 - y1).abs().powf(2.0) + (x2 - x1).abs().powf(2.0)).sqrt(), + )) + } +} + +#[test] +fn raw_returning_fn_test() -> Result<(), Box> { + let mut engine = Engine::new(); + engine.register_fn("get_mystic_number", || 42 as FLOAT); + let mut m = Module::new(); + rhai::set_exported_fn!( + m, + "euclidean_distance".to_string(), + raw_returning_fn::distance_function + ); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!( + engine.eval::( + r#"import "Math::Advanced" as math; + let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"# + )?, + 41.0 + ); + Ok(()) +} diff --git a/codegen/tests/test_modules.rs b/codegen/tests/test_modules.rs new file mode 100644 index 00000000..dd5acd05 --- /dev/null +++ b/codegen/tests/test_modules.rs @@ -0,0 +1,223 @@ +use rhai::module_resolvers::*; +use rhai::{Array, Engine, EvalAltResult, RegisterFn, FLOAT, INT}; + +pub mod empty_module { + use rhai::plugin::*; + + #[export_module] + pub mod EmptyModule {} +} + +#[test] +fn empty_module_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::empty_module::EmptyModule); + let mut r = StaticModuleResolver::new(); + r.insert("Module::Empty".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!( + engine.eval::(r#"import "Module::Empty" as m; 42"#)?, + 42 + ); + Ok(()) +} + +pub mod one_fn_module { + use rhai::plugin::*; + + #[export_module] + pub mod advanced_math { + use rhai::FLOAT; + pub fn get_mystic_number() -> FLOAT { + 42.0 as FLOAT + } + } +} + +#[test] +fn one_fn_module_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::one_fn_module::advanced_math); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!( + engine.eval::( + r#"import "Math::Advanced" as math; + let m = math::get_mystic_number(); + m"# + )?, + 42.0 + ); + Ok(()) +} + +pub mod one_fn_and_const_module { + use rhai::plugin::*; + + #[export_module] + pub mod advanced_math { + use rhai::FLOAT; + + pub const MYSTIC_NUMBER: FLOAT = 42.0 as FLOAT; + + pub fn euclidean_distance(x1: FLOAT, y1: FLOAT, x2: FLOAT, y2: FLOAT) -> FLOAT { + ((y2 - y1).abs().powf(2.0) + (x2 - x1).abs().powf(2.0)).sqrt() + } + } +} + +#[test] +fn one_fn_and_const_module_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::one_fn_and_const_module::advanced_math); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!( + engine.eval::( + r#"import "Math::Advanced" as math; + let m = math::MYSTIC_NUMBER; + let x = math::euclidean_distance(0.0, 1.0, 0.0, m); + x"# + )?, + 41.0 + ); + Ok(()) +} + +pub mod raw_fn_str_module { + use rhai::plugin::*; + + #[export_module] + pub mod host_io { + pub fn write_out_str(message: &str) -> bool { + eprintln!("{}", message); + true + } + } +} + +#[test] +fn raw_fn_str_module_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::raw_fn_str_module::host_io); + let mut r = StaticModuleResolver::new(); + r.insert("Host::IO".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!( + engine.eval::( + r#"import "Host::IO" as io; + let x = io::write_out_str("hello world!"); + x"# + )?, + true + ); + Ok(()) +} + +pub mod mut_opaque_ref_module { + use rhai::plugin::*; + use rhai::INT; + + #[derive(Clone)] + pub struct StatusMessage { + os_code: Option, + message: String, + is_ok: bool, + } + + #[export_module] + pub mod host_msg { + use super::{StatusMessage, INT}; + + pub fn new_message(is_ok: bool, message: &str) -> StatusMessage { + StatusMessage { + is_ok, + os_code: None, + message: message.to_string(), + } + } + + pub fn new_os_message(is_ok: bool, os_code: INT) -> StatusMessage { + StatusMessage { + is_ok, + os_code: Some(os_code), + message: format!("OS Code {}", os_code), + } + } + + pub fn write_out_message(message: &mut StatusMessage) -> bool { + eprintln!("{}", message.message); + true + } + } +} + +#[test] +fn mut_opaque_ref_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::mut_opaque_ref_module::host_msg); + let mut r = StaticModuleResolver::new(); + r.insert("Host::Msg".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!( + engine.eval::( + r#"import "Host::Msg" as msg; + let success = "it worked"; + let message1 = msg::new_message(true, success); + let ok1 = msg::write_out_message(message1); + let message2 = msg::new_os_message(true, 0); + let ok2 = msg::write_out_message(message2); + ok1 && ok2"# + )?, + true + ); + Ok(()) +} + +mod duplicate_fn_rename { + use rhai::plugin::*; + #[export_module] + pub mod my_adds { + use rhai::{FLOAT, INT}; + + #[rhai_fn(name = "add")] + pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT { + f1 + f2 + } + + #[rhai_fn(name = "add")] + pub fn add_int(i1: INT, i2: INT) -> INT { + i1 + i2 + } + } +} + +#[test] +fn duplicate_fn_rename_test() -> Result<(), Box> { + let mut engine = Engine::new(); + engine.register_fn("get_mystic_number", || 42 as FLOAT); + let m = rhai::exported_module!(crate::duplicate_fn_rename::my_adds); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + let output_array = engine.eval::( + r#"import "Math::Advanced" as math; + let fx = get_mystic_number(); + let fy = math::add(fx, 1.0); + let ix = 42; + let iy = math::add(ix, 1); + [fy, iy] + "#, + )?; + assert_eq!(&output_array[0].as_float().unwrap(), &43.0); + assert_eq!(&output_array[1].as_int().unwrap(), &43); + Ok(()) +} diff --git a/codegen/tests/test_nested.rs b/codegen/tests/test_nested.rs new file mode 100644 index 00000000..bf0c0030 --- /dev/null +++ b/codegen/tests/test_nested.rs @@ -0,0 +1,72 @@ +use rhai::module_resolvers::*; +use rhai::{Engine, EvalAltResult, RegisterFn, FLOAT, INT}; + +pub mod one_fn_module_nested_attr { + use rhai::plugin::*; + + #[export_module] + pub mod advanced_math { + use rhai::plugin::*; + use rhai::FLOAT; + + #[rhai_fn(return_raw)] + pub fn get_mystic_number() -> Result> { + Ok(Dynamic::from(42.0 as FLOAT)) + } + } +} + +#[test] +fn one_fn_module_nested_attr_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::one_fn_module_nested_attr::advanced_math); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!( + engine.eval::( + r#"import "Math::Advanced" as math; + let m = math::get_mystic_number(); + m"# + )?, + 42.0 + ); + Ok(()) +} + +pub mod one_fn_submodule_nested_attr { + use rhai::plugin::*; + + #[export_module] + pub mod advanced_math { + #[rhai_mod(name = "constants")] + pub mod my_module { + use rhai::plugin::*; + use rhai::FLOAT; + #[rhai_fn(return_raw)] + pub fn get_mystic_number() -> Result> { + Ok(Dynamic::from(42.0 as FLOAT)) + } + } + } +} + +#[test] +fn one_fn_submodule_nested_attr_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::one_fn_submodule_nested_attr::advanced_math); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!( + engine.eval::( + r#"import "Math::Advanced" as math; + let m = math::constants::get_mystic_number(); + m"# + )?, + 42.0 + ); + Ok(()) +} diff --git a/codegen/tests/ui_tests.rs b/codegen/tests/ui_tests.rs new file mode 100644 index 00000000..ef7bf58f --- /dev/null +++ b/codegen/tests/ui_tests.rs @@ -0,0 +1,8 @@ +#![cfg(test)] +mod ui_tests { + #[test] + fn all() { + let t = trybuild::TestCases::new(); + t.compile_fail("ui_tests/*.rs"); + } +} diff --git a/codegen/ui_tests/export_fn_bad_attr.rs b/codegen/ui_tests/export_fn_bad_attr.rs new file mode 100644 index 00000000..685b8e88 --- /dev/null +++ b/codegen/ui_tests/export_fn_bad_attr.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn(unknown = "thing")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_bad_attr.stderr b/codegen/ui_tests/export_fn_bad_attr.stderr new file mode 100644 index 00000000..12dd2a82 --- /dev/null +++ b/codegen/ui_tests/export_fn_bad_attr.stderr @@ -0,0 +1,11 @@ +error: unknown attribute 'unknown' + --> $DIR/export_fn_bad_attr.rs:9:13 + | +9 | #[export_fn(unknown = "thing")] + | ^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_bad_attr.rs:19:8 + | +19 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/export_fn_bad_value.rs b/codegen/ui_tests/export_fn_bad_value.rs new file mode 100644 index 00000000..f8044f9c --- /dev/null +++ b/codegen/ui_tests/export_fn_bad_value.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn(name = true)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_bad_value.stderr b/codegen/ui_tests/export_fn_bad_value.stderr new file mode 100644 index 00000000..4695abb0 --- /dev/null +++ b/codegen/ui_tests/export_fn_bad_value.stderr @@ -0,0 +1,11 @@ +error: expecting string literal + --> $DIR/export_fn_bad_value.rs:9:20 + | +9 | #[export_fn(name = true)] + | ^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_bad_value.rs:19:8 + | +19 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/export_fn_cfg.rs b/codegen/ui_tests/export_fn_cfg.rs new file mode 100644 index 00000000..52d4c963 --- /dev/null +++ b/codegen/ui_tests/export_fn_cfg.rs @@ -0,0 +1,25 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[cfg(not(feature = "foo"))] +#[export_fn] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_cfg.stderr b/codegen/ui_tests/export_fn_cfg.stderr new file mode 100644 index 00000000..ac068be8 --- /dev/null +++ b/codegen/ui_tests/export_fn_cfg.stderr @@ -0,0 +1,11 @@ +error: cfg attributes not allowed on this item + --> $DIR/export_fn_cfg.rs:9:1 + | +9 | #[cfg(not(feature = "foo"))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_cfg.rs:20:8 + | +20 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/export_fn_extra_value.rs b/codegen/ui_tests/export_fn_extra_value.rs new file mode 100644 index 00000000..d8cb9623 --- /dev/null +++ b/codegen/ui_tests/export_fn_extra_value.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn(return_raw = "yes")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_extra_value.stderr b/codegen/ui_tests/export_fn_extra_value.stderr new file mode 100644 index 00000000..6ebc2763 --- /dev/null +++ b/codegen/ui_tests/export_fn_extra_value.stderr @@ -0,0 +1,11 @@ +error: extraneous value + --> $DIR/export_fn_extra_value.rs:9:26 + | +9 | #[export_fn(return_raw = "yes")] + | ^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_extra_value.rs:19:8 + | +19 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/export_fn_junk_arg.rs b/codegen/ui_tests/export_fn_junk_arg.rs new file mode 100644 index 00000000..3abb9399 --- /dev/null +++ b/codegen/ui_tests/export_fn_junk_arg.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn("wheeeee")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_junk_arg.stderr b/codegen/ui_tests/export_fn_junk_arg.stderr new file mode 100644 index 00000000..04cf996b --- /dev/null +++ b/codegen/ui_tests/export_fn_junk_arg.stderr @@ -0,0 +1,11 @@ +error: expecting identifier + --> $DIR/export_fn_junk_arg.rs:9:13 + | +9 | #[export_fn("wheeeee")] + | ^^^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_junk_arg.rs:19:8 + | +19 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/export_fn_missing_value.rs b/codegen/ui_tests/export_fn_missing_value.rs new file mode 100644 index 00000000..7497a518 --- /dev/null +++ b/codegen/ui_tests/export_fn_missing_value.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn(name)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_missing_value.stderr b/codegen/ui_tests/export_fn_missing_value.stderr new file mode 100644 index 00000000..978798e5 --- /dev/null +++ b/codegen/ui_tests/export_fn_missing_value.stderr @@ -0,0 +1,11 @@ +error: requires value + --> $DIR/export_fn_missing_value.rs:9:13 + | +9 | #[export_fn(name)] + | ^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_missing_value.rs:19:8 + | +19 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/export_fn_path_attr.rs b/codegen/ui_tests/export_fn_path_attr.rs new file mode 100644 index 00000000..a9fed9e9 --- /dev/null +++ b/codegen/ui_tests/export_fn_path_attr.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn(rhai::name = "thing")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_path_attr.stderr b/codegen/ui_tests/export_fn_path_attr.stderr new file mode 100644 index 00000000..baf499cb --- /dev/null +++ b/codegen/ui_tests/export_fn_path_attr.stderr @@ -0,0 +1,11 @@ +error: expecting attribute name + --> $DIR/export_fn_path_attr.rs:9:13 + | +9 | #[export_fn(rhai::name = "thing")] + | ^^^^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_path_attr.rs:19:8 + | +19 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/export_fn_raw_noreturn.rs b/codegen/ui_tests/export_fn_raw_noreturn.rs new file mode 100644 index 00000000..7c8b42e0 --- /dev/null +++ b/codegen/ui_tests/export_fn_raw_noreturn.rs @@ -0,0 +1,25 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn(return_raw)] +pub fn test_fn(input: &mut Point) { + input.x += 1.0; +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + test_fn(&mut n); + if n.x >= 10.0 { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_raw_noreturn.stderr b/codegen/ui_tests/export_fn_raw_noreturn.stderr new file mode 100644 index 00000000..0687c8c6 --- /dev/null +++ b/codegen/ui_tests/export_fn_raw_noreturn.stderr @@ -0,0 +1,11 @@ +error: return_raw functions must return Result + --> $DIR/export_fn_raw_noreturn.rs:10:5 + | +10 | pub fn test_fn(input: &mut Point) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_raw_noreturn.rs:19:5 + | +19 | test_fn(&mut n); + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/export_fn_raw_return.rs b/codegen/ui_tests/export_fn_raw_return.rs new file mode 100644 index 00000000..9df99549 --- /dev/null +++ b/codegen/ui_tests/export_fn_raw_return.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn(return_raw)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_raw_return.stderr b/codegen/ui_tests/export_fn_raw_return.stderr new file mode 100644 index 00000000..f570fda9 --- /dev/null +++ b/codegen/ui_tests/export_fn_raw_return.stderr @@ -0,0 +1,21 @@ +error[E0308]: mismatched types + --> $DIR/export_fn_raw_return.rs:10:8 + | +9 | #[export_fn(return_raw)] + | ------------------------ expected `std::result::Result>` because of return type +10 | pub fn test_fn(input: Point) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `std::result::Result`, found `bool` + | + = note: expected enum `std::result::Result>` + found type `bool` + +error[E0308]: mismatched types + --> $DIR/export_fn_raw_return.rs:10:33 + | +9 | #[export_fn(return_raw)] + | ------------------------ expected `std::result::Result>` because of return type +10 | pub fn test_fn(input: Point) -> bool { + | ^^^^ expected enum `std::result::Result`, found `bool` + | + = note: expected enum `std::result::Result>` + found type `bool` diff --git a/codegen/ui_tests/export_mod_bad_attr.rs b/codegen/ui_tests/export_mod_bad_attr.rs new file mode 100644 index 00000000..8f8f7c2b --- /dev/null +++ b/codegen/ui_tests/export_mod_bad_attr.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[rhai_fn(unknown = "thing")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_bad_attr.stderr b/codegen/ui_tests/export_mod_bad_attr.stderr new file mode 100644 index 00000000..9704d74d --- /dev/null +++ b/codegen/ui_tests/export_mod_bad_attr.stderr @@ -0,0 +1,11 @@ +error: unknown attribute 'unknown' + --> $DIR/export_mod_bad_attr.rs:11:11 + | +11 | #[rhai_fn(unknown = "thing")] + | ^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_mod` + --> $DIR/export_mod_bad_attr.rs:22:8 + | +22 | if test_mod::test_fn(n) { + | ^^^^^^^^ use of undeclared type or module `test_mod` diff --git a/codegen/ui_tests/export_mod_bad_value.rs b/codegen/ui_tests/export_mod_bad_value.rs new file mode 100644 index 00000000..c513dd35 --- /dev/null +++ b/codegen/ui_tests/export_mod_bad_value.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[rhai_fn(name = true)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_bad_value.stderr b/codegen/ui_tests/export_mod_bad_value.stderr new file mode 100644 index 00000000..63b22e4e --- /dev/null +++ b/codegen/ui_tests/export_mod_bad_value.stderr @@ -0,0 +1,11 @@ +error: expecting string literal + --> $DIR/export_mod_bad_value.rs:11:18 + | +11 | #[rhai_fn(name = true)] + | ^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_mod` + --> $DIR/export_mod_bad_value.rs:22:8 + | +22 | if test_mod::test_fn(n) { + | ^^^^^^^^ use of undeclared type or module `test_mod` diff --git a/codegen/ui_tests/export_mod_cfg.rs b/codegen/ui_tests/export_mod_cfg.rs new file mode 100644 index 00000000..49838a73 --- /dev/null +++ b/codegen/ui_tests/export_mod_cfg.rs @@ -0,0 +1,28 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[cfg(not(feature = "foo"))] +#[rhai_fn] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_cfg.stderr b/codegen/ui_tests/export_mod_cfg.stderr new file mode 100644 index 00000000..b932ec86 --- /dev/null +++ b/codegen/ui_tests/export_mod_cfg.stderr @@ -0,0 +1,11 @@ +error: cfg attributes not allowed on this item + --> $DIR/export_mod_cfg.rs:11:1 + | +11 | #[cfg(not(feature = "foo"))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_mod` + --> $DIR/export_mod_cfg.rs:23:8 + | +23 | if test_mod::test_fn(n) { + | ^^^^^^^^ use of undeclared type or module `test_mod` diff --git a/codegen/ui_tests/export_mod_extra_value.rs b/codegen/ui_tests/export_mod_extra_value.rs new file mode 100644 index 00000000..6636f108 --- /dev/null +++ b/codegen/ui_tests/export_mod_extra_value.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[rhai_fn(return_raw = "yes")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_extra_value.stderr b/codegen/ui_tests/export_mod_extra_value.stderr new file mode 100644 index 00000000..0e3c65d6 --- /dev/null +++ b/codegen/ui_tests/export_mod_extra_value.stderr @@ -0,0 +1,11 @@ +error: extraneous value + --> $DIR/export_mod_extra_value.rs:11:24 + | +11 | #[rhai_fn(return_raw = "yes")] + | ^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_mod` + --> $DIR/export_mod_extra_value.rs:22:8 + | +22 | if test_mod::test_fn(n) { + | ^^^^^^^^ use of undeclared type or module `test_mod` diff --git a/codegen/ui_tests/export_mod_junk_arg.rs b/codegen/ui_tests/export_mod_junk_arg.rs new file mode 100644 index 00000000..12190cf2 --- /dev/null +++ b/codegen/ui_tests/export_mod_junk_arg.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[rhai_fn("wheeeee")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_junk_arg.stderr b/codegen/ui_tests/export_mod_junk_arg.stderr new file mode 100644 index 00000000..f4505bae --- /dev/null +++ b/codegen/ui_tests/export_mod_junk_arg.stderr @@ -0,0 +1,11 @@ +error: expecting identifier + --> $DIR/export_mod_junk_arg.rs:11:11 + | +11 | #[rhai_fn("wheeeee")] + | ^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_mod` + --> $DIR/export_mod_junk_arg.rs:22:8 + | +22 | if test_mod::test_fn(n) { + | ^^^^^^^^ use of undeclared type or module `test_mod` diff --git a/codegen/ui_tests/export_mod_missing_value.rs b/codegen/ui_tests/export_mod_missing_value.rs new file mode 100644 index 00000000..f57c247b --- /dev/null +++ b/codegen/ui_tests/export_mod_missing_value.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[rhai_fn(name)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_missing_value.stderr b/codegen/ui_tests/export_mod_missing_value.stderr new file mode 100644 index 00000000..f479f5fa --- /dev/null +++ b/codegen/ui_tests/export_mod_missing_value.stderr @@ -0,0 +1,11 @@ +error: requires value + --> $DIR/export_mod_missing_value.rs:11:11 + | +11 | #[rhai_fn(name)] + | ^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_mod` + --> $DIR/export_mod_missing_value.rs:22:8 + | +22 | if test_mod::test_fn(n) { + | ^^^^^^^^ use of undeclared type or module `test_mod` diff --git a/codegen/ui_tests/export_mod_path_attr.rs b/codegen/ui_tests/export_mod_path_attr.rs new file mode 100644 index 00000000..a489f042 --- /dev/null +++ b/codegen/ui_tests/export_mod_path_attr.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[rhai_fn(rhai::name = "thing")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_path_attr.stderr b/codegen/ui_tests/export_mod_path_attr.stderr new file mode 100644 index 00000000..d96d7c8c --- /dev/null +++ b/codegen/ui_tests/export_mod_path_attr.stderr @@ -0,0 +1,11 @@ +error: expecting attribute name + --> $DIR/export_mod_path_attr.rs:11:11 + | +11 | #[rhai_fn(rhai::name = "thing")] + | ^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_mod` + --> $DIR/export_mod_path_attr.rs:22:8 + | +22 | if test_mod::test_fn(n) { + | ^^^^^^^^ use of undeclared type or module `test_mod` diff --git a/codegen/ui_tests/export_mod_raw_noreturn.rs b/codegen/ui_tests/export_mod_raw_noreturn.rs new file mode 100644 index 00000000..926f6286 --- /dev/null +++ b/codegen/ui_tests/export_mod_raw_noreturn.rs @@ -0,0 +1,28 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[rhai_fn(return_raw)] +pub fn test_fn(input: &mut Point) { + input.x += 1.0; +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + test_mod::test_fn(&mut n); + if n.x >= 10.0 { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_raw_noreturn.stderr b/codegen/ui_tests/export_mod_raw_noreturn.stderr new file mode 100644 index 00000000..a548f339 --- /dev/null +++ b/codegen/ui_tests/export_mod_raw_noreturn.stderr @@ -0,0 +1,11 @@ +error: return_raw functions must return Result + --> $DIR/export_mod_raw_noreturn.rs:12:5 + | +12 | pub fn test_fn(input: &mut Point) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_mod` + --> $DIR/export_mod_raw_noreturn.rs:22:5 + | +22 | test_mod::test_fn(&mut n); + | ^^^^^^^^ use of undeclared type or module `test_mod` diff --git a/codegen/ui_tests/export_mod_raw_return.rs b/codegen/ui_tests/export_mod_raw_return.rs new file mode 100644 index 00000000..ae52301e --- /dev/null +++ b/codegen/ui_tests/export_mod_raw_return.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_mod { +#[rhai_fn(return_raw)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_mod_raw_return.stderr b/codegen/ui_tests/export_mod_raw_return.stderr new file mode 100644 index 00000000..7d22f7a2 --- /dev/null +++ b/codegen/ui_tests/export_mod_raw_return.stderr @@ -0,0 +1,11 @@ +error[E0308]: mismatched types + --> $DIR/export_mod_raw_return.rs:12:8 + | +9 | #[export_module] + | ---------------- expected `std::result::Result>` because of return type +... +12 | pub fn test_fn(input: Point) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `std::result::Result`, found `bool` + | + = note: expected enum `std::result::Result>` + found type `bool` diff --git a/codegen/ui_tests/first_shared_ref.rs b/codegen/ui_tests/first_shared_ref.rs new file mode 100644 index 00000000..f26fc40f --- /dev/null +++ b/codegen/ui_tests/first_shared_ref.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +struct NonClonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_fn] +pub fn test_fn(input: &NonClonable) -> bool { + input.d +} + +fn main() { + let n = NonClonable { + a: 0.0, + b: 10, + c: 'a', + d: true, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/first_shared_ref.stderr b/codegen/ui_tests/first_shared_ref.stderr new file mode 100644 index 00000000..41b0085e --- /dev/null +++ b/codegen/ui_tests/first_shared_ref.stderr @@ -0,0 +1,11 @@ +error: references from Rhai in this position must be mutable + --> $DIR/first_shared_ref.rs:11:23 + | +11 | pub fn test_fn(input: &NonClonable) -> bool { + | ^^^^^^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/first_shared_ref.rs:22:8 + | +22 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/module_cfg_const.rs b/codegen/ui_tests/module_cfg_const.rs new file mode 100644 index 00000000..599f52da --- /dev/null +++ b/codegen/ui_tests/module_cfg_const.rs @@ -0,0 +1,31 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + use rhai::FLOAT; + + #[cfg(feature = "foo")] + pub const MAGIC: FLOAT = 42.0 as FLOAT; + + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/module_cfg_const.stderr b/codegen/ui_tests/module_cfg_const.stderr new file mode 100644 index 00000000..4eaf1b06 --- /dev/null +++ b/codegen/ui_tests/module_cfg_const.stderr @@ -0,0 +1,11 @@ +error: cfg attributes not allowed on this item + --> $DIR/module_cfg_const.rs:13:5 + | +13 | #[cfg(feature = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/module_cfg_const.rs:26:8 + | +26 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/module_cfg_fn.rs b/codegen/ui_tests/module_cfg_fn.rs new file mode 100644 index 00000000..3613b9a5 --- /dev/null +++ b/codegen/ui_tests/module_cfg_fn.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + #[cfg(not(feature = "foo"))] + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/module_cfg_fn.stderr b/codegen/ui_tests/module_cfg_fn.stderr new file mode 100644 index 00000000..80ac12f8 --- /dev/null +++ b/codegen/ui_tests/module_cfg_fn.stderr @@ -0,0 +1,11 @@ +error: cfg attributes not allowed on this item + --> $DIR/module_cfg_fn.rs:11:5 + | +11 | #[cfg(not(feature = "foo"))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/module_cfg_fn.rs:22:8 + | +22 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/non_clonable.rs b/codegen/ui_tests/non_clonable.rs new file mode 100644 index 00000000..69aa85ef --- /dev/null +++ b/codegen/ui_tests/non_clonable.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +struct NonClonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_fn] +pub fn test_fn(input: NonClonable) -> bool { + input.d +} + +fn main() { + let n = NonClonable { + a: 0.0, + b: 10, + c: 'a', + d: true, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/non_clonable.stderr b/codegen/ui_tests/non_clonable.stderr new file mode 100644 index 00000000..dbae1d47 --- /dev/null +++ b/codegen/ui_tests/non_clonable.stderr @@ -0,0 +1,5 @@ +error[E0277]: the trait bound `NonClonable: std::clone::Clone` is not satisfied + --> $DIR/non_clonable.rs:11:23 + | +11 | pub fn test_fn(input: NonClonable) -> bool { + | ^^^^^^^^^^^ the trait `std::clone::Clone` is not implemented for `NonClonable` diff --git a/codegen/ui_tests/non_clonable_second.rs b/codegen/ui_tests/non_clonable_second.rs new file mode 100644 index 00000000..ef33d481 --- /dev/null +++ b/codegen/ui_tests/non_clonable_second.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +struct NonClonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_fn] +pub fn test_fn(a: u32, b: NonClonable) -> bool { + a == 0 && b.d +} + +fn main() { + let n = NonClonable { + a: 0.0, + b: 10, + c: 'a', + d: true, + }; + if test_fn(10, n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/non_clonable_second.stderr b/codegen/ui_tests/non_clonable_second.stderr new file mode 100644 index 00000000..26202d83 --- /dev/null +++ b/codegen/ui_tests/non_clonable_second.stderr @@ -0,0 +1,5 @@ +error[E0277]: the trait bound `NonClonable: std::clone::Clone` is not satisfied + --> $DIR/non_clonable_second.rs:11:27 + | +11 | pub fn test_fn(a: u32, b: NonClonable) -> bool { + | ^^^^^^^^^^^ the trait `std::clone::Clone` is not implemented for `NonClonable` diff --git a/codegen/ui_tests/return_mut_ref.rs b/codegen/ui_tests/return_mut_ref.rs new file mode 100644 index 00000000..26b56794 --- /dev/null +++ b/codegen/ui_tests/return_mut_ref.rs @@ -0,0 +1,28 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Clonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_fn] +pub fn test_fn(input: &mut Clonable) -> &mut bool { + &mut input.d +} + +fn main() { + let n = Clonable { + a: 0.0, + b: 10, + c: 'a', + d: true, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/return_mut_ref.stderr b/codegen/ui_tests/return_mut_ref.stderr new file mode 100644 index 00000000..c849a2c7 --- /dev/null +++ b/codegen/ui_tests/return_mut_ref.stderr @@ -0,0 +1,11 @@ +error: cannot return a reference to Rhai + --> $DIR/return_mut_ref.rs:12:38 + | +12 | pub fn test_fn(input: &mut Clonable) -> &mut bool { + | ^^^^^^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/return_mut_ref.rs:23:8 + | +23 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/return_pointer.rs b/codegen/ui_tests/return_pointer.rs new file mode 100644 index 00000000..302799bd --- /dev/null +++ b/codegen/ui_tests/return_pointer.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Clonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_fn] +pub fn test_fn(input: Clonable) -> *const str { + "yes" +} + +fn main() { + let n = Clonable { + a: 0.0, + b: 10, + c: 'a', + d: true, + }; + println!("{}", unsafe { + let ptr = test_fn(n); + *ptr + }); +} diff --git a/codegen/ui_tests/return_pointer.stderr b/codegen/ui_tests/return_pointer.stderr new file mode 100644 index 00000000..1b736db6 --- /dev/null +++ b/codegen/ui_tests/return_pointer.stderr @@ -0,0 +1,11 @@ +error: cannot return a pointer to Rhai + --> $DIR/return_pointer.rs:12:33 + | +12 | pub fn test_fn(input: Clonable) -> *const str { + | ^^^^^^^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/return_pointer.rs:24:19 + | +24 | let ptr = test_fn(n); + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/return_shared_ref.rs b/codegen/ui_tests/return_shared_ref.rs new file mode 100644 index 00000000..2fde9c25 --- /dev/null +++ b/codegen/ui_tests/return_shared_ref.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Clonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_fn] +pub fn test_fn(input: Clonable) -> &'static str { + "yes" +} + +fn main() { + let n = Clonable { + a: 0.0, + b: 10, + c: 'a', + d: true, + }; + println!("{}", test_fn(n)); +} diff --git a/codegen/ui_tests/return_shared_ref.stderr b/codegen/ui_tests/return_shared_ref.stderr new file mode 100644 index 00000000..13577531 --- /dev/null +++ b/codegen/ui_tests/return_shared_ref.stderr @@ -0,0 +1,11 @@ +error: cannot return a reference to Rhai + --> $DIR/return_shared_ref.rs:12:33 + | +12 | pub fn test_fn(input: Clonable) -> &'static str { + | ^^^^^^^^^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/return_shared_ref.rs:23:20 + | +23 | println!("{}", test_fn(n)); + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/rhai_fn_bad_attr.rs b/codegen/ui_tests/rhai_fn_bad_attr.rs new file mode 100644 index 00000000..119efdb3 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_bad_attr.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_fn(unknown = "thing")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_bad_attr.stderr b/codegen/ui_tests/rhai_fn_bad_attr.stderr new file mode 100644 index 00000000..3fe059dd --- /dev/null +++ b/codegen/ui_tests/rhai_fn_bad_attr.stderr @@ -0,0 +1,11 @@ +error: unknown attribute 'unknown' + --> $DIR/rhai_fn_bad_attr.rs:11:11 + | +11 | #[rhai_fn(unknown = "thing")] + | ^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_bad_attr.rs:22:8 + | +22 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_fn_bad_value.rs b/codegen/ui_tests/rhai_fn_bad_value.rs new file mode 100644 index 00000000..307e3c0e --- /dev/null +++ b/codegen/ui_tests/rhai_fn_bad_value.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_fn(name = true)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_bad_value.stderr b/codegen/ui_tests/rhai_fn_bad_value.stderr new file mode 100644 index 00000000..fedaed9b --- /dev/null +++ b/codegen/ui_tests/rhai_fn_bad_value.stderr @@ -0,0 +1,11 @@ +error: expecting string literal + --> $DIR/rhai_fn_bad_value.rs:11:18 + | +11 | #[rhai_fn(name = true)] + | ^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_bad_value.rs:22:8 + | +22 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_fn_extra_value.rs b/codegen/ui_tests/rhai_fn_extra_value.rs new file mode 100644 index 00000000..f86b2df3 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_extra_value.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_fn(return_raw = "yes")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_extra_value.stderr b/codegen/ui_tests/rhai_fn_extra_value.stderr new file mode 100644 index 00000000..0597f4c2 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_extra_value.stderr @@ -0,0 +1,11 @@ +error: extraneous value + --> $DIR/rhai_fn_extra_value.rs:11:24 + | +11 | #[rhai_fn(return_raw = "yes")] + | ^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_extra_value.rs:22:8 + | +22 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_fn_junk_arg.rs b/codegen/ui_tests/rhai_fn_junk_arg.rs new file mode 100644 index 00000000..b84424ae --- /dev/null +++ b/codegen/ui_tests/rhai_fn_junk_arg.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_fn("wheeeee")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_junk_arg.stderr b/codegen/ui_tests/rhai_fn_junk_arg.stderr new file mode 100644 index 00000000..e2054eef --- /dev/null +++ b/codegen/ui_tests/rhai_fn_junk_arg.stderr @@ -0,0 +1,11 @@ +error: expecting identifier + --> $DIR/rhai_fn_junk_arg.rs:11:11 + | +11 | #[rhai_fn("wheeeee")] + | ^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_junk_arg.rs:22:8 + | +22 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_fn_missing_value.rs b/codegen/ui_tests/rhai_fn_missing_value.rs new file mode 100644 index 00000000..25349530 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_missing_value.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_fn(name)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_missing_value.stderr b/codegen/ui_tests/rhai_fn_missing_value.stderr new file mode 100644 index 00000000..6ea8040a --- /dev/null +++ b/codegen/ui_tests/rhai_fn_missing_value.stderr @@ -0,0 +1,11 @@ +error: requires value + --> $DIR/rhai_fn_missing_value.rs:11:11 + | +11 | #[rhai_fn(name)] + | ^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_missing_value.rs:22:8 + | +22 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_fn_path_attr.rs b/codegen/ui_tests/rhai_fn_path_attr.rs new file mode 100644 index 00000000..5c2174a8 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_path_attr.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_fn(rhai::name = "thing")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_path_attr.stderr b/codegen/ui_tests/rhai_fn_path_attr.stderr new file mode 100644 index 00000000..5b471f16 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_path_attr.stderr @@ -0,0 +1,11 @@ +error: expecting attribute name + --> $DIR/rhai_fn_path_attr.rs:11:11 + | +11 | #[rhai_fn(rhai::name = "thing")] + | ^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_path_attr.rs:22:8 + | +22 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_fn_rename_collision.rs b/codegen/ui_tests/rhai_fn_rename_collision.rs new file mode 100644 index 00000000..38814f3d --- /dev/null +++ b/codegen/ui_tests/rhai_fn_rename_collision.rs @@ -0,0 +1,33 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + pub use super::Point; + #[rhai_fn(name = "foo")] + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } + + #[rhai_fn(name = "foo")] + pub fn test_fn_2(input: Point) -> bool { + input.x < input.y + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_rename_collision.stderr b/codegen/ui_tests/rhai_fn_rename_collision.stderr new file mode 100644 index 00000000..19ddfd35 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_rename_collision.stderr @@ -0,0 +1,17 @@ +error: duplicate Rhai signature for 'foo' + --> $DIR/rhai_fn_rename_collision.rs:17:15 + | +17 | #[rhai_fn(name = "foo")] + | ^^^^^^^^^^^^ + +error: duplicated function renamed 'foo' + --> $DIR/rhai_fn_rename_collision.rs:12:15 + | +12 | #[rhai_fn(name = "foo")] + | ^^^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_rename_collision.rs:28:8 + | +28 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_fn_rename_collision_oneattr.rs b/codegen/ui_tests/rhai_fn_rename_collision_oneattr.rs new file mode 100644 index 00000000..373eab0d --- /dev/null +++ b/codegen/ui_tests/rhai_fn_rename_collision_oneattr.rs @@ -0,0 +1,32 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + pub use super::Point; + #[rhai_fn(name = "foo")] + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } + + pub fn foo(input: Point) -> bool { + input.x < input.y + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_rename_collision_oneattr.stderr b/codegen/ui_tests/rhai_fn_rename_collision_oneattr.stderr new file mode 100644 index 00000000..6702b43a --- /dev/null +++ b/codegen/ui_tests/rhai_fn_rename_collision_oneattr.stderr @@ -0,0 +1,17 @@ +error: duplicate Rhai signature for 'foo' + --> $DIR/rhai_fn_rename_collision_oneattr.rs:12:15 + | +12 | #[rhai_fn(name = "foo")] + | ^^^^^^^^^^^^ + +error: duplicated function 'foo' + --> $DIR/rhai_fn_rename_collision_oneattr.rs:17:12 + | +17 | pub fn foo(input: Point) -> bool { + | ^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_rename_collision_oneattr.rs:27:8 + | +27 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_fn_rename_dot.rs b/codegen/ui_tests/rhai_fn_rename_dot.rs new file mode 100644 index 00000000..9e2180d9 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_rename_dot.rs @@ -0,0 +1,28 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + pub use super::Point; + #[rhai_fn(name = "foo.bar")] + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_rename_dot.stderr b/codegen/ui_tests/rhai_fn_rename_dot.stderr new file mode 100644 index 00000000..61299e8b --- /dev/null +++ b/codegen/ui_tests/rhai_fn_rename_dot.stderr @@ -0,0 +1,11 @@ +error: Rhai function names may not contain dot + --> $DIR/rhai_fn_rename_dot.rs:12:22 + | +12 | #[rhai_fn(name = "foo.bar")] + | ^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_rename_dot.rs:23:8 + | +23 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_bad_attr.rs b/codegen/ui_tests/rhai_mod_bad_attr.rs new file mode 100644 index 00000000..09319ce6 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_bad_attr.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod(unknown = "thing")] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_bad_attr.stderr b/codegen/ui_tests/rhai_mod_bad_attr.stderr new file mode 100644 index 00000000..87209038 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_bad_attr.stderr @@ -0,0 +1,11 @@ +error: unknown attribute 'unknown' + --> $DIR/rhai_mod_bad_attr.rs:11:12 + | +11 | #[rhai_mod(unknown = "thing")] + | ^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_bad_attr.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_bad_value.rs b/codegen/ui_tests/rhai_mod_bad_value.rs new file mode 100644 index 00000000..3d30fdcc --- /dev/null +++ b/codegen/ui_tests/rhai_mod_bad_value.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod(name = true)] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_bad_value.stderr b/codegen/ui_tests/rhai_mod_bad_value.stderr new file mode 100644 index 00000000..765f8a1f --- /dev/null +++ b/codegen/ui_tests/rhai_mod_bad_value.stderr @@ -0,0 +1,11 @@ +error: expecting string literal + --> $DIR/rhai_mod_bad_value.rs:11:19 + | +11 | #[rhai_mod(name = true)] + | ^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_bad_value.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_inner_cfg_false.rs b/codegen/ui_tests/rhai_mod_inner_cfg_false.rs new file mode 100644 index 00000000..cfb3e1b6 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_inner_cfg_false.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + #[cfg(feature = "unset_feature")] + pub mod test_mod { + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_inner_cfg_false.stderr b/codegen/ui_tests/rhai_mod_inner_cfg_false.stderr new file mode 100644 index 00000000..04068220 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_inner_cfg_false.stderr @@ -0,0 +1,5 @@ +error[E0433]: failed to resolve: could not find `test_mod` in `test_module` + --> $DIR/rhai_mod_inner_cfg_false.rs:24:21 + | +24 | if test_module::test_mod::test_fn(n) { + | ^^^^^^^^ could not find `test_mod` in `test_module` diff --git a/codegen/ui_tests/rhai_mod_junk_arg.rs b/codegen/ui_tests/rhai_mod_junk_arg.rs new file mode 100644 index 00000000..842c0bad --- /dev/null +++ b/codegen/ui_tests/rhai_mod_junk_arg.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod("wheeeee")] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_junk_arg.stderr b/codegen/ui_tests/rhai_mod_junk_arg.stderr new file mode 100644 index 00000000..c2dcc28a --- /dev/null +++ b/codegen/ui_tests/rhai_mod_junk_arg.stderr @@ -0,0 +1,11 @@ +error: expecting identifier + --> $DIR/rhai_mod_junk_arg.rs:11:12 + | +11 | #[rhai_mod("wheeeee")] + | ^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_junk_arg.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_missing_value.rs b/codegen/ui_tests/rhai_mod_missing_value.rs new file mode 100644 index 00000000..f231b65d --- /dev/null +++ b/codegen/ui_tests/rhai_mod_missing_value.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod(name)] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_missing_value.stderr b/codegen/ui_tests/rhai_mod_missing_value.stderr new file mode 100644 index 00000000..62e0502f --- /dev/null +++ b/codegen/ui_tests/rhai_mod_missing_value.stderr @@ -0,0 +1,11 @@ +error: requires value + --> $DIR/rhai_mod_missing_value.rs:11:12 + | +11 | #[rhai_mod(name)] + | ^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_missing_value.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_name_collisions.rs b/codegen/ui_tests/rhai_mod_name_collisions.rs new file mode 100644 index 00000000..c7709555 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_name_collisions.rs @@ -0,0 +1,31 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + pub use super::Point; + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } + + pub fn test_fn(input: Point) -> bool { + input.x < input.y + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_name_collisions.stderr b/codegen/ui_tests/rhai_mod_name_collisions.stderr new file mode 100644 index 00000000..539bd3eb --- /dev/null +++ b/codegen/ui_tests/rhai_mod_name_collisions.stderr @@ -0,0 +1,17 @@ +error: duplicate function 'test_fn' + --> $DIR/rhai_mod_name_collisions.rs:16:12 + | +16 | pub fn test_fn(input: Point) -> bool { + | ^^^^^^^ + +error: duplicated function 'test_fn' + --> $DIR/rhai_mod_name_collisions.rs:12:12 + | +12 | pub fn test_fn(input: Point) -> bool { + | ^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_name_collisions.rs:26:8 + | +26 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_path_attr.rs b/codegen/ui_tests/rhai_mod_path_attr.rs new file mode 100644 index 00000000..43be9604 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_path_attr.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod(rhai::name = "thing")] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_path_attr.stderr b/codegen/ui_tests/rhai_mod_path_attr.stderr new file mode 100644 index 00000000..bd165324 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_path_attr.stderr @@ -0,0 +1,11 @@ +error: expecting attribute name + --> $DIR/rhai_mod_path_attr.rs:11:12 + | +11 | #[rhai_mod(rhai::name = "thing")] + | ^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_path_attr.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_return_raw.rs b/codegen/ui_tests/rhai_mod_return_raw.rs new file mode 100644 index 00000000..e5fed7e0 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_return_raw.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod(return_raw = "yes")] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_return_raw.stderr b/codegen/ui_tests/rhai_mod_return_raw.stderr new file mode 100644 index 00000000..f767dce0 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_return_raw.stderr @@ -0,0 +1,11 @@ +error: unknown attribute 'return_raw' + --> $DIR/rhai_mod_return_raw.rs:11:12 + | +11 | #[rhai_mod(return_raw = "yes")] + | ^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_return_raw.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_unknown_type.rs b/codegen/ui_tests/rhai_mod_unknown_type.rs new file mode 100644 index 00000000..7c19ab18 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_unknown_type.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + pub use super::Point; + pub fn test_fn(input: Pointer) -> bool { + input.x < input.y + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_unknown_type.stderr b/codegen/ui_tests/rhai_mod_unknown_type.stderr new file mode 100644 index 00000000..392f90a9 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_unknown_type.stderr @@ -0,0 +1,23 @@ +error[E0412]: cannot find type `Pointer` in this scope + --> $DIR/rhai_mod_unknown_type.rs:12:27 + | +4 | pub struct Point { + | ---------------- similarly named struct `Point` defined here +... +12 | pub fn test_fn(input: Pointer) -> bool { + | ^^^^^^^ + | +help: a struct with a similar name exists + | +12 | pub fn test_fn(input: Point) -> bool { + | ^^^^^ +help: consider importing one of these items + | +11 | use core::fmt::Pointer; + | +11 | use crate::mem::fmt::Pointer; + | +11 | use std::fmt::Pointer; + | +11 | use syn::export::fmt::Pointer; + | diff --git a/codegen/ui_tests/rhai_mod_unknown_type_return.rs b/codegen/ui_tests/rhai_mod_unknown_type_return.rs new file mode 100644 index 00000000..c2287eaa --- /dev/null +++ b/codegen/ui_tests/rhai_mod_unknown_type_return.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + pub use super::Point; + pub fn test_fn(input: Point) -> boool { + input.x < input.y + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_unknown_type_return.stderr b/codegen/ui_tests/rhai_mod_unknown_type_return.stderr new file mode 100644 index 00000000..43f2896c --- /dev/null +++ b/codegen/ui_tests/rhai_mod_unknown_type_return.stderr @@ -0,0 +1,5 @@ +error[E0412]: cannot find type `boool` in this scope + --> $DIR/rhai_mod_unknown_type_return.rs:12:37 + | +12 | pub fn test_fn(input: Point) -> boool { + | ^^^^^ help: a builtin type with a similar name exists: `bool` diff --git a/codegen/ui_tests/second_shared_ref.rs b/codegen/ui_tests/second_shared_ref.rs new file mode 100644 index 00000000..4768c065 --- /dev/null +++ b/codegen/ui_tests/second_shared_ref.rs @@ -0,0 +1,28 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Clonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_fn] +pub fn test_fn(input: Clonable, factor: &bool) -> bool { + input.d & factor +} + +fn main() { + let n = Clonable { + a: 0.0, + b: 10, + c: 'a', + d: true, + }; + if test_fn(n, &true) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/second_shared_ref.stderr b/codegen/ui_tests/second_shared_ref.stderr new file mode 100644 index 00000000..91b493ed --- /dev/null +++ b/codegen/ui_tests/second_shared_ref.stderr @@ -0,0 +1,11 @@ +error: this type in this position passes from Rhai by value + --> $DIR/second_shared_ref.rs:12:41 + | +12 | pub fn test_fn(input: Clonable, factor: &bool) -> bool { + | ^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/second_shared_ref.rs:23:8 + | +23 | if test_fn(n, &true) { + | ^^^^^^^ not found in this scope diff --git a/diag_test/Cargo.toml b/diag_test/Cargo.toml new file mode 100644 index 00000000..17998ecf --- /dev/null +++ b/diag_test/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "diag_test" +version = "0.1.0" +authors = ["J Henry Waugh "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "test_template" +path = "test_template.rs" + +[dependencies] +rhai = { version = "*", path = ".." } diff --git a/diag_test/test_template.rs b/diag_test/test_template.rs new file mode 100644 index 00000000..98088637 --- /dev/null +++ b/diag_test/test_template.rs @@ -0,0 +1,35 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + #[rhai_mod(name = "bar")] + pub mod test_mod { + #[rhai_fn(name = "foo")] + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } + + #[rhai_fn(return_raw)] + pub fn test_fn_raw(input: Point) -> Result> { + Ok(Dynamic::from(input.x > input.y)) + } + } +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_mod::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 59584d28..c6457b52 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -26,15 +26,14 @@ The Rhai Scripting Language 4. [Create a Rust Closure from a Rhai Function](engine/func.md) 5. [Evaluate Expressions Only](engine/expressions.md) 6. [Raw Engine](engine/raw.md) + 7. [Scope - Initializing and Maintaining State](engine/scope.md) + 8. [Engine Configuration Options](engine/options.md) 4. [Extend Rhai with Rust](rust/index.md) 1. [Traits](rust/traits.md) 2. [Register a Rust Function](rust/functions.md) 1. [String Parameters in Rust Functions](rust/strings.md) 3. [Register a Generic Rust Function](rust/generic.md) 4. [Register a Fallible Rust Function](rust/fallible.md) - 5. [Packages](rust/packages/index.md) - 1. [Built-in Packages](rust/packages/builtin.md) - 2. [Create a Custom Package](rust/packages/create.md) 6. [Override a Built-in Function](rust/override.md) 7. [Operator Overloading](rust/operators.md) 8. [Register a Custom Type and its Methods](rust/custom.md) @@ -42,8 +41,13 @@ The Rhai Scripting Language 2. [Indexers](rust/indexers.md) 3. [Disable Custom Types](rust/disable-custom.md) 4. [Printing Custom Types](rust/print-custom.md) - 9. [Scope - Initializing and Maintaining State](rust/scope.md) - 10. [Engine Configuration Options](rust/options.md) + 9. [Packages](rust/packages/index.md) + 1. [Built-in Packages](rust/packages/builtin.md) + 2. [Load a Plugin Module as a Package](rust/packages/plugin.md) + 3. [Manually Create a Custom Package](rust/packages/create.md) + 10. [Plugins](plugins/index.md) + 1. [Create a Plugin Module](plugins/module.md) + 2. [Create a Plugin Function](plugins/function.md) 5. [Rhai Language Reference](language/index.md) 1. [Comments](language/comments.md) 2. [Values and Types](language/values-and-types.md) @@ -84,7 +88,7 @@ The Rhai Scripting Language 17. [Modules](language/modules/index.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) 2. [Import Modules](language/modules/import.md) - 3. [Create from Rust](rust/modules/index.md) + 3. [Create from Rust](rust/modules/create.md) 4. [Create from AST](language/modules/ast.md) 5. [Module Resolvers](rust/modules/resolvers.md) 1. [Custom Implementation](rust/modules/imp-resolver.md) diff --git a/doc/src/about/features.md b/doc/src/about/features.md index d0624c2b..cacc1885 100644 --- a/doc/src/about/features.md +++ b/doc/src/about/features.md @@ -14,7 +14,7 @@ Easy * Easily [call a script-defined function]({{rootUrl}}/engine/call-fn.md) from Rust. -* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) to do checked arithmetic operations, and [`smallvec`](https://crates.io/crates/smallvec/)); +* Very few additional dependencies (right now only [`smallvec`](https://crates.io/crates/smallvec/)); for [`no-std`] builds, a number of additional dependencies are pulled in to provide for functionalities that used to be in `std`. Fast @@ -66,6 +66,8 @@ Flexible * Supports [most build targets](targets.md) including `no-std` and [WASM]. +* [Plugins] system powered by procedural macros simplifies custom API development. + * Surgically [disable keywords and operators] to restrict the language. * Use as a [DSL] by [disabling keywords/operators][disable keywords and operators], [custom operators] diff --git a/doc/src/rust/options.md b/doc/src/engine/options.md similarity index 100% rename from doc/src/rust/options.md rename to doc/src/engine/options.md diff --git a/doc/src/rust/scope.md b/doc/src/engine/scope.md similarity index 100% rename from doc/src/rust/scope.md rename to doc/src/engine/scope.md diff --git a/doc/src/language/num-fn.md b/doc/src/language/num-fn.md index a963c7e2..c9427a35 100644 --- a/doc/src/language/num-fn.md +++ b/doc/src/language/num-fn.md @@ -9,11 +9,11 @@ Integer Functions The following standard functions (defined in the [`BasicMathPackage`][packages] but excluded if using a [raw `Engine`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: -| Function | Description | -| ------------ | --------------------------------------------------------------- | -| `abs` | absolute value | -| `sign` | returns -1 if the number is negative, +1 if positive, 0 if zero | -| [`to_float`] | converts an integer type to `f64` | +| Function | Description | +| ------------ | ----------------------------------------------------------------------- | +| `abs` | absolute value | +| `sign` | returns -1 (`INT`) if the number is negative, +1 if positive, 0 if zero | +| [`to_float`] | converts an integer type to `FLOAT` | Floating-Point Functions ----------------------- diff --git a/doc/src/links.md b/doc/src/links.md index 953c1164..0231689b 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -33,7 +33,13 @@ [packages]: {{rootUrl}}/rust/packages/index.md [custom package]: {{rootUrl}}/rust/packages/create.md [custom packages]: {{rootUrl}}/rust/packages/create.md -[`Scope`]: {{rootUrl}}/rust/scope.md +[plugin]: {{rootUrl}}/plugins/index.md +[plugins]: {{rootUrl}}/plugins/index.md +[plugin module]: {{rootUrl}}/plugins/module.md +[plugin modules]: {{rootUrl}}/plugins/module.md +[plugin function]: {{rootUrl}}/plugins/function.md +[plugin functions]: {{rootUrl}}/plugins/function.md +[`Scope`]: {{rootUrl}}/engine/scope.md [`serde`]: {{rootUrl}}/rust/serde.md [`type_of()`]: {{rootUrl}}/language/type-of.md @@ -79,6 +85,7 @@ [function]: {{rootUrl}}/language/functions.md [functions]: {{rootUrl}}/language/functions.md [function overloading]: {{rootUrl}}/rust/functions.md#function-overloading +[fallible function]: {{rootUrl}}/rust/fallible.md [fallible functions]: {{rootUrl}}/rust/fallible.md [function pointer]: {{rootUrl}}/language/fn-ptr.md [function pointers]: {{rootUrl}}/language/fn-ptr.md diff --git a/doc/src/plugins/function.md b/doc/src/plugins/function.md new file mode 100644 index 00000000..43af9e13 --- /dev/null +++ b/doc/src/plugins/function.md @@ -0,0 +1,78 @@ +Create a Plugin Function +======================== + +{{#include ../links.md}} + + +Sometimes only a few ad hoc functions are required and it is simpler to register +individual functions instead of a full-blown [plugin module]. + + +Macros +------ + +| Macro | Apply to | Behavior | +| ------------------------ | ------------------------------------------------------------- | --------------------------------------------------------- | +| `#[export_fn]` | Rust function defined in module | Export the function | +| `#[rhai_fn(return_raw)]` | Rust function returning `Result>` | Specify that this is a [fallible function] | +| `register_exported_fn!` | [`Engine`] instance, register name, function name | Register function into the [`Engine`] under specific name | +| `set_exported_fn!` | [`Module`], register name, function name | Register function into the [`Module`] under specific name | + + +`#[export_fn]` and `register_exported_fn!` +----------------------------------------- + +Apply `#[export_fn]` onto a function defined at _module level_ to convert it into a Rhai plugin function. + +The function cannot be nested inside another function - it can only be defined directly under a module. + +To register the plugin function, simply call `register_exported_fn!`. The name of the function can be +any text string, so it is possible to register _overloaded_ functions as well as operators. + +```rust +use rhai::plugins::*; // import macros + +#[export_fn] +fn increment(num: &mut i64) { + *num += 1; +} + +fn main() { + let mut engine = Engine::new(); + + // 'register_exported_fn!' registers the function as 'inc' with the Engine. + register_exported_fn!(engine, "inc", increment); +} +``` + + +Fallible Functions +------------------ + +To register [fallible functions] (i.e. functions that may return errors), apply the +`#[rhai_fn(return_raw)]` attribute on plugin functions that return `Result>`. + +A syntax error is generated if the function with `#[rhai_fn(return_raw)]` does not +have the appropriate return type. + +```rust +use rhai::plugins::*; // import macros + +#[export_fn] +#[rhai_fn(return_raw)] +pub fn double_and_divide(x: i64, y: i64) -> Result> { + if y == 0 { + Err("Division by zero!".into()) + } else { + let result = (x * 2) / y; + Ok(result.into()) + } +} + +fn main() { + let mut engine = Engine::new(); + + // Overloads the operator '+' with the Engine. + register_exported_fn!(engine, "+", double_and_divide); +} +``` diff --git a/doc/src/plugins/index.md b/doc/src/plugins/index.md new file mode 100644 index 00000000..353dedd3 --- /dev/null +++ b/doc/src/plugins/index.md @@ -0,0 +1,11 @@ +Plugins +======= + +{{#include ../links.md}} + +Rhai contains a robust _plugin_ system that greatly simplifies registration of custom functions. + +Instead of the large `Engine::register_XXX` API, and the parallel `Module::set_fn_XXX` API, +a _plugin_ simplifies the work of creating and registering multiple functions into an [`Engine`]. + +Plugins are processed via a set of procedural macros under the `rhai::plugins` module. diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md new file mode 100644 index 00000000..22380425 --- /dev/null +++ b/doc/src/plugins/module.md @@ -0,0 +1,194 @@ +Create a Plugin Module +====================== + +{{#include ../links.md}} + + +The core of creating a plugin [module] is the `#[export_module]` attribute. + +When applied on a module definition, `#[export_module]` automatically generates Rhai-acceptable +functions from all `pub` functions defined within. + +The resulting module can then be loaded into an [`Engine`] as a normal [module], +or as a [custom package]. + + +Macros +------ + +| Macro | Apply to | Behavior | +| --------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------- | +| `#[export_module]` | Rust module | Export all `pub` functions | +| `#[rhai_fn(skip)]` | Function in Rust module | Do not export this function | +| `#[rhai_fn(return_raw)]` | `pub` function in Rust module returning `Result>` | Specify that this is a [fallible function] | +| `#[rhai_fn(name = "...")]` | `pub` function in Rust module | Register function under specific name | +| `#[rhai_fn(get = "...")]` | `pub` function in Rust module (first parameter must be `&mut`) | Register a property getter under specific name | +| `#[rhai_fn(set = "...")]` | `pub` function in Rust module (first parameter must be `&mut`) | Register a property setter under specific name | +| `#[rhai_fn(index_get]` | `pub` function in Rust module (first parameter must be `&mut`) | Register an index getter | +| `#[rhai_fn(index_set)]` | `pub` function in Rust module (first parameter must be `&mut`) | Register an index setter | +| `#[rhai_mod(name = "...")]` | `pub` sub-module in Rust module | Export the sub-module under specific name | +| `exported_module!` | Rust module name | Create a [module] containing exported functions | + + +`#[export_module]` and `exported_module!` +---------------------------------------- + +Apply `#[export_module]` onto a standard module to convert all `pub` functions +into Rhai plugin functions. + +```rust +use rhai::plugins::*; // import macros + +#[export_module] +mod my_module { + // This function will be registered as 'greet'. + pub fn greet(name: &str) -> String { + format!("hello, {}!", name) + } + // This function will be registered as 'get_num'. + pub fn get_num() -> i64 { + mystic_number() + } + // This function will be registered as 'increment'. + pub fn increment(num: &mut i64) { + *num += 1; + } + // This function is NOT registered. + fn mystic_number() -> i64 { + 42 + } +} + +fn main() { + let mut engine = Engine::new(); + + // 'exported_module!' creates the plugin module. + let module = exported_module!(my_module); + + // A module can simply be loaded as a custom package. + engine.load_package(module); +} +``` + +The above automatically defines a plugin module named `my_module` which can be converted into +a Rhai [module] via `exported_module!`. The functions contained within the module definition +(i.e. `greet`, `get_num` and `increment`) are automatically registered into the [`Engine`] when +`Engine::load_package` is called. + +```rust +let x = greet("world"); +x == "hello, world!"; + +let x = greet(get_num().to_string()); +x == "hello, 42!"; + +let x = get_num(); +x == 42; + +increment(x); +x == 43; +``` + + +Getters, Setters and Indexers +----------------------------- + +Functions can be marked as [getters/setters] and [indexers] for [custom types] via the `#[rhai_fn]` +attribute, which is applied on a function level. + +```rust +use rhai::plugins::*; // import macros + +#[export_module] +mod my_module { + // This is a normal function 'greet'. + pub fn greet(name: &str) -> String { + format!("hello, {}!", name) + } + // This is a getter for 'MyType::prop'. + #[rhai_fn(get = "prop")] + pub fn get_prop(obj: &mut MyType) -> i64 { + obj.prop + } + // This is a setter for 'MyType::prop'. + #[rhai_fn(set = "prop")] + pub fn set_prop(obj: &mut MyType, value: i64) { + obj.prop = value; + } + // This is an index getter for 'MyType'. + #[rhai_fn(index_get)] + pub fn get_index(obj: &mut MyType, index: i64) -> bool { + obj.list[index] + } + // This is an index setter for 'MyType'. + #[rhai_fn(index_set)] + pub fn get_index(obj: &mut MyType, index: i64, state: bool) { + obj.list[index] = state; + } +} +``` + + +Function Overloading and Operators +--------------------------------- + +Operators and overloaded functions can be specified via `#[rhai_fn(name = "...")]` applied upon +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. + +Operators (which require function names that are not valid for Rust) can also be registered this way. + +```rust +use rhai::plugins::*; // import macros + +#[export_module] +mod my_module { + // This is the '+' operator for 'MyType'. + #[rhai_fn(name = "+")] + pub fn add(obj: &mut MyType, value: i64) { + obj.prop += value; + } + // This function is 'calc (i64)'. + #[rhai_fn(name = "calc")] + pub fn calc_with_default(num: i64) -> i64 { + ... + } + // This function is 'calc (i64, bool)'. + #[rhai_fn(name = "calc")] + pub fn calc_with_option(num: i64, option: bool) -> i64 { + ... + } +} +``` + + +Fallible Functions +------------------ + +To register [fallible functions] (i.e. functions that may return errors), apply the +`#[rhai_fn(return_raw)]` attribute on functions that return `Result>`. + +A syntax error is generated if the function with `#[rhai_fn(return_raw)]` does not +have the appropriate return type. + +```rust +use rhai::plugins::*; // import macros + +#[export_module] +mod my_module { + // This overloads the '/' operator for i64. + #[rhai_fn(name = "/", return_raw)] + pub fn double_and_divide(x: i64, y: i64) -> Result> { + if y == 0 { + Err("Division by zero!".into()) + } else { + let result = (x * 2) / y; + Ok(result.into()) + } + } +} +``` diff --git a/doc/src/rust/modules/index.md b/doc/src/rust/modules/create.md similarity index 81% rename from doc/src/rust/modules/index.md rename to doc/src/rust/modules/create.md index be6576bb..26441067 100644 --- a/doc/src/rust/modules/index.md +++ b/doc/src/rust/modules/create.md @@ -3,13 +3,23 @@ Create a Module from Rust {{#include ../../links.md}} -Manually creating a [`Module`] is possible via the `Module` API. + +Create via Plugin +----------------- + +By far the simplest way to create a [module] is via a [plugin module]. + + +Create via `Module` API +----------------------- + +Manually creating a [module] is possible via the `Module` API. For the complete `Module` API, refer to the [documentation](https://docs.rs/rhai/{{version}}/rhai/struct.Module.html) online. -Make the Module Available to the Engine --------------------------------------- +Make the `Module` Available to the `Engine` +------------------------------------------ In order to _use_ a custom module, there must be a [module resolver]. diff --git a/doc/src/rust/packages/create.md b/doc/src/rust/packages/create.md index 89f0e306..d212d38f 100644 --- a/doc/src/rust/packages/create.md +++ b/doc/src/rust/packages/create.md @@ -1,5 +1,5 @@ -Create a Custom Package -====================== +Manually Create a Custom Package +=============================== {{#include ../../links.md}} @@ -13,7 +13,7 @@ Loading a package into an [`Engine`] is functionally equivalent to calling `Engi on _each_ of the functions inside the package. But because packages are _shared_, loading an existing package is _much_ cheaper than registering all the functions one by one. -The macro `rhai::def_package!` is used to create a new custom package. +The macro `rhai::def_package!` can be used to create a new custom package. Macro Parameters @@ -53,3 +53,48 @@ 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]. + +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. + +```rust +// Import necessary types and traits. +use rhai::{ + def_package, + packages::Package, + packages::{ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage} +}; +use rhai::plugin::*; + +// Define plugin module. +#[export_module] +mod my_module { + pub fn greet(name: &str) -> String { + format!("hello, {}!", name) + } + pub fn get_num() -> i64 { + 42 + } +} + +// Define the package 'MyPackage'. +def_package!(rhai:MyPackage:"My own personal super package", module, { + // Aggregate existing packages simply by calling 'init' on each. + ArithmeticPackage::init(module); + LogicPackage::init(module); + BasicArrayPackage::init(module); + BasicMapPackage::init(module); + + // Merge the plugin module into the custom package. + module.merge_flatten(exported_module!(my_module)); +}); +``` diff --git a/doc/src/rust/packages/index.md b/doc/src/rust/packages/index.md index 0b41656f..ee0daa37 100644 --- a/doc/src/rust/packages/index.md +++ b/doc/src/rust/packages/index.md @@ -11,7 +11,7 @@ packages to be used. Packages typically contain Rust functions that are callable within a Rhai script. All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers). -Once a package is created (e.g. via `new`), it can be _shared_ (via `get`) among multiple instances of [`Engine`], +Once a package is created (e.g. via `Package::new`), it can be _shared_ (via `Package::get`) among multiple instances of [`Engine`], even across threads (under [`sync`]). Therefore, a package only has to be created _once_. ```rust @@ -36,3 +36,21 @@ 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). + + +Load a Module as a Package +-------------------------- + +Stand-alone [modules] can be loaded directly into an [`Engine`] as a package via the same `Engine::load_package` API. + +```rust +let mut module = Module::new(); + : + // add functions into module + : + +engine.load_package(module); +``` + +[Modules], however, are not _shared_, so use a [custom package] if it must be shared among multiple +instances of [`Engine`]. diff --git a/doc/src/rust/packages/plugin.md b/doc/src/rust/packages/plugin.md new file mode 100644 index 00000000..42bc9519 --- /dev/null +++ b/doc/src/rust/packages/plugin.md @@ -0,0 +1,41 @@ +Load a Plugin Module as a Package +================================ + +{{#include ../../links.md}} + +[Plugin modules] can be loaded as a package just like a normal [module]. + +```rust +use rhai::Engine; +use rhai::plugin::*; + +// Define plugin module. +#[export_module] +mod my_module { + pub fn greet(name: &str) -> String { + format!("hello, {}!", name) + } + pub fn get_num() -> i64 { + 42 + } +} + +fn main() { + let mut engine = Engine::new(); + + // Create plugin module. + let module = exported_module!(my_module); + + // Make the 'greet' and 'get_num' functions available to scripts. + engine.load_package(module); +} +``` + + +Share a Package Among `Engine`s +------------------------------ + +Loading a [module] via `Engine::load_package` consumes the module and it cannot be used again. + +If the functions are needed for multiple instances of [`Engine`], create a [custom package] from the +[plugin module], which can then be shared with `Package::get`. diff --git a/src/any.rs b/src/any.rs index c154a682..7b1fa184 100644 --- a/src/any.rs +++ b/src/any.rs @@ -535,45 +535,47 @@ impl Dynamic { /// ``` #[inline(always)] pub fn from(value: T) -> Self { - let type_id = TypeId::of::(); - - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return ::downcast_ref::(&value) .unwrap() .clone() .into(); } #[cfg(not(feature = "no_float"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return ::downcast_ref::(&value) .unwrap() .clone() .into(); } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return ::downcast_ref::(&value) .unwrap() .clone() .into(); } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return ::downcast_ref::(&value) .unwrap() .clone() .into(); } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return ::downcast_ref::(&value) .unwrap() .clone() .into(); } - if type_id == TypeId::of::<()>() { + if TypeId::of::() == TypeId::of::<()>() { return ().into(); } let mut boxed = Box::new(value); + boxed = match unsafe_cast_box::<_, Dynamic>(boxed) { + Ok(d) => return *d, + Err(val) => val, + }; boxed = match unsafe_cast_box::<_, String>(boxed) { Ok(s) => return (*s).into(), Err(val) => val, @@ -599,11 +601,6 @@ impl Dynamic { Err(val) => val, }; - boxed = match unsafe_cast_box::<_, Dynamic>(boxed) { - Ok(d) => return *d, - Err(val) => val, - }; - Self(Union::Variant(Box::new(boxed))) } @@ -660,8 +657,6 @@ impl Dynamic { /// ``` #[inline(always)] pub fn try_cast(self) -> Option { - let type_id = TypeId::of::(); - match self.0 { #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] @@ -673,11 +668,11 @@ impl Dynamic { _ => (), } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Int(value) => unsafe_try_cast(value), _ => None, @@ -685,35 +680,35 @@ impl Dynamic { } #[cfg(not(feature = "no_float"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Float(value) => unsafe_try_cast(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Bool(value) => unsafe_try_cast(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Str(value) => unsafe_try_cast(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Str(value) => unsafe_try_cast(value.into_owned()), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Char(value) => unsafe_try_cast(value), _ => None, @@ -721,7 +716,7 @@ impl Dynamic { } #[cfg(not(feature = "no_index"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), _ => None, @@ -729,21 +724,21 @@ impl Dynamic { } #[cfg(not(feature = "no_object"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match self.0 { Union::FnPtr(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), _ => None, }; } - if type_id == TypeId::of::<()>() { + if TypeId::of::() == TypeId::of::<()>() { return match self.0 { Union::Unit(value) => unsafe_try_cast(value), _ => None, @@ -928,72 +923,70 @@ impl Dynamic { /// Returns `None` if the cast fails, or if the value is shared. #[inline(always)] pub(crate) fn downcast_ref(&self) -> Option<&T> { - let type_id = TypeId::of::(); - - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::Int(value) => ::downcast_ref::(value), _ => None, }; } #[cfg(not(feature = "no_float"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::Float(value) => ::downcast_ref::(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::Bool(value) => ::downcast_ref::(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::Str(value) => ::downcast_ref::(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::Str(value) => ::downcast_ref::(value.as_ref()), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::Char(value) => ::downcast_ref::(value), _ => None, }; } #[cfg(not(feature = "no_index"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::Array(value) => ::downcast_ref::(value.as_ref()), _ => None, }; } #[cfg(not(feature = "no_object"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::Map(value) => ::downcast_ref::(value.as_ref()), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &self.0 { Union::FnPtr(value) => ::downcast_ref::(value.as_ref()), _ => None, }; } - if type_id == TypeId::of::<()>() { + if TypeId::of::() == TypeId::of::<()>() { return match &self.0 { Union::Unit(value) => ::downcast_ref::(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return ::downcast_ref::(self); } @@ -1011,66 +1004,64 @@ impl Dynamic { /// Returns `None` if the cast fails, or if the value is shared. #[inline(always)] pub(crate) fn downcast_mut(&mut self) -> Option<&mut T> { - let type_id = TypeId::of::(); - - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &mut self.0 { Union::Int(value) => ::downcast_mut::(value), _ => None, }; } #[cfg(not(feature = "no_float"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &mut self.0 { Union::Float(value) => ::downcast_mut::(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &mut self.0 { Union::Bool(value) => ::downcast_mut::(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &mut self.0 { Union::Str(value) => ::downcast_mut::(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &mut self.0 { Union::Char(value) => ::downcast_mut::(value), _ => None, }; } #[cfg(not(feature = "no_index"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &mut self.0 { Union::Array(value) => ::downcast_mut::(value.as_mut()), _ => None, }; } #[cfg(not(feature = "no_object"))] - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &mut self.0 { Union::Map(value) => ::downcast_mut::(value.as_mut()), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return match &mut self.0 { Union::FnPtr(value) => ::downcast_mut::(value.as_mut()), _ => None, }; } - if type_id == TypeId::of::<()>() { + if TypeId::of::() == TypeId::of::<()>() { return match &mut self.0 { Union::Unit(value) => ::downcast_mut::(value), _ => None, }; } - if type_id == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return ::downcast_mut::(self); } diff --git a/src/fn_call.rs b/src/fn_call.rs index f22b89bc..e7c5fdc7 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -231,7 +231,11 @@ impl Engine { backup.change_first_arg_to_copy(is_ref && func.is_pure(), args); // Run external function - let result = func.get_native_fn()(self, lib, args); + let result = if func.is_plugin_fn() { + func.get_plugin_fn().call(args, Position::none()) + } else { + func.get_native_fn()(self, lib, args) + }; // Restore the original reference backup.restore_first_arg(args); @@ -1078,6 +1082,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_native() => { if !f.is_method() { // Clone first argument @@ -1119,7 +1124,7 @@ pub fn run_builtin_binary_op( x: &Dynamic, y: &Dynamic, ) -> Result, Box> { - use crate::packages::arithmetic::*; + use crate::packages::arithmetic::arith_basic::INT::functions::*; let args_type = x.type_id(); @@ -1133,14 +1138,14 @@ pub fn run_builtin_binary_op( if cfg!(not(feature = "unchecked")) { match op { - "+" => return add(x, y).map(Into::into).map(Some), - "-" => return sub(x, y).map(Into::into).map(Some), - "*" => return mul(x, y).map(Into::into).map(Some), - "/" => return div(x, y).map(Into::into).map(Some), - "%" => return modulo(x, y).map(Into::into).map(Some), - "~" => return pow_i_i(x, y).map(Into::into).map(Some), - ">>" => return shr(x, y).map(Into::into).map(Some), - "<<" => return shl(x, y).map(Into::into).map(Some), + "+" => return add(x, y).map(Some), + "-" => return subtract(x, y).map(Some), + "*" => return multiply(x, y).map(Some), + "/" => return divide(x, y).map(Some), + "%" => return modulo(x, y).map(Some), + "~" => return power(x, y).map(Some), + ">>" => return shift_right(x, y).map(Some), + "<<" => return shift_left(x, y).map(Some), _ => (), } } else { @@ -1247,7 +1252,7 @@ pub fn run_builtin_op_assignment( x: &mut Dynamic, y: &Dynamic, ) -> Result, Box> { - use crate::packages::arithmetic::*; + use crate::packages::arithmetic::arith_basic::INT::functions::*; let args_type = x.type_id(); @@ -1261,14 +1266,14 @@ pub fn run_builtin_op_assignment( if cfg!(not(feature = "unchecked")) { match op { - "+=" => return Ok(Some(*x = add(*x, y)?)), - "-=" => return Ok(Some(*x = sub(*x, y)?)), - "*=" => return Ok(Some(*x = mul(*x, y)?)), - "/=" => return Ok(Some(*x = div(*x, y)?)), - "%=" => return Ok(Some(*x = modulo(*x, y)?)), - "~=" => return Ok(Some(*x = pow_i_i(*x, y)?)), - ">>=" => return Ok(Some(*x = shr(*x, y)?)), - "<<=" => return Ok(Some(*x = shl(*x, y)?)), + "+=" => return Ok(Some(*x = add(*x, y)?.as_int().unwrap())), + "-=" => return Ok(Some(*x = subtract(*x, y)?.as_int().unwrap())), + "*=" => return Ok(Some(*x = multiply(*x, y)?.as_int().unwrap())), + "/=" => return Ok(Some(*x = divide(*x, y)?.as_int().unwrap())), + "%=" => return Ok(Some(*x = modulo(*x, y)?.as_int().unwrap())), + "~=" => return Ok(Some(*x = power(*x, y)?.as_int().unwrap())), + ">>=" => return Ok(Some(*x = shift_right(*x, y)?.as_int().unwrap())), + "<<=" => return Ok(Some(*x = shift_left(*x, y)?.as_int().unwrap())), _ => (), } } else { diff --git a/src/fn_native.rs b/src/fn_native.rs index 3b8e040a..820e5dc7 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -4,6 +4,7 @@ use crate::any::Dynamic; use crate::engine::Engine; use crate::module::Module; use crate::parser::{FnAccess, ScriptFnDef}; +use crate::plugin::PluginFunction; use crate::result::EvalAltResult; use crate::token::{is_valid_identifier, Position}; use crate::utils::ImmutableString; @@ -198,6 +199,11 @@ pub type FnAny = /// A standard function that gets an iterator from a type. pub type IteratorFn = fn(Dynamic) -> Box>; +#[cfg(feature = "sync")] +pub type SharedPluginFunction = Arc; +#[cfg(not(feature = "sync"))] +pub type SharedPluginFunction = Rc; + /// A standard callback function. #[cfg(not(feature = "sync"))] pub type Callback = Box R + 'static>; @@ -215,6 +221,8 @@ pub enum CallableFunction { Method(Shared), /// An iterator function. Iterator(IteratorFn), + /// A plugin-defined function, + Plugin(SharedPluginFunction), /// A script-defined function. #[cfg(not(feature = "no_function"))] Script(Shared), @@ -226,6 +234,7 @@ impl fmt::Debug for CallableFunction { Self::Pure(_) => write!(f, "NativePureFunction"), Self::Method(_) => write!(f, "NativeMethod"), Self::Iterator(_) => write!(f, "NativeIterator"), + Self::Plugin(_) => write!(f, "PluginFunction"), #[cfg(not(feature = "no_function"))] Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f), @@ -239,6 +248,7 @@ impl fmt::Display for CallableFunction { Self::Pure(_) => write!(f, "NativePureFunction"), Self::Method(_) => write!(f, "NativeMethod"), Self::Iterator(_) => write!(f, "NativeIterator"), + Self::Plugin(_) => write!(f, "PluginFunction"), #[cfg(not(feature = "no_function"))] CallableFunction::Script(s) => fmt::Display::fmt(s, f), @@ -253,6 +263,8 @@ impl CallableFunction { Self::Pure(_) => true, Self::Method(_) | Self::Iterator(_) => false, + Self::Plugin(p) => !p.is_method_call(), + #[cfg(not(feature = "no_function"))] Self::Script(_) => false, } @@ -263,6 +275,8 @@ impl CallableFunction { Self::Method(_) => true, Self::Pure(_) | Self::Iterator(_) => false, + Self::Plugin(p) => p.is_method_call(), + #[cfg(not(feature = "no_function"))] Self::Script(_) => false, } @@ -271,7 +285,7 @@ impl CallableFunction { pub fn is_iter(&self) -> bool { match self { Self::Iterator(_) => true, - Self::Pure(_) | Self::Method(_) => false, + Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => false, #[cfg(not(feature = "no_function"))] Self::Script(_) => false, @@ -283,13 +297,24 @@ impl CallableFunction { #[cfg(not(feature = "no_function"))] Self::Script(_) => true, + Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => false, + } + } + /// Is this a plugin-defined function? + pub fn is_plugin_fn(&self) -> bool { + match self { + Self::Plugin(_) => true, Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false, + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => false, } } /// Is this a native Rust function? pub fn is_native(&self) -> bool { match self { Self::Pure(_) | Self::Method(_) => true, + Self::Plugin(_) => true, Self::Iterator(_) => true, #[cfg(not(feature = "no_function"))] @@ -299,6 +324,7 @@ impl CallableFunction { /// Get the access mode. pub fn access(&self) -> FnAccess { match self { + Self::Plugin(_) => FnAccess::Public, Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => FnAccess::Public, #[cfg(not(feature = "no_function"))] @@ -313,7 +339,7 @@ impl CallableFunction { pub fn get_native_fn(&self) -> &FnAny { match self { Self::Pure(f) | Self::Method(f) => f.as_ref(), - Self::Iterator(_) => unreachable!(), + Self::Iterator(_) | Self::Plugin(_) => unreachable!(), #[cfg(not(feature = "no_function"))] Self::Script(_) => unreachable!(), @@ -327,7 +353,7 @@ impl CallableFunction { #[cfg(not(feature = "no_function"))] pub fn get_shared_fn_def(&self) -> Shared { match self { - Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => unreachable!(), + Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(), Self::Script(f) => f.clone(), } } @@ -338,7 +364,7 @@ impl CallableFunction { /// Panics if the `CallableFunction` is not `Script`. pub fn get_fn_def(&self) -> &ScriptFnDef { match self { - Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => unreachable!(), + Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(), #[cfg(not(feature = "no_function"))] Self::Script(f) => f, @@ -352,7 +378,21 @@ impl CallableFunction { pub fn get_iter_fn(&self) -> IteratorFn { match self { Self::Iterator(f) => *f, - Self::Pure(_) | Self::Method(_) => unreachable!(), + Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => unreachable!(), + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => unreachable!(), + } + } + /// Get a reference to a plugin function. + /// + /// # Panics + /// + /// Panics if the `CallableFunction` is not `Plugin`. + pub fn get_plugin_fn<'s>(&'s self) -> SharedPluginFunction { + match self { + Self::Plugin(f) => f.clone(), + Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => unreachable!(), #[cfg(not(feature = "no_function"))] Self::Script(_) => unreachable!(), @@ -366,6 +406,18 @@ impl CallableFunction { pub fn from_method(func: Box) -> Self { Self::Method(func.into()) } + + #[cfg(feature = "sync")] + /// Create a new `CallableFunction::Plugin`. + pub fn from_plugin(plugin: impl PluginFunction + 'static + Send + Sync) -> Self { + Self::Plugin(Arc::new(plugin)) + } + + #[cfg(not(feature = "sync"))] + /// Create a new `CallableFunction::Plugin`. + pub fn from_plugin(plugin: impl PluginFunction + 'static) -> Self { + Self::Plugin(Rc::new(plugin)) + } } impl From for CallableFunction { diff --git a/src/fn_register.rs b/src/fn_register.rs index 5c519d4c..28d33198 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -7,12 +7,118 @@ use crate::engine::Engine; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; use crate::module::Module; use crate::parser::FnAccess; +use crate::plugin::Plugin; use crate::r#unsafe::unsafe_cast_box; use crate::result::EvalAltResult; use crate::utils::ImmutableString; use crate::stdlib::{any::TypeId, boxed::Box, mem, string::String}; +/// A trait to register custom plugins with the `Engine`. +/// +/// A plugin consists of a number of functions. All functions will be registered with the engine. +pub trait RegisterPlugin { + /// Allow extensions of the engine's behavior. + /// + /// This can include importing modules, registering functions to the global name space, and + /// more. + /// + /// # Example + /// + /// ``` + /// # #[cfg(not(feature = "no_float"))] + /// use rhai::FLOAT as NUMBER; + /// # #[cfg(feature = "no_float")] + /// use rhai::INT as NUMBER; + /// # #[cfg(not(feature = "no_module"))] + /// use rhai::{Module, ModuleResolver, RegisterFn, RegisterPlugin}; + /// # #[cfg(not(feature = "no_module"))] + /// use rhai::plugin::*; + /// # #[cfg(not(feature = "no_module"))] + /// use rhai::module_resolvers::*; + /// + /// // A function we want to expose to Rhai. + /// #[derive(Copy, Clone)] + /// struct DistanceFunction(); + /// + /// # #[cfg(not(feature = "no_module"))] + /// impl PluginFunction for DistanceFunction { + /// fn is_method_call(&self) -> bool { false } + /// fn is_varadic(&self) -> bool { false } + /// + /// fn call(&self, args: &mut[&mut Dynamic], pos: Position) -> 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::(); + /// let y2: NUMBER = std::mem::take(args[3]).clone().cast::(); + /// # #[cfg(not(feature = "no_float"))] + /// let square_sum = (y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0); + /// # #[cfg(feature = "no_float")] + /// let square_sum = (y2 - y1).abs().pow(2) + (x2 -x1).abs().pow(2); + /// Ok(Dynamic::from(square_sum)) + /// } + /// + /// fn clone_boxed(&self) -> Box { + /// Box::new(DistanceFunction()) + /// } + /// + /// fn input_types(&self) -> Box<[std::any::TypeId]> { + /// vec![std::any::TypeId::of::(), + /// std::any::TypeId::of::(), + /// std::any::TypeId::of::(), + /// std::any::TypeId::of::()].into_boxed_slice() + /// } + /// } + /// + /// // A simple custom plugin. This should not usually be done with hand-written code. + /// #[derive(Copy, Clone)] + /// pub struct AdvancedMathPlugin(); + /// + /// # #[cfg(not(feature = "no_module"))] + /// impl Plugin for AdvancedMathPlugin { + /// fn register_contents(self, engine: &mut Engine) { + /// // Plugins are allowed to have side-effects on the engine. + /// engine.register_fn("get_mystic_number", || { 42 as NUMBER }); + /// + /// // Main purpose: create a module to expose the functions to Rhai. + /// // + /// // This is currently a hack. There needs to be a better API here for "plugin" + /// // modules. + /// let mut m = Module::new(); + /// m.set_fn("euclidean_distance".to_string(), FnAccess::Public, + /// &[std::any::TypeId::of::(), + /// std::any::TypeId::of::(), + /// std::any::TypeId::of::(), + /// std::any::TypeId::of::()], + /// CallableFunction::from_plugin(DistanceFunction())); + /// let mut r = StaticModuleResolver::new(); + /// r.insert("Math::Advanced".to_string(), m); + /// engine.set_module_resolver(Some(r)); + /// } + /// } + /// + /// + /// # fn main() -> Result<(), Box> { + /// + /// # #[cfg(not(feature = "no_module"))] { + /// let mut engine = Engine::new(); + /// engine.register_plugin(AdvancedMathPlugin()); + /// + /// # #[cfg(feature = "no_float")] + /// assert_eq!(engine.eval::( + /// r#"import "Math::Advanced" as math; + /// let x = math::euclidean_distance(0, 1, 0, get_mystic_number()); x"#)?, 1681); + /// # #[cfg(not(feature = "no_float"))] + /// assert_eq!(engine.eval::( + /// r#"import "Math::Advanced" as math; + /// let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"#)?, 1681.0); + /// # } // end cfg + /// # Ok(()) + /// # } + /// ``` + fn register_plugin(&mut self, plugin: PL); +} + /// Trait to register custom functions with the `Engine`. pub trait RegisterFn { /// Register a custom function with the `Engine`. @@ -117,6 +223,12 @@ pub fn by_value(data: &mut Dynamic) -> T { } } +impl RegisterPlugin for Engine { + fn register_plugin(&mut self, plugin: PL) { + plugin.register_contents(self); + } +} + /// This macro creates a closure wrapping a registered function. macro_rules! make_func { ($fn:ident : $map:expr ; $($par:ident => $let:stmt => $convert:expr => $arg:expr),*) => { diff --git a/src/lib.rs b/src/lib.rs index 7406b45a..4939e119 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,6 +70,7 @@ mod module; mod optimize; pub mod packages; mod parser; +pub mod plugin; mod result; mod scope; #[cfg(feature = "serde")] @@ -85,7 +86,7 @@ pub use any::Dynamic; pub use engine::Engine; pub use error::{ParseError, ParseErrorType}; pub use fn_native::{FnPtr, IteratorFn}; -pub use fn_register::{RegisterFn, RegisterResultFn}; +pub use fn_register::{RegisterFn, RegisterPlugin, RegisterResultFn}; pub use module::Module; pub use parser::{ImmutableString, AST, INT}; pub use result::EvalAltResult; @@ -94,8 +95,12 @@ pub use syntax::{EvalContext, Expression}; pub use token::Position; pub use utils::calc_fn_spec as calc_fn_hash; +pub use rhai_codegen::*; + #[cfg(not(feature = "no_function"))] pub use parser::FnAccess; +#[cfg(feature = "no_function")] +pub use parser::FnAccess; #[cfg(not(feature = "no_function"))] pub use fn_func::Func; diff --git a/src/module.rs b/src/module.rs index 28c64ff0..c6e161b6 100644 --- a/src/module.rs +++ b/src/module.rs @@ -931,6 +931,24 @@ impl Module { /// Combine another module into this module. /// The other module is consumed to merge into this module. pub fn combine(&mut self, other: Self) -> &mut Self { + self.modules.extend(other.modules.into_iter()); + self.variables.extend(other.variables.into_iter()); + self.functions.extend(other.functions.into_iter()); + self.type_iterators.extend(other.type_iterators.into_iter()); + self.all_functions.clear(); + self.all_variables.clear(); + self.indexed = false; + self + } + + /// Combine another module into this module. + /// The other module is consumed to merge into this module. + /// Sub-modules are flattened onto the root module, with higher level overriding lower level. + pub fn combine_flatten(&mut self, other: Self) -> &mut Self { + other.modules.into_iter().for_each(|(_, m)| { + self.combine_flatten(m); + }); + self.variables.extend(other.variables.into_iter()); self.functions.extend(other.functions.into_iter()); self.type_iterators.extend(other.type_iterators.into_iter()); @@ -942,15 +960,26 @@ impl Module { /// Merge another module into this module. pub fn merge(&mut self, other: &Self) -> &mut Self { - self.merge_filtered(other, |_, _, _| true) + self.merge_filtered(other, &|_, _, _| true) } /// Merge another module into this module, with only selected script-defined functions based on a filter predicate. pub(crate) fn merge_filtered( &mut self, other: &Self, - _filter: impl Fn(FnAccess, &str, usize) -> bool, + _filter: &impl Fn(FnAccess, &str, usize) -> bool, ) -> &mut Self { + #[cfg(not(feature = "no_function"))] + for (k, v) in &other.modules { + let mut m = Self::new(); + m.merge_filtered(v, _filter); + self.modules.insert(k.clone(), m); + } + + #[cfg(feature = "no_function")] + self.modules + .extend(other.modules.iter().map(|(k, v)| (k.clone(), v.clone()))); + self.variables .extend(other.variables.iter().map(|(k, v)| (k.clone(), v.clone()))); diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 5c95a059..d72e2e8b 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -1,434 +1,338 @@ +#![allow(non_snake_case)] + use crate::def_package; -use crate::module::FuncReturn; use crate::parser::INT; +use crate::plugin::*; use crate::{result::EvalAltResult, token::Position}; -use num_traits::{ - identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, - CheckedShr, CheckedSub, -}; +#[cfg(not(feature = "no_float"))] +use crate::parser::FLOAT; #[cfg(feature = "no_std")] #[cfg(not(feature = "no_float"))] use num_traits::float::Float; -use crate::stdlib::{ - fmt::Display, - format, - ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub}, -}; +use crate::stdlib::{format, string::String}; -// Checked add -pub fn add(x: T, y: T) -> FuncReturn { - x.checked_add(&y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Addition overflow: {} + {}", x, y), - Position::none(), - ) - .into() - }) -} -// Checked subtract -pub fn sub(x: T, y: T) -> FuncReturn { - x.checked_sub(&y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Subtraction underflow: {} - {}", x, y), - Position::none(), - ) - .into() - }) -} -// Checked multiply -pub fn mul(x: T, y: T) -> FuncReturn { - x.checked_mul(&y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Multiplication overflow: {} * {}", x, y), - Position::none(), - ) - .into() - }) -} -// Checked divide -pub fn div(x: T, y: T) -> FuncReturn -where - T: Display + CheckedDiv + PartialEq + Zero, -{ - // Detect division by zero - if y == T::zero() { - return EvalAltResult::ErrorArithmetic( - format!("Division by zero: {} / {}", x, y), - Position::none(), - ) - .into(); - } - - x.checked_div(&y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Division overflow: {} / {}", x, y), - Position::none(), - ) - .into() - }) -} -// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX -pub fn neg(x: T) -> FuncReturn { - x.checked_neg().ok_or_else(|| { - EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) - .into() - }) -} -// Checked absolute -pub fn abs(x: T) -> FuncReturn { - // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics - // when the number is ::MIN instead of returning ::MIN itself. - if x >= ::zero() { - Ok(x) - } else { - x.checked_neg().ok_or_else(|| { - EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none()) - .into() - }) - } -} -// Unchecked add - may panic on overflow -fn add_u(x: T, y: T) -> FuncReturn<::Output> { - Ok(x + y) -} -// Unchecked subtract - may panic on underflow -fn sub_u(x: T, y: T) -> FuncReturn<::Output> { - Ok(x - y) -} -// Unchecked multiply - may panic on overflow -fn mul_u(x: T, y: T) -> FuncReturn<::Output> { - Ok(x * y) -} -// Unchecked divide - may panic when dividing by zero -fn div_u(x: T, y: T) -> FuncReturn<::Output> { - Ok(x / y) -} -// Unchecked negative - may panic on overflow -fn neg_u(x: T) -> FuncReturn<::Output> { - Ok(-x) -} -// Unchecked absolute - may panic on overflow -fn abs_u(x: T) -> FuncReturn<::Output> -where - T: Neg + PartialOrd + Default + Into<::Output>, -{ - // Numbers should default to zero - if x < Default::default() { - Ok(-x) - } else { - Ok(x.into()) - } -} -// Bit operators -fn binary_and(x: T, y: T) -> FuncReturn<::Output> { - Ok(x & y) -} -fn binary_or(x: T, y: T) -> FuncReturn<::Output> { - Ok(x | y) -} -fn binary_xor(x: T, y: T) -> FuncReturn<::Output> { - Ok(x ^ y) -} -// Checked left-shift -pub fn shl(x: T, y: INT) -> FuncReturn { - // Cannot shift by a negative number of bits - if y < 0 { - return EvalAltResult::ErrorArithmetic( - format!("Left-shift by a negative number: {} << {}", x, y), - Position::none(), - ) - .into(); - } - - CheckedShl::checked_shl(&x, y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Left-shift by too many bits: {} << {}", x, y), - Position::none(), - ) - .into() - }) -} -// Checked right-shift -pub fn shr(x: T, y: INT) -> FuncReturn { - // Cannot shift by a negative number of bits - if y < 0 { - return EvalAltResult::ErrorArithmetic( - format!("Right-shift by a negative number: {} >> {}", x, y), - Position::none(), - ) - .into(); - } - - CheckedShr::checked_shr(&x, y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Right-shift by too many bits: {} % {}", x, y), - Position::none(), - ) - .into() - }) -} -// Unchecked left-shift - may panic if shifting by a negative number of bits -pub fn shl_u>(x: T, y: T) -> FuncReturn<>::Output> { - Ok(x.shl(y)) -} -// Unchecked right-shift - may panic if shifting by a negative number of bits -pub fn shr_u>(x: T, y: T) -> FuncReturn<>::Output> { - Ok(x.shr(y)) -} -// Checked modulo -pub fn modulo(x: T, y: T) -> FuncReturn { - x.checked_rem(&y).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Modulo division by zero or overflow: {} % {}", x, y), - Position::none(), - ) - .into() - }) -} -// Unchecked modulo - may panic if dividing by zero -fn modulo_u(x: T, y: T) -> FuncReturn<::Output> { - Ok(x % y) -} -// Checked power -pub fn pow_i_i(x: INT, y: INT) -> FuncReturn { - if cfg!(not(feature = "only_i32")) { - if y > (u32::MAX as INT) { - EvalAltResult::ErrorArithmetic( - format!("Integer raised to too large an index: {} ~ {}", x, y), - Position::none(), - ) - .into() - } else if y < 0 { - EvalAltResult::ErrorArithmetic( - format!("Integer raised to a negative index: {} ~ {}", x, y), - Position::none(), - ) - .into() - } else { - x.checked_pow(y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), - Position::none(), - ) - .into() - }) - } - } else { - if y < 0 { - EvalAltResult::ErrorArithmetic( - format!("Integer raised to a negative index: {} ~ {}", x, y), - Position::none(), - ) - .into() - } else { - x.checked_pow(y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), - Position::none(), - ) - .into() - }) - } - } -} -// Floating-point power - always well-defined -#[cfg(not(feature = "no_float"))] -pub fn pow_f_f_32(x: f32, y: f32) -> FuncReturn { - Ok(x.powf(y)) -} -// Checked power -#[cfg(not(feature = "no_float"))] -pub fn pow_f_i_32(x: f32, y: INT) -> FuncReturn { - // Raise to power that is larger than an i32 - if y > (i32::MAX as INT) { - return EvalAltResult::ErrorArithmetic( - format!("Number raised to too large an index: {} ~ {}", x, y), - Position::none(), - ) - .into(); - } - - Ok(x.powi(y as i32)) -} -// Checked power -#[cfg(not(feature = "no_float"))] -pub fn pow_f_i_64(x: f64, y: INT) -> FuncReturn { - // Raise to power that is larger than an i32 - if y > (i32::MAX as INT) { - return EvalAltResult::ErrorArithmetic( - format!("Number raised to too large an index: {} ~ {}", x, y), - Position::none(), - ) - .into(); - } - - Ok(x.powi(y as i32)) -} -// Unchecked power - may be incorrect if the power index is too high (> i32::MAX) -#[cfg(not(feature = "no_float"))] -pub fn pow_f_i_u_32(x: f32, y: INT) -> FuncReturn { - Ok(x.powi(y as i32)) -} -// Unchecked power - may be incorrect if the power index is too high (> i32::MAX) -#[cfg(not(feature = "no_float"))] -pub fn pow_f_i_u_64(x: f64, y: INT) -> FuncReturn { - Ok(x.powi(y as i32)) +#[inline(always)] +pub fn make_err(msg: String) -> Box { + EvalAltResult::ErrorArithmetic(msg, Position::none()).into() } -macro_rules! reg_unary { - ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { - $( $lib.set_fn_1($op, $func::<$par>); )* - }; +macro_rules! gen_arithmetic_functions { + ($root:ident => $($arg_type:ident),+) => { + pub mod $root { $(pub mod $arg_type { + use super::super::*; + + #[export_module] + pub mod functions { + #[rhai_fn(name = "+", return_raw)] + #[inline] + pub fn add(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_add(y).ok_or_else(|| make_err(format!("Addition overflow: {} + {}", x, y))).map(Dynamic::from) + } else { + Ok(Dynamic::from(x + y)) + } + } + #[rhai_fn(name = "-", return_raw)] + #[inline] + pub fn subtract(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_sub(y).ok_or_else(|| make_err(format!("Subtraction overflow: {} - {}", x, y))).map(Dynamic::from) + } else { + Ok(Dynamic::from(x - y)) + } + } + #[rhai_fn(name = "*", return_raw)] + #[inline] + pub fn multiply(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_mul(y).ok_or_else(|| make_err(format!("Multiplication overflow: {} * {}", x, y))).map(Dynamic::from) + } else { + Ok(Dynamic::from(x * y)) + } + } + #[rhai_fn(name = "/", return_raw)] + #[inline] + pub fn divide(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + // Detect division by zero + if y == 0 { + Err(make_err(format!("Division by zero: {} / {}", x, y))) + } else { + x.checked_div(y).ok_or_else(|| make_err(format!("Division overflow: {} / {}", x, y))).map(Dynamic::from) + } + } else { + Ok(Dynamic::from(x / y)) + } + } + #[rhai_fn(name = "%", return_raw)] + #[inline] + pub fn modulo(x: $arg_type, y: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_rem(y).ok_or_else(|| make_err(format!("Modulo division by zero or overflow: {} % {}", x, y))).map(Dynamic::from) + } else { + Ok(Dynamic::from(x % y)) + } + } + #[rhai_fn(name = "~", return_raw)] + #[inline] + pub fn power(x: INT, y: INT) -> Result> { + if cfg!(not(feature = "unchecked")) { + if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { + Err(make_err(format!("Integer raised to too large an index: {} ~ {}", x, y))) + } else if y < 0 { + Err(make_err(format!("Integer raised to a negative index: {} ~ {}", x, y))) + } else { + x.checked_pow(y as u32).ok_or_else(|| make_err(format!("Power overflow: {} ~ {}", x, y))).map(Dynamic::from) + } + } else { + Ok(Dynamic::from(x.pow(y as u32))) + } + } + + #[rhai_fn(name = "<<", return_raw)] + #[inline] + pub fn shift_left(x: $arg_type, y: INT) -> Result> { + if cfg!(not(feature = "unchecked")) { + if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { + Err(make_err(format!("Left-shift by too many bits: {} << {}", x, y))) + } else if y < 0 { + Err(make_err(format!("Left-shift by a negative number: {} << {}", x, y))) + } else { + x.checked_shl(y as u32).ok_or_else(|| make_err(format!("Left-shift by too many bits: {} << {}", x, y))).map(Dynamic::from) + } + } else { + Ok(Dynamic::from(x << y)) + } + } + #[rhai_fn(name = ">>", return_raw)] + #[inline] + pub fn shift_right(x: $arg_type, y: INT) -> Result> { + if cfg!(not(feature = "unchecked")) { + if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { + Err(make_err(format!("Right-shift by too many bits: {} >> {}", x, y))) + } else if y < 0 { + Err(make_err(format!("Right-shift by a negative number: {} >> {}", x, y))) + } else { + x.checked_shr(y as u32).ok_or_else(|| make_err(format!("Right-shift by too many bits: {} >> {}", x, y))).map(Dynamic::from) + } + } else { + Ok(Dynamic::from(x >> y)) + } + } + #[rhai_fn(name = "&")] + #[inline(always)] + pub fn binary_and(x: $arg_type, y: $arg_type) -> $arg_type { + x & y + } + #[rhai_fn(name = "|")] + #[inline(always)] + pub fn binary_or(x: $arg_type, y: $arg_type) -> $arg_type { + x | y + } + #[rhai_fn(name = "^")] + #[inline(always)] + pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type { + x ^ y + } + } + })* } + } } -macro_rules! reg_op { - ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { - $( $lib.set_fn_2($op, $func::<$par>); )* - }; + +macro_rules! gen_signed_functions { + ($root:ident => $($arg_type:ident),+) => { + pub mod $root { $(pub mod $arg_type { + use super::super::*; + + #[export_module] + pub mod functions { + #[rhai_fn(name = "-", return_raw)] + #[inline] + pub fn neg(x: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_neg().ok_or_else(|| make_err(format!("Negation overflow: -{}", x))).map(Dynamic::from) + } else { + Ok(Dynamic::from(-x)) + } + } + #[rhai_fn(return_raw)] + #[inline] + pub fn abs(x: $arg_type) -> Result> { + if cfg!(not(feature = "unchecked")) { + x.checked_abs().ok_or_else(|| make_err(format!("Negation overflow: -{}", x))).map(Dynamic::from) + } else { + Ok(Dynamic::from(x.abs())) + } + } + #[inline] + pub fn sign(x: $arg_type) -> INT { + if x == 0 { + 0 + } else if x < 0 { + -1 + } else { + 1 + } + } + } + })* } + } } -macro_rules! reg_sign { - ($lib:expr, $op:expr, $ret:ty, $($par:ty),*) => { - $( $lib.set_fn_1($op, |value: $par| -> Result<$ret, _> { - Ok(if value == (0 as $par) { - (0 as $ret) - } else if value < (0 as $par) { - (-1 as $ret) - } else { - (1 as $ret) - }) - }); )* - }; + +macro_rules! reg_functions { + ($mod_name:ident += $root:ident ; $($arg_type:ident),+ ) => { $( + $mod_name.combine_flatten(exported_module!($root::$arg_type::functions)); + )* } } def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - if cfg!(not(feature = "unchecked")) { - // Checked basic arithmetic - reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "/", div, i8, u8, i16, u16, i32, u32, u64); - // Checked bit shifts - reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, u32, u64); + reg_functions!(lib += signed_basic; INT); - if cfg!(not(target_arch = "wasm32")) { - reg_op!(lib, "+", add, i128, u128); - reg_op!(lib, "-", sub, i128, u128); - reg_op!(lib, "*", mul, i128, u128); - reg_op!(lib, "/", div, i128, u128); - // Checked bit shifts - reg_op!(lib, "<<", shl, i128, u128); - reg_op!(lib, ">>", shr, i128, u128); - reg_op!(lib, "%", modulo, i128, u128); - } - } + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_functions!(lib += arith_numbers; i8, u8, i16, u16, i32, u32, u64); + reg_functions!(lib += signed_numbers; i8, i16, i32); - if cfg!(feature = "unchecked") { - // Unchecked basic arithmetic - reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, u32, u64); - // Unchecked bit shifts - reg_op!(lib, "<<", shl_u, i64, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, u32, u64); - - if cfg!(not(target_arch = "wasm32")) { - reg_op!(lib, "+", add_u, i128, u128); - reg_op!(lib, "-", sub_u, i128, u128); - reg_op!(lib, "*", mul_u, i128, u128); - reg_op!(lib, "/", div_u, i128, u128); - // Unchecked bit shifts - reg_op!(lib, "<<", shl_u, i128, u128); - reg_op!(lib, ">>", shr_u, i128, u128); - reg_op!(lib, "%", modulo_u, i128, u128); - } - } - - reg_sign!(lib, "sign", INT, i8, i16, i32, i64); - - if cfg!(not(target_arch = "wasm32")) { - reg_sign!(lib, "sign", INT, i128); - } - } - - // Basic arithmetic for floating-point - no need to check - if cfg!(not(feature = "no_float")) { - reg_op!(lib, "+", add_u, f32); - reg_op!(lib, "-", sub_u, f32); - reg_op!(lib, "*", mul_u, f32); - reg_op!(lib, "/", div_u, f32); - reg_op!(lib, "%", modulo_u, f32); - reg_sign!(lib, "sign", f32, f32); - reg_sign!(lib, "sign", f64, f64); - } - - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, u32, u64); - - if cfg!(not(target_arch = "wasm32")) { - reg_op!(lib, "|", binary_or, i128, u128); - reg_op!(lib, "&", binary_and, i128, u128); - reg_op!(lib, "^", binary_xor, i128, u128); + #[cfg(not(target_arch = "wasm32"))] + { + reg_functions!(lib += arith_num_128; i128, u128); + reg_functions!(lib += signed_num_128; i128); } } + // Basic arithmetic for floating-point #[cfg(not(feature = "no_float"))] { - // Power - lib.set_fn_2("~", pow_f_f_32); - - // Checked float raised to integer power - if cfg!(not(feature = "unchecked")) { - lib.set_fn_2("~", pow_f_i_32); - lib.set_fn_2("~", pow_f_i_64); - } else { - lib.set_fn_2("~", pow_f_i_u_32); - lib.set_fn_2("~", pow_f_i_u_64); - } - - // Floating-point unary - reg_unary!(lib, "-", neg_u, f32, f64); - reg_unary!(lib, "abs", abs_u, f32, f64); - } - - // Checked unary - if cfg!(not(feature = "unchecked")) { - reg_unary!(lib, "-", neg, INT); - reg_unary!(lib, "abs", abs, INT); - - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - reg_unary!(lib, "-", neg, i8, i16, i32, i64); - reg_unary!(lib, "abs", abs, i8, i16, i32, i64); - - if cfg!(not(target_arch = "wasm32")) { - reg_unary!(lib, "-", neg, i128); - reg_unary!(lib, "abs", abs, i128); - } - } - } - - // Unchecked unary - if cfg!(feature = "unchecked") { - reg_unary!(lib, "-", neg_u, INT); - reg_unary!(lib, "abs", abs_u, INT); - - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - reg_unary!(lib, "-", neg_u, i8, i16, i32, i64); - reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64); - - if cfg!(not(target_arch = "wasm32")) { - reg_unary!(lib, "-", neg_u, i128); - reg_unary!(lib, "abs", abs_u, i128); - } - } + lib.combine_flatten(exported_module!(f32_functions)); + lib.combine_flatten(exported_module!(f64_functions)); } }); + +gen_arithmetic_functions!(arith_basic => INT); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +gen_arithmetic_functions!(arith_numbers => i8, u8, i16, u16, i32, u32, u64); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +gen_arithmetic_functions!(arith_num_128 => i128, u128); + +gen_signed_functions!(signed_basic => INT); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +gen_signed_functions!(signed_numbers => i8, i16, i32); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +gen_signed_functions!(signed_num_128 => i128); + +#[cfg(not(feature = "no_float"))] +#[export_module] +mod f32_functions { + #[rhai_fn(name = "+")] + #[inline(always)] + pub fn add(x: f32, y: f32) -> f32 { + x + y + } + #[rhai_fn(name = "-")] + #[inline(always)] + pub fn subtract(x: f32, y: f32) -> f32 { + x - y + } + #[rhai_fn(name = "*")] + #[inline(always)] + pub fn multiply(x: f32, y: f32) -> f32 { + x * y + } + #[rhai_fn(name = "/")] + #[inline(always)] + pub fn divide(x: f32, y: f32) -> f32 { + x / y + } + #[rhai_fn(name = "%")] + #[inline(always)] + pub fn modulo(x: f32, y: f32) -> f32 { + x % y + } + #[rhai_fn(name = "-")] + #[inline(always)] + pub fn neg(x: f32) -> f32 { + -x + } + #[inline(always)] + pub fn abs(x: f32) -> f32 { + x.abs() + } + #[inline] + pub fn sign(x: f32) -> INT { + if x == 0.0 { + 0 + } else if x < 0.0 { + -1 + } else { + 1 + } + } + #[rhai_fn(name = "~", return_raw)] + #[inline(always)] + pub fn pow_f_f(x: f32, y: f32) -> Result> { + Ok(Dynamic::from(x.powf(y))) + } + #[rhai_fn(name = "~", return_raw)] + #[inline] + pub fn pow_f_i(x: f32, y: INT) -> Result> { + if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { + Err(make_err(format!( + "Number raised to too large an index: {} ~ {}", + x, y + ))) + } else { + Ok(Dynamic::from(x.powi(y as i32))) + } + } +} + +#[cfg(not(feature = "no_float"))] +#[export_module] +mod f64_functions { + #[rhai_fn(name = "-")] + #[inline(always)] + pub fn neg(x: f64) -> f64 { + -x + } + #[inline(always)] + pub fn abs(x: f64) -> f64 { + x.abs() + } + #[inline] + pub fn sign(x: f64) -> INT { + if x == 0.0 { + 0 + } else if x < 0.0 { + -1 + } else { + 1 + } + } + #[rhai_fn(name = "~", return_raw)] + #[inline] + pub fn pow_f_i(x: FLOAT, y: INT) -> Result> { + if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { + Err(make_err(format!( + "Number raised to too large an index: {} ~ {}", + x, y + ))) + } else { + Ok(x.powi(y as i32).into()) + } + } +} diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index fc6ca5ec..66c35924 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -1,40 +1,151 @@ #![cfg(not(feature = "no_index"))] +#![allow(non_snake_case)] use crate::any::{Dynamic, Variant}; use crate::def_package; use crate::engine::{Array, Engine}; use crate::fn_native::FnPtr; -use crate::module::{FuncReturn, Module}; use crate::parser::{ImmutableString, INT}; +use crate::plugin::*; #[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; +#[cfg(not(feature = "no_object"))] +use crate::engine::Map; + use crate::stdlib::{any::TypeId, boxed::Box}; #[cfg(not(feature = "unchecked"))] use crate::stdlib::string::ToString; -// Register array utility functions -fn push(list: &mut Array, item: T) -> FuncReturn<()> { - list.push(Dynamic::from(item)); - Ok(()) -} -fn ins(list: &mut Array, position: INT, item: T) -> FuncReturn<()> { - if position <= 0 { - list.insert(0, Dynamic::from(item)); - } else if (position as usize) >= list.len() - 1 { - push(list, item)?; - } else { - list.insert(position as usize, Dynamic::from(item)); +pub type Unit = (); + +macro_rules! gen_array_functions { + ($root:ident => $($arg_type:ident),+ ) => { + pub mod $root { $(pub mod $arg_type { + use super::super::*; + + #[export_fn] + #[inline(always)] + pub fn push(list: &mut Array, item: $arg_type) { + list.push(Dynamic::from(item)); + } + + #[export_fn] + pub fn insert(list: &mut Array, position: INT, item: $arg_type) { + if position <= 0 { + list.insert(0, Dynamic::from(item)); + } else if (position as usize) >= list.len() - 1 { + push(list, item); + } else { + list.insert(position as usize, Dynamic::from(item)); + } + } + })* } } - Ok(()) } + +macro_rules! reg_functions { + ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( + set_exported_fn!($mod_name, "push", $root::$arg_type::push); + set_exported_fn!($mod_name, "insert", $root::$arg_type::insert); + + $mod_name.set_raw_fn("pad", + &[TypeId::of::(), TypeId::of::(), TypeId::of::<$arg_type>()], + pad::<$arg_type>); + )* } +} + +def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { + lib.combine_flatten(exported_module!(array_functions)); + + reg_functions!(lib += basic; INT, bool, char, ImmutableString, FnPtr, Array, Unit); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_functions!(lib += numbers; i8, u8, i16, u16, i32, i64, u32, u64); + + #[cfg(not(target_arch = "wasm32"))] + reg_functions!(lib += num_128; i128, u128); + } + + #[cfg(not(feature = "no_float"))] + reg_functions!(lib += float; f32, f64); + + #[cfg(not(feature = "no_object"))] + reg_functions!(lib += map; Map); + + // Register array iterator + lib.set_iter( + TypeId::of::(), + |arr| Box::new(arr.cast::().into_iter()) as Box>, + ); +}); + +#[export_module] +mod array_functions { + #[inline(always)] + pub fn len(list: &mut Array) -> INT { + list.len() as INT + } + #[rhai_fn(get = "len")] + #[inline(always)] + pub fn len_prop(list: &mut Array) -> INT { + len(list) + } + #[inline(always)] + pub fn append(x: &mut Array, y: Array) { + x.extend(y); + } + #[rhai_fn(name = "+=")] + #[inline(always)] + pub fn append_operator(x: &mut Array, y: Array) { + append(x, y) + } + #[rhai_fn(name = "+")] + #[inline] + pub fn concat(mut x: Array, y: Array) -> Array { + x.extend(y); + x + } + #[inline] + pub fn pop(list: &mut Array) -> Dynamic { + list.pop().unwrap_or_else(|| ().into()) + } + pub fn shift(list: &mut Array) -> Dynamic { + if list.is_empty() { + ().into() + } else { + list.remove(0) + } + } + pub fn remove(list: &mut Array, len: INT) -> Dynamic { + if len < 0 || (len as usize) >= list.len() { + ().into() + } else { + list.remove(len as usize) + } + } + #[inline(always)] + pub fn clear(list: &mut Array) { + list.clear(); + } + pub fn truncate(list: &mut Array, len: INT) { + if len >= 0 { + list.truncate(len as usize); + } else { + list.clear(); + } + } +} + fn pad( _engine: &Engine, _: &Module, args: &mut [&mut Dynamic], -) -> FuncReturn<()> { +) -> Result<(), Box> { let len = *args[1].read_lock::().unwrap(); // Check if array will be over max size limit @@ -63,115 +174,19 @@ fn pad( Ok(()) } -macro_rules! reg_op { - ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { - $( $lib.set_fn_2_mut($op, $func::<$par>); )* - }; -} -macro_rules! reg_tri { - ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { - $( $lib.set_fn_3_mut($op, $func::<$par>); )* - }; -} -macro_rules! reg_pad { - ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { - $({ - $lib.set_raw_fn($op, - &[TypeId::of::(), TypeId::of::(), TypeId::of::<$par>()], - $func::<$par> - ); - })* - }; -} +gen_array_functions!(basic => INT, bool, char, ImmutableString, FnPtr, Array, Unit); -def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { - reg_op!(lib, "push", push, INT, bool, char, ImmutableString, FnPtr, Array, ()); - reg_pad!(lib, "pad", pad, INT, bool, char, ImmutableString, FnPtr, Array, ()); - reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, FnPtr, Array, ()); +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +gen_array_functions!(numbers => i8, u8, i16, u16, i32, i64, u32, u64); - lib.set_fn_2_mut("append", |x: &mut Array, y: Array| { - x.extend(y); - Ok(()) - }); - lib.set_fn_2_mut("+=", |x: &mut Array, y: Array| { - x.extend(y); - Ok(()) - }); - lib.set_fn_2( - "+", - |mut x: Array, y: Array| { - x.extend(y); - Ok(x) - }, - ); +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +gen_array_functions!(num_128 => i128, u128); - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - reg_op!(lib, "push", push, i8, u8, i16, u16, i32, i64, u32, u64); - reg_pad!(lib, "pad", pad, i8, u8, i16, u16, i32, u32, i64, u64); - reg_tri!(lib, "insert", ins, i8, u8, i16, u16, i32, i64, u32, u64); +#[cfg(not(feature = "no_float"))] +gen_array_functions!(float => f32, f64); - if cfg!(not(target_arch = "wasm32")) { - reg_op!(lib, "push", push, i128, u128); - reg_pad!(lib, "pad", pad, i128, u128); - reg_tri!(lib, "insert", ins, i128, u128); - } - } - - #[cfg(not(feature = "no_float"))] - { - reg_op!(lib, "push", push, f32, f64); - reg_pad!(lib, "pad", pad, f32, f64); - reg_tri!(lib, "insert", ins, f32, f64); - } - - lib.set_fn_1_mut( - "pop", - |list: &mut Array| Ok(list.pop().unwrap_or_else(|| ().into())), - ); - lib.set_fn_1_mut( - "shift", - |list: &mut Array| { - Ok(if list.is_empty() { - ().into() - } else { - list.remove(0) - }) - }, - ); - lib.set_fn_2_mut( - "remove", - |list: &mut Array, len: INT| { - Ok(if len < 0 || (len as usize) >= list.len() { - ().into() - } else { - list.remove(len as usize) - }) - }, - ); - lib.set_fn_1_mut("len", |list: &mut Array| Ok(list.len() as INT)); - - #[cfg(not(feature = "no_object"))] - lib.set_getter_fn("len", |list: &mut Array| Ok(list.len() as INT)); - - lib.set_fn_1_mut("clear", |list: &mut Array| { - list.clear(); - Ok(()) - }); - lib.set_fn_2_mut( - "truncate", - |list: &mut Array, len: INT| { - if len >= 0 { - list.truncate(len as usize); - } else { - list.clear(); - } - Ok(()) - }, - ); - - // Register array iterator - lib.set_iter( - TypeId::of::(), - |arr| Box::new(arr.cast::().into_iter()) as Box>, - ); -}); +#[cfg(not(feature = "no_object"))] +gen_array_functions!(map => Map); diff --git a/src/packages/eval.rs b/src/packages/eval.rs index 6f97901d..0e2187da 100644 --- a/src/packages/eval.rs +++ b/src/packages/eval.rs @@ -1,12 +1,14 @@ +use crate::any::Dynamic; use crate::def_package; -use crate::module::FuncReturn; use crate::parser::ImmutableString; +use crate::plugin::*; +use crate::result::EvalAltResult; def_package!(crate:EvalPackage:"Disable 'eval'.", lib, { - lib.set_fn_1( - "eval", - |_: ImmutableString| -> FuncReturn<()> { - Err("eval is evil!".into()) - }, - ); + set_exported_fn!(lib, "eval", eval_override); }); + +#[export_fn(return_raw)] +fn eval_override(_script: ImmutableString) -> Result> { + Err("eval is evil!".into()) +} diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index dde08fd5..bb3835d7 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -1,9 +1,21 @@ use crate::def_package; use crate::fn_native::FnPtr; +use crate::plugin::*; def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, { - lib.set_fn_1_mut("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone())); - - #[cfg(not(feature = "no_object"))] - lib.set_getter_fn("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone())); + lib.combine_flatten(exported_module!(fn_ptr_functions)); }); + +#[export_module] +mod fn_ptr_functions { + #[inline(always)] + pub fn name(f: &mut FnPtr) -> ImmutableString { + f.get_fn_name().clone() + } + + #[rhai_fn(get = "name")] + #[inline(always)] + pub fn name_prop(f: &mut FnPtr) -> ImmutableString { + name(f) + } +} diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 6c70a38c..48e46047 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -1,65 +1,85 @@ use crate::def_package; -use crate::module::FuncReturn; +use crate::plugin::*; -// Comparison operators -pub fn lt(x: T, y: T) -> FuncReturn { - Ok(x < y) -} -pub fn lte(x: T, y: T) -> FuncReturn { - Ok(x <= y) -} -pub fn gt(x: T, y: T) -> FuncReturn { - Ok(x > y) -} -pub fn gte(x: T, y: T) -> FuncReturn { - Ok(x >= y) -} -pub fn eq(x: T, y: T) -> FuncReturn { - Ok(x == y) -} -pub fn ne(x: T, y: T) -> FuncReturn { - Ok(x != y) -} +macro_rules! gen_cmp_functions { + ($root:ident => $($arg_type:ident),+) => { + mod $root { $(pub mod $arg_type { + use super::super::*; -// Logic operators -fn not(x: bool) -> FuncReturn { - Ok(!x) -} - -macro_rules! reg_op { - ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { - $( $lib.set_fn_2($op, $func::<$par>); )* + #[export_module] + pub mod functions { + #[rhai_fn(name = "<")] + #[inline(always)] + pub fn lt(x: $arg_type, y: $arg_type) -> bool { + x < y + } + #[rhai_fn(name = "<=")] + #[inline(always)] + pub fn lte(x: $arg_type, y: $arg_type) -> bool { + x <= y + } + #[rhai_fn(name = ">")] + #[inline(always)] + pub fn gt(x: $arg_type, y: $arg_type) -> bool { + x > y + } + #[rhai_fn(name = ">=")] + #[inline(always)] + pub fn gte(x: $arg_type, y: $arg_type) -> bool { + x >= y + } + #[rhai_fn(name = "==")] + #[inline(always)] + pub fn eq(x: $arg_type, y: $arg_type) -> bool { + x == y + } + #[rhai_fn(name = "!=")] + #[inline(always)] + pub fn ne(x: $arg_type, y: $arg_type) -> bool { + x != y + } + } + })* } }; } -def_package!(crate:LogicPackage:"Logical operators.", lib, { - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, ">=", gte, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, u32, u64); - reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, u32, u64); +macro_rules! reg_functions { + ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( + $mod_name.combine_flatten(exported_module!($root::$arg_type::functions)); + )* } +} - if cfg!(not(target_arch = "wasm32")) { - reg_op!(lib, "<", lt, i128, u128); - reg_op!(lib, "<=", lte, i128, u128); - reg_op!(lib, ">", gt, i128, u128); - reg_op!(lib, ">=", gte, i128, u128); - reg_op!(lib, "==", eq, i128, u128); - reg_op!(lib, "!=", ne, i128, u128); - } +def_package!(crate:LogicPackage:"Logical operators.", lib, { + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_functions!(lib += numbers; i8, u8, i16, u16, i32, u32, u64); + + #[cfg(not(target_arch = "wasm32"))] + reg_functions!(lib += num_128; i128, u128); } #[cfg(not(feature = "no_float"))] - { - reg_op!(lib, "<", lt, f32); - reg_op!(lib, "<=", lte, f32); - reg_op!(lib, ">", gt, f32); - reg_op!(lib, ">=", gte, f32); - reg_op!(lib, "==", eq, f32); - reg_op!(lib, "!=", ne, f32); - } + reg_functions!(lib += float; f32); - lib.set_fn_1("!", not); + set_exported_fn!(lib, "!", not); }); + +// Logic operators +#[export_fn] +#[inline(always)] +fn not(x: bool) -> bool { + !x +} + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +gen_cmp_functions!(numbers => i8, u8, i16, u16, i32, u32, u64); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +gen_cmp_functions!(num_128 => i128, u128); + +#[cfg(not(feature = "no_float"))] +gen_cmp_functions!(float => f32); diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 4e2e58ad..d479fcfd 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -3,77 +3,70 @@ use crate::any::Dynamic; use crate::def_package; use crate::engine::Map; -use crate::module::FuncReturn; use crate::parser::{ImmutableString, INT}; +use crate::plugin::*; use crate::stdlib::vec::Vec; -fn map_get_keys(map: &mut Map) -> FuncReturn> { - Ok(map.iter().map(|(k, _)| k.clone().into()).collect()) -} -fn map_get_values(map: &mut Map) -> FuncReturn> { - Ok(map.iter().map(|(_, v)| v.clone()).collect()) -} - def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { - lib.set_fn_2_mut( - "has", - |map: &mut Map, prop: ImmutableString| Ok(map.contains_key(&prop)), - ); - lib.set_fn_1_mut("len", |map: &mut Map| Ok(map.len() as INT)); - lib.set_fn_1_mut("clear", |map: &mut Map| { - map.clear(); - Ok(()) - }); - lib.set_fn_2_mut( - "remove", - |x: &mut Map, name: ImmutableString| Ok(x.remove(&name).unwrap_or_else(|| ().into())), - ); - lib.set_fn_2_mut( - "mixin", - |map1: &mut Map, map2: Map| { - map2.into_iter().for_each(|(key, value)| { - map1.insert(key, value); - }); - Ok(()) - }, - ); - lib.set_fn_2_mut( - "fill_with", - |map1: &mut Map, map2: Map| { - map2.into_iter().for_each(|(key, value)| { - if !map1.contains_key(&key) { - map1.insert(key, value); - } - }); - Ok(()) - }, - ); - lib.set_fn_2_mut( - "+=", - |map1: &mut Map, map2: Map| { - map2.into_iter().for_each(|(key, value)| { - map1.insert(key, value); - }); - Ok(()) - }, - ); - lib.set_fn_2( - "+", - |mut map1: Map, map2: Map| { - map2.into_iter().for_each(|(key, value)| { - map1.insert(key, value); - }); - Ok(map1) - }, - ); - - // Register map access functions - if cfg!(not(feature = "no_index")) { - lib.set_fn_1_mut("keys", map_get_keys); - } - - if cfg!(not(feature = "no_index")) { - lib.set_fn_1_mut("values", map_get_values); - } + lib.combine_flatten(exported_module!(map_functions)); }); + +#[export_module] +mod map_functions { + #[inline(always)] + pub fn has(map: &mut Map, prop: ImmutableString) -> bool { + map.contains_key(&prop) + } + #[inline(always)] + pub fn len(map: &mut Map) -> INT { + map.len() as INT + } + #[rhai_fn(get = "len")] + #[inline(always)] + pub fn len_prop(map: &mut Map) -> INT { + len(map) + } + #[inline(always)] + pub fn clear(map: &mut Map) { + map.clear(); + } + #[inline] + pub fn remove(x: &mut Map, name: ImmutableString) -> Dynamic { + x.remove(&name).unwrap_or_else(|| ().into()) + } + pub fn mixin(map1: &mut Map, map2: Map) { + map2.into_iter().for_each(|(key, value)| { + map1.insert(key, value); + }); + } + #[rhai_fn(name = "+=")] + #[inline(always)] + pub fn mixin_operator(map1: &mut Map, map2: Map) { + mixin(map1, map2) + } + #[rhai_fn(name = "+")] + pub fn merge(mut map1: Map, map2: Map) -> Map { + map2.into_iter().for_each(|(key, value)| { + map1.insert(key, value); + }); + map1 + } + pub fn fill_with(map1: &mut Map, map2: Map) { + map2.into_iter().for_each(|(key, value)| { + if !map1.contains_key(&key) { + map1.insert(key, value); + } + }); + } + + #[cfg(not(feature = "no_index"))] + pub mod indexing { + pub fn keys(map: &mut Map) -> Vec { + map.iter().map(|(k, _)| k.clone().into()).collect() + } + pub fn values(map: &mut Map) -> Vec { + map.iter().map(|(_, v)| v.clone()).collect() + } + } +} diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 0818e8fe..a7c17e50 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -1,5 +1,8 @@ +#![allow(non_snake_case)] + use crate::def_package; use crate::parser::INT; +use crate::plugin::*; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; @@ -21,121 +24,260 @@ pub const MAX_INT: INT = i32::MAX; #[cfg(not(feature = "only_i32"))] pub const MAX_INT: INT = i64::MAX; +macro_rules! gen_conversion_functions { + ($root:ident => $func_name:ident ( $($arg_type:ident),+ ) -> $result_type:ty) => { + pub mod $root { $(pub mod $arg_type { + use super::super::*; + + #[export_fn] + #[inline(always)] + pub fn $func_name(x: $arg_type) -> $result_type { + x as $result_type + } + })* } + } +} + +macro_rules! reg_functions { + ($mod_name:ident += $root:ident :: $func_name:ident ( $($arg_type:ident),+ ) ) => { $( + set_exported_fn!($mod_name, stringify!($func_name), $root::$arg_type::$func_name); + )* } +} + def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { #[cfg(not(feature = "no_float"))] { - // Advanced math functions - lib.set_fn_1("sin", |x: FLOAT| Ok(x.to_radians().sin())); - lib.set_fn_1("cos", |x: FLOAT| Ok(x.to_radians().cos())); - lib.set_fn_1("tan", |x: FLOAT| Ok(x.to_radians().tan())); - lib.set_fn_1("sinh", |x: FLOAT| Ok(x.to_radians().sinh())); - lib.set_fn_1("cosh", |x: FLOAT| Ok(x.to_radians().cosh())); - lib.set_fn_1("tanh", |x: FLOAT| Ok(x.to_radians().tanh())); - lib.set_fn_1("asin", |x: FLOAT| Ok(x.asin().to_degrees())); - lib.set_fn_1("acos", |x: FLOAT| Ok(x.acos().to_degrees())); - lib.set_fn_1("atan", |x: FLOAT| Ok(x.atan().to_degrees())); - lib.set_fn_1("asinh", |x: FLOAT| Ok(x.asinh().to_degrees())); - lib.set_fn_1("acosh", |x: FLOAT| Ok(x.acosh().to_degrees())); - lib.set_fn_1("atanh", |x: FLOAT| Ok(x.atanh().to_degrees())); - lib.set_fn_1("sqrt", |x: FLOAT| Ok(x.sqrt())); - lib.set_fn_1("exp", |x: FLOAT| Ok(x.exp())); - lib.set_fn_1("ln", |x: FLOAT| Ok(x.ln())); - lib.set_fn_2("log", |x: FLOAT, base: FLOAT| Ok(x.log(base))); - lib.set_fn_1("log10", |x: FLOAT| Ok(x.log10())); - lib.set_fn_1("floor", |x: FLOAT| Ok(x.floor())); - lib.set_fn_1("ceiling", |x: FLOAT| Ok(x.ceil())); - lib.set_fn_1("round", |x: FLOAT| Ok(x.ceil())); - lib.set_fn_1("int", |x: FLOAT| Ok(x.trunc())); - lib.set_fn_1("fraction", |x: FLOAT| Ok(x.fract())); - lib.set_fn_1("is_nan", |x: FLOAT| Ok(x.is_nan())); - lib.set_fn_1("is_finite", |x: FLOAT| Ok(x.is_finite())); - lib.set_fn_1("is_infinite", |x: FLOAT| Ok(x.is_infinite())); + // Floating point functions + lib.combine_flatten(exported_module!(float_functions)); - #[cfg(not(feature = "no_object"))] + // Trig functions + lib.combine_flatten(exported_module!(trig_functions)); + + reg_functions!(lib += basic_to_float::to_float(INT)); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] { - lib.set_getter_fn("floor", |x: &mut FLOAT| Ok(x.floor())); - lib.set_getter_fn("ceiling", |x: &mut FLOAT| Ok(x.ceil())); - lib.set_getter_fn("round", |x: &mut FLOAT| Ok(x.ceil())); - lib.set_getter_fn("int", |x: &mut FLOAT| Ok(x.trunc())); - lib.set_getter_fn("fraction", |x: &mut FLOAT| Ok(x.fract())); - lib.set_getter_fn("is_nan", |x: &mut FLOAT| Ok(x.is_nan())); - lib.set_getter_fn("is_finite", |x: &mut FLOAT| Ok(x.is_finite())); - lib.set_getter_fn("is_infinite", |x: &mut FLOAT| Ok(x.is_infinite())); - } + reg_functions!(lib += numbers_to_float::to_float(i8, u8, i16, u16, i32, u32, i64, u32)); - // Register conversion functions - lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: f32| Ok(x as FLOAT)); - - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - lib.set_fn_1("to_float", |x: i8| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: u8| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: i16| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: u16| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: i32| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: u32| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: i64| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: u64| Ok(x as FLOAT)); - - if cfg!(not(target_arch = "wasm32")) { - lib.set_fn_1("to_float", |x: i128| Ok(x as FLOAT)); - lib.set_fn_1("to_float", |x: u128| Ok(x as FLOAT)); - } + #[cfg(not(target_arch = "wasm32"))] + reg_functions!(lib += num_128_to_float::to_float(i128, u128)); } } - lib.set_fn_1("to_int", |ch: char| Ok(ch as INT)); + reg_functions!(lib += basic_to_int::to_int(char)); - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - lib.set_fn_1("to_int", |x: i8| Ok(x as INT)); - lib.set_fn_1("to_int", |x: u8| Ok(x as INT)); - lib.set_fn_1("to_int", |x: i16| Ok(x as INT)); - lib.set_fn_1("to_int", |x: u16| Ok(x as INT)); - } - - if cfg!(not(feature = "only_i32")) { - lib.set_fn_1("to_int", |x: i32| Ok(x as INT)); - lib.set_fn_1("to_int", |x: u64| Ok(x as INT)); - - if cfg!(feature = "only_i64") { - lib.set_fn_1("to_int", |x: u32| Ok(x as INT)); - } - } - - #[cfg(not(feature = "no_float"))] + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] { - if cfg!(not(feature = "unchecked")) { - lib.set_fn_1( - "to_int", - |x: f32| { - if x > (MAX_INT as f32) { - return EvalAltResult::ErrorArithmetic( - format!("Integer overflow: to_int({})", x), - Position::none(), - ).into(); - } + reg_functions!(lib += numbers_to_int::to_int(i8, u8, i16, u16, i32, u32, i64, u64)); - Ok(x.trunc() as INT) - }, - ); - lib.set_fn_1( - "to_int", - |x: FLOAT| { - if x > (MAX_INT as FLOAT) { - return EvalAltResult::ErrorArithmetic( - format!("Integer overflow: to_int({})", x), - Position::none(), - ).into(); - } - - Ok(x.trunc() as INT) - }, - ); - } - - if cfg!(feature = "unchecked") { - lib.set_fn_1("to_int", |x: f32| Ok(x as INT)); - lib.set_fn_1("to_int", |x: f64| Ok(x as INT)); - } + #[cfg(not(target_arch = "wasm32"))] + reg_functions!(lib += num_128_to_int::to_int(i128, u128)); } }); + +#[cfg(not(feature = "no_float"))] +#[export_module] +mod trig_functions { + use crate::parser::FLOAT; + + #[inline(always)] + pub fn sin(x: FLOAT) -> FLOAT { + x.to_radians().sin() + } + #[inline(always)] + pub fn cos(x: FLOAT) -> FLOAT { + x.to_radians().cos() + } + #[inline(always)] + pub fn tan(x: FLOAT) -> FLOAT { + x.to_radians().tan() + } + #[inline(always)] + pub fn sinh(x: FLOAT) -> FLOAT { + x.to_radians().sinh() + } + #[inline(always)] + pub fn cosh(x: FLOAT) -> FLOAT { + x.to_radians().cosh() + } + #[inline(always)] + pub fn tanh(x: FLOAT) -> FLOAT { + x.to_radians().tanh() + } + #[inline(always)] + pub fn asin(x: FLOAT) -> FLOAT { + x.asin().to_degrees() + } + #[inline(always)] + pub fn acos(x: FLOAT) -> FLOAT { + x.acos().to_degrees() + } + #[inline(always)] + pub fn atan(x: FLOAT) -> FLOAT { + x.atan().to_degrees() + } + #[inline(always)] + pub fn asinh(x: FLOAT) -> FLOAT { + x.asinh().to_degrees() + } + #[inline(always)] + pub fn acosh(x: FLOAT) -> FLOAT { + x.acosh().to_degrees() + } + #[inline(always)] + pub fn atanh(x: FLOAT) -> FLOAT { + x.atanh().to_degrees() + } +} + +#[cfg(not(feature = "no_float"))] +#[export_module] +mod float_functions { + use crate::parser::FLOAT; + + #[inline(always)] + pub fn sqrt(x: FLOAT) -> FLOAT { + x.sqrt() + } + #[inline(always)] + pub fn exp(x: FLOAT) -> FLOAT { + x.exp() + } + #[inline(always)] + pub fn ln(x: FLOAT) -> FLOAT { + x.ln() + } + #[inline(always)] + pub fn log(x: FLOAT, base: FLOAT) -> FLOAT { + x.log(base) + } + #[inline(always)] + pub fn log10(x: FLOAT) -> FLOAT { + x.log10() + } + #[inline(always)] + pub fn floor(x: FLOAT) -> FLOAT { + x.floor() + } + #[rhai_fn(get = "floor")] + #[inline(always)] + pub fn floor_prop(x: FLOAT) -> FLOAT { + floor(x) + } + #[inline(always)] + pub fn ceiling(x: FLOAT) -> FLOAT { + x.ceil() + } + #[rhai_fn(get = "ceiling")] + #[inline(always)] + pub fn ceiling_prop(x: FLOAT) -> FLOAT { + ceiling(x) + } + #[inline(always)] + pub fn round(x: FLOAT) -> FLOAT { + x.ceil() + } + #[rhai_fn(get = "round")] + #[inline(always)] + pub fn round_prop(x: FLOAT) -> FLOAT { + ceiling(x) + } + #[inline(always)] + pub fn int(x: FLOAT) -> FLOAT { + x.trunc() + } + #[rhai_fn(get = "int")] + #[inline(always)] + pub fn int_prop(x: FLOAT) -> FLOAT { + int(x) + } + #[inline(always)] + pub fn fraction(x: FLOAT) -> FLOAT { + x.fract() + } + #[rhai_fn(get = "fraction")] + #[inline(always)] + pub fn fraction_prop(x: FLOAT) -> FLOAT { + fraction(x) + } + #[inline(always)] + pub fn is_nan(x: FLOAT) -> bool { + x.is_nan() + } + #[rhai_fn(get = "is_nan")] + #[inline(always)] + pub fn is_nan_prop(x: FLOAT) -> bool { + is_nan(x) + } + #[inline(always)] + pub fn is_finite(x: FLOAT) -> bool { + x.is_finite() + } + #[rhai_fn(get = "is_finite")] + #[inline(always)] + pub fn is_finite_prop(x: FLOAT) -> bool { + is_finite(x) + } + #[inline(always)] + pub fn is_infinite(x: FLOAT) -> bool { + x.is_infinite() + } + #[rhai_fn(get = "is_infinite")] + #[inline(always)] + pub fn is_infinite_prop(x: FLOAT) -> bool { + is_infinite(x) + } + #[rhai_fn(name = "to_int", return_raw)] + #[inline] + pub fn f32_to_int(x: f32) -> Result> { + if cfg!(not(feature = "unchecked")) && x > (MAX_INT as f32) { + EvalAltResult::ErrorArithmetic( + format!("Integer overflow: to_int({})", x), + Position::none(), + ) + .into() + } else { + Ok((x.trunc() as INT).into()) + } + } + #[rhai_fn(name = "to_int", return_raw)] + #[inline] + pub fn f64_to_int(x: f64) -> Result> { + if cfg!(not(feature = "unchecked")) && x > (MAX_INT as f64) { + EvalAltResult::ErrorArithmetic( + format!("Integer overflow: to_int({})", x), + Position::none(), + ) + .into() + } else { + Ok((x.trunc() as INT).into()) + } + } +} + +#[cfg(not(feature = "no_float"))] +gen_conversion_functions!(basic_to_float => to_float (INT) -> FLOAT); + +#[cfg(not(feature = "no_float"))] +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +gen_conversion_functions!(numbers_to_float => to_float (i8, u8, i16, u16, i32, u32, i64, u64) -> FLOAT); + +#[cfg(not(feature = "no_float"))] +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +gen_conversion_functions!(num_128_to_float => to_float (i128, u128) -> FLOAT); + +gen_conversion_functions!(basic_to_int => to_int (char) -> INT); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +gen_conversion_functions!(numbers_to_int => to_int (i8, u8, i16, u16, i32, u32, i64, u64) -> INT); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +gen_conversion_functions!(num_128_to_int => to_int (i128, u128) -> INT); diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 08778150..d6f57c02 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -1,8 +1,10 @@ +#![allow(non_snake_case)] + use crate::def_package; use crate::engine::{FN_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; use crate::fn_native::FnPtr; -use crate::module::FuncReturn; use crate::parser::{ImmutableString, INT}; +use crate::plugin::*; #[cfg(not(feature = "no_index"))] use crate::engine::Array; @@ -16,76 +18,145 @@ use crate::stdlib::{ string::ToString, }; -// Register print and debug -fn to_debug(x: &mut T) -> FuncReturn { - Ok(format!("{:?}", x).into()) -} -fn to_string(x: &mut T) -> FuncReturn { - Ok(x.to_string().into()) -} -#[cfg(not(feature = "no_object"))] -fn format_map(x: &mut Map) -> FuncReturn { - Ok(format!("#{:?}", x).into()) +type Unit = (); + +macro_rules! gen_functions { + ($root:ident => $fn_name:ident ( $($arg_type:ident),+ )) => { + pub mod $root { $(pub mod $arg_type { + use super::super::*; + + #[export_fn] + #[inline(always)] + pub fn to_string_func(x: &mut $arg_type) -> ImmutableString { + super::super::$fn_name(x) + } + })* } + } } -macro_rules! reg_op { - ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { - $( $lib.set_fn_1_mut($op, $func::<$par>); )* - }; +macro_rules! reg_print_functions { + ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( + set_exported_fn!($mod_name, FN_TO_STRING, $root::$arg_type::to_string_func); + set_exported_fn!($mod_name, KEYWORD_PRINT, $root::$arg_type::to_string_func); + )* } +} + +macro_rules! reg_debug_functions { + ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( + set_exported_fn!($mod_name, KEYWORD_DEBUG, $root::$arg_type::to_string_func); + )* } } def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, { - reg_op!(lib, KEYWORD_PRINT, to_string, INT, bool, char, FnPtr); - reg_op!(lib, FN_TO_STRING, to_string, INT, bool, char, FnPtr); - lib.set_fn_1_mut(KEYWORD_DEBUG, |f: &mut FnPtr| Ok(f.to_string())); + reg_print_functions!(lib += print_basic; INT, bool, char, FnPtr); + set_exported_fn!(lib, KEYWORD_PRINT, print_empty_string); + set_exported_fn!(lib, KEYWORD_PRINT, print_unit); + set_exported_fn!(lib, FN_TO_STRING, print_unit); + set_exported_fn!(lib, KEYWORD_PRINT, print_string); + set_exported_fn!(lib, FN_TO_STRING, print_string); - lib.set_fn_0(KEYWORD_PRINT, || Ok("".to_string())); - lib.set_fn_1(KEYWORD_PRINT, |_: ()| Ok("".to_string())); - lib.set_fn_1(FN_TO_STRING, |_: ()| Ok("".to_string())); + reg_debug_functions!(lib += debug_basic; INT, bool, Unit, char, ImmutableString); + set_exported_fn!(lib, KEYWORD_DEBUG, print_empty_string); + set_exported_fn!(lib, KEYWORD_DEBUG, debug_fn_ptr); - lib.set_fn_1(KEYWORD_PRINT, |s: ImmutableString| Ok(s)); - lib.set_fn_1(FN_TO_STRING, |s: ImmutableString| Ok(s)); + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_print_functions!(lib += print_numbers; i8, u8, i16, u16, i32, u32, i64, u64); + reg_debug_functions!(lib += debug_numbers; i8, u8, i16, u16, i32, u32, i64, u64); - reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, ImmutableString); - - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - reg_op!(lib, KEYWORD_PRINT, to_string, i8, u8, i16, u16, i32, u32); - reg_op!(lib, FN_TO_STRING, to_string, i8, u8, i16, u16, i32, u32); - reg_op!(lib, KEYWORD_DEBUG, to_debug, i8, u8, i16, u16, i32, u32); - reg_op!(lib, KEYWORD_PRINT, to_string, i64, u64); - reg_op!(lib, FN_TO_STRING, to_string, i64, u64); - reg_op!(lib, KEYWORD_DEBUG, to_debug, i64, u64); - - if cfg!(not(target_arch = "wasm32")) { - reg_op!(lib, KEYWORD_PRINT, to_string, i128, u128); - reg_op!(lib, FN_TO_STRING, to_string, i128, u128); - reg_op!(lib, KEYWORD_DEBUG, to_debug, i128, u128); + #[cfg(not(target_arch = "wasm32"))] + { + reg_print_functions!(lib += print_num_128; i128, u128); + reg_debug_functions!(lib += debug_num_128; i128, u128); } } #[cfg(not(feature = "no_float"))] { - reg_op!(lib, KEYWORD_PRINT, to_string, f32, f64); - reg_op!(lib, FN_TO_STRING, to_string, f32, f64); - reg_op!(lib, KEYWORD_DEBUG, to_debug, f32, f64); + reg_print_functions!(lib += print_float; f32, f64); + reg_debug_functions!(lib += debug_float; f32, f64); } #[cfg(not(feature = "no_index"))] { - reg_op!(lib, KEYWORD_PRINT, to_debug, Array); - reg_op!(lib, FN_TO_STRING, to_debug, Array); - reg_op!(lib, KEYWORD_DEBUG, to_debug, Array); + reg_print_functions!(lib += print_array; Array); + reg_debug_functions!(lib += print_array; Array); } #[cfg(not(feature = "no_object"))] { - lib.set_fn_1_mut(KEYWORD_PRINT, format_map); - lib.set_fn_1_mut(FN_TO_STRING, format_map); - lib.set_fn_1_mut(KEYWORD_DEBUG, format_map); + set_exported_fn!(lib, KEYWORD_PRINT, format_map::format_map); + set_exported_fn!(lib, FN_TO_STRING, format_map::format_map); + set_exported_fn!(lib, KEYWORD_DEBUG, format_map::format_map); } - - lib.set_fn_2("+", |s: ImmutableString, ch: char| Ok(s + ch)); - lib.set_fn_2_mut("+=", |s: &mut ImmutableString, ch: char| { *s += ch; Ok(()) }); - lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| { *s += ch; Ok(()) }); - lib.set_fn_2_mut("append", |s: &mut ImmutableString, s2: ImmutableString| { *s += &s2; Ok(()) }); }); + +gen_functions!(print_basic => to_string(INT, bool, char, FnPtr)); +gen_functions!(debug_basic => to_debug(INT, bool, Unit, char, ImmutableString)); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +gen_functions!(print_numbers => to_string(i8, u8, i16, u16, i32, u32, i64, u64)); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +gen_functions!(debug_numbers => to_debug(i8, u8, i16, u16, i32, u32, i64, u64)); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +gen_functions!(print_num_128 => to_string(i128, u128)); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +gen_functions!(debug_num_128 => to_debug(i128, u128)); + +#[cfg(not(feature = "no_float"))] +gen_functions!(print_float => to_string(f32, f64)); + +#[cfg(not(feature = "no_float"))] +gen_functions!(debug_float => to_debug(f32, f64)); + +#[cfg(not(feature = "no_index"))] +gen_functions!(print_array => to_debug(Array)); + +// Register print and debug +#[export_fn] +#[inline(always)] +fn print_empty_string() -> ImmutableString { + "".to_string().into() +} +#[export_fn] +#[inline(always)] +fn print_unit(_x: ()) -> ImmutableString { + "".to_string().into() +} +#[export_fn] +#[inline(always)] +fn print_string(s: ImmutableString) -> ImmutableString { + s +} +#[export_fn] +#[inline(always)] +fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString { + to_string(f) +} +#[inline(always)] +fn to_string(x: &mut T) -> ImmutableString { + x.to_string().into() +} +#[inline] +fn to_debug(x: &mut T) -> ImmutableString { + format!("{:?}", x).into() +} +#[cfg(not(feature = "no_object"))] +mod format_map { + use super::*; + #[inline] + #[export_fn] + pub fn format_map(x: &mut Map) -> ImmutableString { + format!("#{:?}", x).into() + } +} diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 0b58c19d..3766d487 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,232 +1,69 @@ +#![allow(non_snake_case)] + use crate::any::Dynamic; use crate::def_package; use crate::engine::Engine; use crate::fn_native::FnPtr; -use crate::module::{FuncReturn, Module}; use crate::parser::{ImmutableString, INT}; +use crate::plugin::*; use crate::utils::StaticVec; #[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; -#[cfg(not(feature = "no_index"))] -use crate::engine::Array; - use crate::stdlib::{ - any::TypeId, - boxed::Box, - fmt::Display, - format, mem, - string::{String, ToString}, - vec::Vec, + any::TypeId, boxed::Box, format, mem, string::String, string::ToString, vec::Vec, }; -fn prepend(x: T, y: ImmutableString) -> FuncReturn { - Ok(format!("{}{}", x, y).into()) -} -fn append(x: ImmutableString, y: T) -> FuncReturn { - Ok(format!("{}{}", x, y).into()) -} -fn sub_string(s: ImmutableString, start: INT, len: INT) -> FuncReturn { - let offset = if s.is_empty() || len <= 0 { - return Ok("".to_string().into()); - } else if start < 0 { - 0 - } else if (start as usize) >= s.chars().count() { - return Ok("".to_string().into()); - } else { - start as usize - }; +macro_rules! gen_concat_functions { + ($root:ident => $($arg_type:ident),+ ) => { + pub mod $root { $(pub mod $arg_type { + use super::super::*; - let chars: StaticVec<_> = s.chars().collect(); + #[export_fn] + #[inline] + pub fn append_func(x: &mut ImmutableString, y: $arg_type) -> String { + format!("{}{}", x, y) + } - let len = if offset + (len as usize) > chars.len() { - chars.len() - offset - } else { - len as usize - }; - - Ok(chars - .iter() - .skip(offset) - .take(len) - .cloned() - .collect::() - .into()) -} -fn crop_string(s: &mut ImmutableString, start: INT, len: INT) -> FuncReturn<()> { - let offset = if s.is_empty() || len <= 0 { - s.make_mut().clear(); - return Ok(()); - } else if start < 0 { - 0 - } else if (start as usize) >= s.chars().count() { - s.make_mut().clear(); - return Ok(()); - } else { - start as usize - }; - - let chars: StaticVec<_> = s.chars().collect(); - - let len = if offset + (len as usize) > chars.len() { - chars.len() - offset - } else { - len as usize - }; - - let copy = s.make_mut(); - copy.clear(); - copy.extend(chars.iter().skip(offset).take(len)); - - Ok(()) + #[export_fn] + #[inline] + pub fn prepend_func(x: &mut $arg_type, y: ImmutableString) -> String { + format!("{}{}", x, y) + } + })* } + } } -macro_rules! reg_op { - ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { - $( $lib.set_fn_2($op, $func::<$par>); )* - }; +macro_rules! reg_functions { + ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( + set_exported_fn!($mod_name, "+", $root::$arg_type::append_func); + set_exported_fn!($mod_name, "+", $root::$arg_type::prepend_func); + )* } } def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, { - reg_op!(lib, "+", append, INT, bool, char, FnPtr); - lib.set_fn_2( "+", |x: ImmutableString, _: ()| Ok(x)); + reg_functions!(lib += basic; INT, bool, char, FnPtr); - reg_op!(lib, "+", prepend, INT, bool, char, FnPtr); - lib.set_fn_2("+", |_: (), y: ImmutableString| Ok(y)); + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_functions!(lib += numbers; i8, u8, i16, u16, i32, i64, u32, u64); - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - reg_op!(lib, "+", append, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(lib, "+", prepend, i8, u8, i16, u16, i32, i64, u32, u64); - - if cfg!(not(target_arch = "wasm32")) { - reg_op!(lib, "+", append, i128, u128); - reg_op!(lib, "+", prepend, i128, u128); - } + #[cfg(not(target_arch = "wasm32"))] + reg_functions!(lib += num_128; i128, u128); } #[cfg(not(feature = "no_float"))] - { - reg_op!(lib, "+", append, f32, f64); - reg_op!(lib, "+", prepend, f32, f64); - } + reg_functions!(lib += float; f32, f64); - #[cfg(not(feature = "no_index"))] - { - lib.set_fn_2_mut("+", |x: &mut ImmutableString, y: Array| Ok(format!("{}{:?}", x, y))); - lib.set_fn_2_mut("+", |x: &mut Array, y: ImmutableString| Ok(format!("{:?}{}", x, y))); - } + lib.combine_flatten(exported_module!(string_functions)); - lib.set_fn_1_mut("len", |s: &mut ImmutableString| Ok(s.chars().count() as INT)); - - #[cfg(not(feature = "no_object"))] - lib.set_getter_fn("len", |s: &mut ImmutableString| Ok(s.chars().count() as INT)); - - lib.set_fn_2_mut( - "contains", - |s: &mut ImmutableString, ch: char| Ok(s.contains(ch)), - ); - lib.set_fn_2_mut( - "contains", - |s: &mut ImmutableString, find: ImmutableString| Ok(s.contains(find.as_str())), - ); - lib.set_fn_3_mut( - "index_of", - |s: &mut ImmutableString, ch: char, start: INT| { - let start = if start < 0 { - 0 - } else if (start as usize) >= s.chars().count() { - return Ok(-1 as INT); - } else { - s.chars().take(start as usize).collect::().len() - }; - - Ok(s[start..] - .find(ch) - .map(|index| s[0..start + index].chars().count() as INT) - .unwrap_or(-1 as INT)) - }, - ); - lib.set_fn_2_mut( - "index_of", - |s: &mut ImmutableString, ch: char| { - Ok(s.find(ch) - .map(|index| s[0..index].chars().count() as INT) - .unwrap_or(-1 as INT)) - }, - ); - lib.set_fn_3_mut( - "index_of", - |s: &mut ImmutableString, find: ImmutableString, start: INT| { - let start = if start < 0 { - 0 - } else if (start as usize) >= s.chars().count() { - return Ok(-1 as INT); - } else { - s.chars().take(start as usize).collect::().len() - }; - - Ok(s[start..] - .find(find.as_str()) - .map(|index| s[0..start + index].chars().count() as INT) - .unwrap_or(-1 as INT)) - }, - ); - lib.set_fn_2_mut( - "index_of", - |s: &mut ImmutableString, find: ImmutableString| { - Ok(s.find(find.as_str()) - .map(|index| s[0..index].chars().count() as INT) - .unwrap_or(-1 as INT)) - }, - ); - lib.set_fn_1_mut("clear", |s: &mut ImmutableString| { - s.make_mut().clear(); - Ok(()) - }); - lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| { - s.make_mut().push(ch); - Ok(()) - }); - lib.set_fn_2_mut( - "append", - |s: &mut ImmutableString, add: ImmutableString| { - s.make_mut().push_str(add.as_str()); - Ok(()) - } - ); - lib.set_fn_3("sub_string", sub_string); - lib.set_fn_2( - "sub_string", - |s: ImmutableString, start: INT| { - let len = s.len() as INT; - sub_string(s, start, len) - }, - ); - lib.set_fn_3_mut("crop", crop_string); - lib.set_fn_2_mut( - "crop", - |s: &mut ImmutableString, start: INT| crop_string(s, start, s.len() as INT), - ); - lib.set_fn_2_mut( - "truncate", - |s: &mut ImmutableString, len: INT| { - if len > 0 { - let chars: StaticVec<_> = s.chars().collect(); - let copy = s.make_mut(); - copy.clear(); - copy.extend(chars.into_iter().take(len as usize)); - } else { - s.make_mut().clear(); - } - Ok(()) - }, - ); lib.set_raw_fn( "pad", &[TypeId::of::(), TypeId::of::(), TypeId::of::()], |_engine: &Engine, _: &Module, args: &mut [&mut Dynamic]| { - let len = *args[1].read_lock::< INT>().unwrap(); + let len = *args[1].read_lock::().unwrap(); // Check if string will be over max size limit #[cfg(not(feature = "unchecked"))] @@ -267,45 +104,6 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str Ok(()) }, ); - lib.set_fn_3_mut( - "replace", - |s: &mut ImmutableString, find: ImmutableString, sub: ImmutableString| { - *s = s.replace(find.as_str(), sub.as_str()).into(); - Ok(()) - }, - ); - lib.set_fn_3_mut( - "replace", - |s: &mut ImmutableString, find: ImmutableString, sub: char| { - *s = s.replace(find.as_str(), &sub.to_string()).into(); - Ok(()) - }, - ); - lib.set_fn_3_mut( - "replace", - |s: &mut ImmutableString, find: char, sub: ImmutableString| { - *s = s.replace(&find.to_string(), sub.as_str()).into(); - Ok(()) - }, - ); - lib.set_fn_3_mut( - "replace", - |s: &mut ImmutableString, find: char, sub: char| { - *s = s.replace(&find.to_string(), &sub.to_string()).into(); - Ok(()) - }, - ); - lib.set_fn_1_mut( - "trim", - |s: &mut ImmutableString| { - let trimmed = s.trim(); - - if trimmed.len() < s.len() { - *s = trimmed.to_string().into(); - } - Ok(()) - }, - ); // Register string iterator lib.set_iter( @@ -315,3 +113,236 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str ) as Box>, ); }); + +gen_concat_functions!(basic => INT, bool, char, FnPtr); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +gen_concat_functions!(numbers => i8, u8, i16, u16, i32, i64, u32, u64); + +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +#[cfg(not(target_arch = "wasm32"))] +gen_concat_functions!(num_128 => i128, u128); + +#[cfg(not(feature = "no_float"))] +gen_concat_functions!(float => f32, f64); + +#[export_module] +mod string_functions { + #[rhai_fn(name = "+")] + #[inline(always)] + pub fn add_append_unit(s: ImmutableString, _x: ()) -> ImmutableString { + s + } + #[rhai_fn(name = "+")] + #[inline(always)] + pub fn add_prepend_unit(_x: (), s: ImmutableString) -> ImmutableString { + s + } + #[rhai_fn(name = "+=")] + #[inline(always)] + pub fn append_char(s: &mut ImmutableString, ch: char) { + *s += ch; + } + #[rhai_fn(name = "+=")] + #[inline(always)] + pub fn append_string(s: &mut ImmutableString, add: ImmutableString) { + *s += &add; + } + + #[inline(always)] + pub fn len(s: &mut ImmutableString) -> INT { + s.chars().count() as INT + } + + #[rhai_fn(get = "len")] + #[inline(always)] + pub fn len_prop(s: &mut ImmutableString) -> INT { + len(s) + } + + #[inline(always)] + pub fn clear(s: &mut ImmutableString) { + s.make_mut().clear(); + } + pub fn truncate(s: &mut ImmutableString, len: INT) { + if len > 0 { + let chars: StaticVec<_> = s.chars().collect(); + let copy = s.make_mut(); + copy.clear(); + copy.extend(chars.into_iter().take(len as usize)); + } else { + s.make_mut().clear(); + } + } + pub fn trim(s: &mut ImmutableString) { + let trimmed = s.trim(); + + if trimmed.len() < s.len() { + *s = trimmed.to_string().into(); + } + } + + #[rhai_fn(name = "contains")] + #[inline(always)] + pub fn contains_char(s: &mut ImmutableString, ch: char) -> bool { + s.contains(ch) + } + #[rhai_fn(name = "contains")] + #[inline(always)] + pub fn contains_string(s: &mut ImmutableString, find: ImmutableString) -> bool { + s.contains(find.as_str()) + } + + #[rhai_fn(name = "index_of")] + pub fn index_of_char_starting_from(s: &mut ImmutableString, ch: char, start: INT) -> INT { + let start = if start < 0 { + 0 + } else if (start as usize) >= s.chars().count() { + return -1 as INT; + } else { + s.chars().take(start as usize).collect::().len() + }; + + s[start..] + .find(ch) + .map(|index| s[0..start + index].chars().count() as INT) + .unwrap_or(-1 as INT) + } + #[rhai_fn(name = "index_of")] + pub fn index_of_char(s: &mut ImmutableString, ch: char) -> INT { + s.find(ch) + .map(|index| s[0..index].chars().count() as INT) + .unwrap_or(-1 as INT) + } + #[rhai_fn(name = "index_of")] + pub fn index_of_string_starting_from( + s: &mut ImmutableString, + find: ImmutableString, + start: INT, + ) -> INT { + let start = if start < 0 { + 0 + } else if (start as usize) >= s.chars().count() { + return -1 as INT; + } else { + s.chars().take(start as usize).collect::().len() + }; + + s[start..] + .find(find.as_str()) + .map(|index| s[0..start + index].chars().count() as INT) + .unwrap_or(-1 as INT) + } + #[rhai_fn(name = "index_of")] + pub fn index_of_string(s: &mut ImmutableString, find: ImmutableString) -> INT { + s.find(find.as_str()) + .map(|index| s[0..index].chars().count() as INT) + .unwrap_or(-1 as INT) + } + + #[rhai_fn(name = "sub_string")] + pub fn sub_string(s: ImmutableString, start: INT, len: INT) -> ImmutableString { + let offset = if s.is_empty() || len <= 0 { + return "".to_string().into(); + } else if start < 0 { + 0 + } else if (start as usize) >= s.chars().count() { + return "".to_string().into(); + } else { + start as usize + }; + + let chars: StaticVec<_> = s.chars().collect(); + + let len = if offset + (len as usize) > chars.len() { + chars.len() - offset + } else { + len as usize + }; + + chars + .iter() + .skip(offset) + .take(len) + .cloned() + .collect::() + .into() + } + #[rhai_fn(name = "sub_string")] + #[inline(always)] + pub fn sub_string_starting_from(s: ImmutableString, start: INT) -> ImmutableString { + let len = s.len() as INT; + sub_string(s, start, len) + } + + #[rhai_fn(name = "crop")] + pub fn crop_string(s: &mut ImmutableString, start: INT, len: INT) { + let offset = if s.is_empty() || len <= 0 { + s.make_mut().clear(); + return; + } else if start < 0 { + 0 + } else if (start as usize) >= s.chars().count() { + s.make_mut().clear(); + return; + } else { + start as usize + }; + + let chars: StaticVec<_> = s.chars().collect(); + + let len = if offset + (len as usize) > chars.len() { + chars.len() - offset + } else { + len as usize + }; + + let copy = s.make_mut(); + copy.clear(); + copy.extend(chars.iter().skip(offset).take(len)); + } + #[rhai_fn(name = "crop")] + #[inline(always)] + pub fn crop_string_starting_from(s: &mut ImmutableString, start: INT) { + crop_string(s, start, s.len() as INT); + } + + #[rhai_fn(name = "replace")] + #[inline(always)] + pub fn replace_string(s: &mut ImmutableString, find: ImmutableString, sub: ImmutableString) { + *s = s.replace(find.as_str(), sub.as_str()).into(); + } + #[rhai_fn(name = "replace")] + #[inline(always)] + pub fn replace_string_with_char(s: &mut ImmutableString, find: ImmutableString, sub: char) { + *s = s.replace(find.as_str(), &sub.to_string()).into(); + } + #[rhai_fn(name = "replace")] + #[inline(always)] + pub fn replace_char_with_string(s: &mut ImmutableString, find: char, sub: ImmutableString) { + *s = s.replace(&find.to_string(), sub.as_str()).into(); + } + #[rhai_fn(name = "replace")] + #[inline(always)] + pub fn replace_char(s: &mut ImmutableString, find: char, sub: char) { + *s = s.replace(&find.to_string(), &sub.to_string()).into(); + } + + #[cfg(not(feature = "no_index"))] + pub mod arrays { + use crate::engine::Array; + + #[rhai_fn(name = "+")] + #[inline] + pub fn append(x: &mut ImmutableString, y: Array) -> String { + format!("{}{:?}", x, y) + } + #[rhai_fn(name = "+")] + #[inline] + pub fn prepend(x: &mut Array, y: ImmutableString) -> String { + format!("{:?}{}", x, y) + } + } +} diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 34f6821d..3ea2b255 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -1,11 +1,10 @@ #![cfg(not(feature = "no_std"))] -use super::logic::{eq, gt, gte, lt, lte, ne}; #[cfg(feature = "no_float")] -#[cfg(not(feature = "unchecked"))] -use super::math_basic::MAX_INT; +use super::{arithmetic::make_err, math_basic::MAX_INT}; use crate::def_package; +use crate::plugin::*; use crate::result::EvalAltResult; #[cfg(not(feature = "no_float"))] @@ -14,10 +13,6 @@ use crate::parser::FLOAT; #[cfg(feature = "no_float")] use crate::parser::INT; -#[cfg(feature = "no_float")] -#[cfg(not(feature = "unchecked"))] -use crate::token::Position; - #[cfg(not(target_arch = "wasm32"))] use crate::stdlib::time::Instant; @@ -26,83 +21,109 @@ use instant::Instant; def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { // Register date/time functions - lib.set_fn_0("timestamp", || Ok(Instant::now())); + lib.combine_flatten(exported_module!(time_functions)); +}); - lib.set_fn_2( - "-", - |ts1: Instant, ts2: Instant| { - if ts2 > ts1 { - #[cfg(not(feature = "no_float"))] - return Ok(-(ts2 - ts1).as_secs_f64()); - - #[cfg(feature = "no_float")] - { - let seconds = (ts2 - ts1).as_secs(); - - #[cfg(not(feature = "unchecked"))] - if seconds > (MAX_INT as u64) { - return EvalAltResult::ErrorArithmetic( - format!( - "Integer overflow for timestamp duration: {}", - -(seconds as i64) - ), - Position::none(), - ).into(); - } - - return Ok(-(seconds as INT)); - } - } else { - #[cfg(not(feature = "no_float"))] - return Ok((ts1 - ts2).as_secs_f64()); - - #[cfg(feature = "no_float")] - { - let seconds = (ts1 - ts2).as_secs(); - - #[cfg(not(feature = "unchecked"))] - if seconds > (MAX_INT as u64) { - return EvalAltResult::ErrorArithmetic( - format!("Integer overflow for timestamp duration: {}", seconds), - Position::none(), - ).into(); - } - - return Ok(seconds as INT); - } - } - }, - ); - - lib.set_fn_2("<", lt::); - lib.set_fn_2("<=", lte::); - lib.set_fn_2(">", gt::); - lib.set_fn_2(">=", gte::); - lib.set_fn_2("==", eq::); - lib.set_fn_2("!=", ne::); - - #[cfg(not(feature = "no_float"))] - fn elapsed (timestamp: &mut Instant) -> Result> { - Ok(timestamp.elapsed().as_secs_f64()) +#[export_module] +mod time_functions { + #[inline(always)] + pub fn timestamp() -> Instant { + Instant::now() } - - #[cfg(feature = "no_float")] - fn elapsed (timestamp: &mut Instant) -> Result> { - let seconds = timestamp.elapsed().as_secs(); - - #[cfg(not(feature = "unchecked"))] - if seconds > (MAX_INT as u64) { - return EvalAltResult::ErrorArithmetic( - format!("Integer overflow for timestamp.elapsed: {}", seconds), - Position::none(), - ).into(); + #[rhai_fn(return_raw)] + pub fn elapsed(timestamp: &mut Instant) -> Result> { + #[cfg(not(feature = "no_float"))] + { + Ok((timestamp.elapsed().as_secs_f64() as FLOAT).into()) } - Ok(seconds as INT) + #[cfg(feature = "no_float")] + { + let seconds = timestamp.elapsed().as_secs(); + + if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) { + Err(make_err(format!( + "Integer overflow for timestamp.elapsed: {}", + seconds + ))) + } else { + Ok((seconds as INT).into()) + } + } } - lib.set_fn_1_mut("elapsed", elapsed); + #[rhai_fn(get = "elapsed", return_raw)] + #[inline(always)] + pub fn elapsed_prop(timestamp: &mut Instant) -> Result> { + elapsed(timestamp) + } - #[cfg(not(feature = "no_object"))] - lib.set_getter_fn("elapsed", elapsed); -}); + #[rhai_fn(return_raw, name = "-")] + pub fn time_diff(ts1: Instant, ts2: Instant) -> Result> { + #[cfg(not(feature = "no_float"))] + { + Ok(if ts2 > ts1 { + -(ts2 - ts1).as_secs_f64() as FLOAT + } else { + (ts1 - ts2).as_secs_f64() as FLOAT + } + .into()) + } + + #[cfg(feature = "no_float")] + if ts2 > ts1 { + let seconds = (ts2 - ts1).as_secs(); + + if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) { + Err(make_err(format!( + "Integer overflow for timestamp duration: -{}", + seconds + ))) + } else { + Ok(Dynamic::from(-(seconds as INT))) + } + } else { + let seconds = (ts1 - ts2).as_secs(); + + if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) { + Err(make_err(format!( + "Integer overflow for timestamp duration: {}", + seconds + ))) + } else { + Ok((seconds as INT).into()) + } + } + } + + #[rhai_fn(name = "==")] + #[inline(always)] + pub fn eq(x: Instant, y: Instant) -> bool { + x == y + } + #[rhai_fn(name = "!=")] + #[inline(always)] + pub fn ne(x: Instant, y: Instant) -> bool { + x != y + } + #[rhai_fn(name = "<")] + #[inline(always)] + pub fn lt(x: Instant, y: Instant) -> bool { + x < y + } + #[rhai_fn(name = "<=")] + #[inline(always)] + pub fn lte(x: Instant, y: Instant) -> bool { + x <= y + } + #[rhai_fn(name = ">")] + #[inline(always)] + pub fn gt(x: Instant, y: Instant) -> bool { + x > y + } + #[rhai_fn(name = ">=")] + #[inline(always)] + pub fn gte(x: Instant, y: Instant) -> bool { + x >= y + } +} diff --git a/src/parser.rs b/src/parser.rs index eaaf559d..a3232247 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -133,7 +133,7 @@ impl AST { filter: impl Fn(FnAccess, &str, usize) -> bool, ) -> Self { let mut functions: Module = Default::default(); - functions.merge_filtered(&self.1, filter); + functions.merge_filtered(&self.1, &filter); Self(Default::default(), functions) } @@ -266,7 +266,7 @@ impl AST { }; let mut functions = functions.clone(); - functions.merge_filtered(&other.1, filter); + functions.merge_filtered(&other.1, &filter); Self::new(ast, functions) } diff --git a/src/plugin.rs b/src/plugin.rs new file mode 100644 index 00000000..b03df99c --- /dev/null +++ b/src/plugin.rs @@ -0,0 +1,56 @@ +//! Module defining plugins in Rhai. Is exported for use by plugin authors. + +pub use crate::{ + stdlib::any::TypeId, + stdlib::boxed::Box, + stdlib::format, + stdlib::string::ToString, + stdlib::vec::Vec, + stdlib::vec as new_vec, + stdlib::mem, + fn_native::CallableFunction, + Dynamic, + Engine, + EvalAltResult, + FnAccess, + ImmutableString, + Module, + Position, + RegisterResultFn, +}; + +#[cfg(features = "no_module")] +pub use rhai_codegen::{export_fn, register_exported_fn}; +#[cfg(not(features = "no_module"))] +pub use rhai_codegen::*; + +#[cfg(features = "sync")] +/// Represents an externally-written plugin for the Rhai interpreter. +/// +/// This trait should not be used directly. Use the `#[plugin]` procedural attribute instead. +pub trait Plugin: Send { + fn register_contents(self, engine: &mut Engine); +} + +#[cfg(not(features = "sync"))] +/// Represents an externally-written plugin for the Rhai interpreter. +/// +/// This trait should not be used directly. Use the `#[plugin]` procedural attribute instead. +pub trait Plugin: Send + Sync { + fn register_contents(self, engine: &mut Engine); +} + +/// Represents a function that is statically defined within a plugin. +/// +/// This trait should not be used directly. Use the `#[plugin]` procedural attribute instead. +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 clone_boxed(&self) -> Box; + + fn input_types(&self) -> Box<[TypeId]>; +} diff --git a/src/settings.rs b/src/settings.rs index 6e9e020d..4f0f7ccf 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -17,6 +17,7 @@ use crate::stdlib::boxed::Box; impl Engine { /// Load a new package into the `Engine`. + /// Anything that can be converted into a `PackageLibrary` is accepted, including a simple `Module`. /// /// When searching for functions, packages loaded later are preferred. /// In other words, loaded packages are searched in reverse order. diff --git a/tests/compound_equality.rs b/tests/compound_equality.rs index b09115a0..af5c16b6 100644 --- a/tests/compound_equality.rs +++ b/tests/compound_equality.rs @@ -45,16 +45,16 @@ fn test_divide_equals() -> Result<(), Box> { } #[test] -fn test_left_shift_equals() -> Result<(), Box> { +fn test_right_shift_equals() -> Result<(), Box> { let engine = Engine::new(); assert_eq!(engine.eval::("let x = 9; x >>=1; x")?, 4); Ok(()) } #[test] -fn test_right_shift_equals() -> Result<(), Box> { +fn test_left_shift_equals() -> Result<(), Box> { let engine = Engine::new(); - assert_eq!(engine.eval::("let x = 4; x<<= 2; x")?, 16); + assert_eq!(engine.eval::("let x = 4; x <<= 2; x")?, 16); Ok(()) } diff --git a/tests/macro_register.rs b/tests/macro_register.rs new file mode 100644 index 00000000..525704c2 --- /dev/null +++ b/tests/macro_register.rs @@ -0,0 +1,16 @@ +use rhai::plugin::*; +use rhai::{Engine, EvalAltResult, INT}; + +#[export_fn] +pub fn add_together(x: INT, y: INT) -> INT { + x + y +} + +#[test] +fn test_exported_fn_register() -> Result<(), Box> { + let mut engine = Engine::new(); + register_exported_fn!(engine, "add_two", add_together); + assert_eq!(engine.eval::("let a = 1; add_two(a, 41)")?, 42); + + Ok(()) +} diff --git a/tests/macro_unroll.rs b/tests/macro_unroll.rs new file mode 100644 index 00000000..c872381a --- /dev/null +++ b/tests/macro_unroll.rs @@ -0,0 +1,68 @@ +#![cfg(not(any(feature = "no_index", feature = "no_module")))] + +use rhai::plugin::*; +use rhai::{Engine, EvalAltResult, Module, INT}; + +pub fn add_generic>(x: T, y: T) -> T { + x + y +} + +pub fn mul_generic>(x: T, y: T) -> T { + x * y +} + +macro_rules! generate_ops { + ($op_name:ident, $op_fn:ident, $($type_names:ident),+) => { + pub mod $op_name { + $( + pub mod $type_names { + use rhai::plugin::*; + use super::super::$op_fn; + #[export_fn] + pub fn op(x: $type_names, y: $type_names) -> $type_names { + $op_fn(x, y) + } + } + )* + } + } +} + +macro_rules! register_in_bulk { + ($mod_name:ident, $op_name:ident, $($type_names:ident),+) => { + $( + { + let type_str = stringify!($type_names); + set_exported_fn!($mod_name, + format!(concat!(stringify!($op_name), "_{}"), type_str), + crate::$op_name::$type_names::op); + } + )* + } +} + +generate_ops!(add, add_generic, i8, i16, i32, i64); +generate_ops!(mul, mul_generic, i8, i16, i32, i64); + +#[test] +fn test_generated_ops() -> Result<(), Box> { + let mut engine = Engine::new(); + + let mut m = Module::new(); + register_in_bulk!(m, add, i8, i16, i32, i64); + register_in_bulk!(m, mul, i8, i16, i32, i64); + + engine.load_package(m); + + #[cfg(feature = "only_i32")] + assert_eq!(engine.eval::("let a = 0; add_i32(a, 1)")?, 1); + #[cfg(not(feature = "only_i32"))] + assert_eq!(engine.eval::("let a = 0; add_i64(a, 1)")?, 1); + + #[cfg(feature = "only_i32")] + assert_eq!(engine.eval::("let a = 1; mul_i32(a, 2)")?, 2); + #[cfg(not(feature = "only_i32"))] + assert_eq!(engine.eval::("let a = 1; mul_i64(a, 2)")?, 2); + + Ok(()) +} diff --git a/tests/math.rs b/tests/math.rs index 4e4a5d4b..c12e3e34 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -1,5 +1,8 @@ use rhai::{Engine, EvalAltResult, INT}; +#[cfg(not(feature = "no_float"))] +use rhai::FLOAT; + #[test] fn test_math() -> Result<(), Box> { let engine = Engine::new(); @@ -10,6 +13,12 @@ fn test_math() -> Result<(), Box> { assert_eq!(engine.eval::("1 / 2")?, 0); assert_eq!(engine.eval::("3 % 2")?, 1); + #[cfg(not(feature = "no_float"))] + assert!((engine.eval::("sin(30.0)")? - 0.5).abs() < 0.001); + + #[cfg(not(feature = "no_float"))] + assert!(engine.eval::("cos(90.0)")?.abs() < 0.001); + #[cfg(not(feature = "only_i32"))] assert_eq!( engine.eval::("abs(-9223372036854775807)")?, diff --git a/tests/plugins.rs b/tests/plugins.rs new file mode 100644 index 00000000..dd56ae61 --- /dev/null +++ b/tests/plugins.rs @@ -0,0 +1,83 @@ +#![cfg(not(any(feature = "no_index", feature = "no_module")))] + +use rhai::plugin::*; +use rhai::{Engine, EvalAltResult, INT}; + +mod test { + use rhai::plugin::*; + + #[export_module] + pub mod special_array_package { + use rhai::{Array, INT}; + + #[cfg(not(feature = "no_object"))] + pub mod feature { + #[rhai_fn(get = "foo", return_raw)] + #[inline(always)] + pub fn foo(array: &mut Array) -> Result> { + Ok(array[0].clone()) + } + } + + #[rhai_fn(name = "test")] + #[inline(always)] + pub fn len(array: &mut Array, mul: INT) -> INT { + (array.len() as INT) * mul + } + #[rhai_fn(name = "+")] + #[inline(always)] + pub fn funky_add(x: INT, y: INT) -> INT { + x / 2 + y * 2 + } + } +} + +macro_rules! gen_unary_functions { + ($op_name:ident = $op_fn:ident ( $($arg_type:ident),+ ) -> $return_type:ident) => { + mod $op_name { $( + pub mod $arg_type { + use super::super::*; + + #[export_fn(name="test")] + pub fn single(x: $arg_type) -> $return_type { + super::super::$op_fn(x) + } + } + )* } + } +} + +macro_rules! reg_functions { + ($mod_name:ident += $op_name:ident :: $func:ident ( $($arg_type:ident),+ )) => { + $(register_exported_fn!($mod_name, stringify!($op_name), $op_name::$arg_type::$func);)* + } +} + +fn make_greeting(n: T) -> String { + format!("{} kitties", n) +} + +gen_unary_functions!(greet = make_greeting(INT, bool, char) -> String); + +#[test] +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)); + engine.load_package(m); + + reg_functions!(engine += greet::single(INT, bool, char)); + + #[cfg(not(feature = "no_object"))] + assert_eq!(engine.eval::("let a = [1, 2, 3]; a.foo")?, 1); + + assert_eq!(engine.eval::("let a = [1, 2, 3]; test(a, 2)")?, 6); + assert_eq!(engine.eval::("2 + 2")?, 5); + assert_eq!( + engine.eval::("let a = [1, 2, 3]; greet(test(a, 2))")?, + "6 kitties" + ); + + Ok(()) +} diff --git a/tests/time.rs b/tests/time.rs index b3f6668f..ad7fc4c9 100644 --- a/tests/time.rs +++ b/tests/time.rs @@ -39,5 +39,14 @@ fn test_timestamp() -> Result<(), Box> { )? < 10 ); + assert!(engine.eval::( + r" + let time1 = timestamp(); + for x in range(0, 10000) {} + let time2 = timestamp(); + time1 <= time2 + " + )?); + Ok(()) }