Merge pull request #230 from schungx/master

Refine plugins.
This commit is contained in:
Stephen Chung 2020-09-09 22:28:10 +08:00 committed by GitHub
commit ecce9c5477
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 2626 additions and 2044 deletions

View File

@ -52,7 +52,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: test
args: --all ${{matrix.flags}}
args: ${{matrix.flags}}
# no-std builds are a bit more extensive to test
no_std_build:
name: NoStdBuild

View File

@ -1,8 +1,14 @@
[workspace]
members = [
".",
"codegen"
]
[package]
name = "rhai"
version = "0.18.3"
edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
description = "Embedded scripting for Rust"
homepage = "https://github.com/jonathandturner/rhai"
repository = "https://github.com/jonathandturner/rhai"
@ -38,7 +44,7 @@ internals = [] # expose internal data structures
unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers.
# compiling for no-std
no_std = [ "no_closure", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ]
no_std = [ "smallvec/union", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ]
[profile.release]
lto = "fat"

View File

@ -20,10 +20,6 @@ Bug fixes
* Imported modules now work inside closures.
* Closures that capture now work under `no_object`.
Version 0.18.2
==============
New features
------------

View File

@ -2,7 +2,7 @@
name = "rhai_codegen"
version = "0.1.0"
edition = "2018"
authors = ["jhwgh1968", "Stephen Chung"]
authors = ["jhwgh1968"]
description = "Proceducral macro support package for Rhai, a scripting language for Rust"
homepage = "https://github.com/jonathandturner/rhai"
repository = "https://github.com/jonathandturner/rhai"

View File

@ -1,5 +1,18 @@
use syn::{parse::ParseStream, parse::Parser, spanned::Spanned};
#[derive(Debug)]
pub enum ExportScope {
PubOnly,
Prefix(String),
All,
}
impl Default for ExportScope {
fn default() -> ExportScope {
ExportScope::PubOnly
}
}
pub trait ExportedParams: Sized {
fn parse_stream(args: ParseStream) -> syn::Result<Self>;
fn no_attrs() -> Self;

View File

@ -10,14 +10,16 @@ use alloc::format;
#[cfg(not(no_std))]
use std::format;
use std::borrow::Cow;
use quote::{quote, quote_spanned};
use syn::{parse::Parse, parse::ParseStream, parse::Parser, spanned::Spanned};
use crate::attrs::{ExportInfo, ExportedParams};
use crate::attrs::{ExportInfo, ExportScope, ExportedParams};
#[derive(Debug, Default)]
pub(crate) struct ExportedFnParams {
pub name: Option<String>,
pub name: Option<Vec<String>>,
pub return_raw: bool,
pub skip: bool,
pub span: Option<proc_macro2::Span>,
@ -53,11 +55,12 @@ impl ExportedParams for ExportedFnParams {
Default::default()
}
fn from_info(
info: crate::attrs::ExportInfo,
) -> syn::Result<Self> {
let ExportInfo { item_span: span, items: attrs } = info;
let mut name = None;
fn from_info(info: crate::attrs::ExportInfo) -> syn::Result<Self> {
let ExportInfo {
item_span: span,
items: attrs,
} = info;
let mut name = Vec::new();
let mut return_raw = false;
let mut skip = false;
for attr in attrs {
@ -71,15 +74,15 @@ impl ExportedParams for ExportedFnParams {
"Rhai function names may not contain dot",
));
}
name = Some(s.value())
name.push(s.value())
}
("get", Some(s)) => name = Some(make_getter(&s.value())),
("set", Some(s)) => name = Some(make_setter(&s.value())),
("get", Some(s)) => name.push(make_getter(&s.value())),
("set", Some(s)) => name.push(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()),
("index_get", None) => name.push(FN_IDX_GET.to_string()),
("index_set", None) => name.push(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"))
@ -96,7 +99,7 @@ impl ExportedParams for ExportedFnParams {
}
Ok(ExportedFnParams {
name,
name: if name.is_empty() { None } else { Some(name) },
return_raw,
skip,
span: Some(span),
@ -222,10 +225,24 @@ impl ExportedFn {
&self.params
}
pub(crate) fn update_scope(&mut self, parent_scope: &ExportScope) {
let keep = match (self.params.skip, parent_scope) {
(true, _) => false,
(_, ExportScope::PubOnly) => self.is_public,
(_, ExportScope::Prefix(s)) => self.name().to_string().starts_with(s),
(_, ExportScope::All) => true,
};
self.params.skip = !keep;
}
pub(crate) fn skipped(&self) -> bool {
self.params.skip
}
pub(crate) fn signature(&self) -> &syn::Signature {
&self.signature
}
pub(crate) fn mutable_receiver(&self) -> bool {
self.mut_receiver
}
@ -242,6 +259,14 @@ impl ExportedFn {
&self.signature.ident
}
pub(crate) fn exported_name<'n>(&'n self) -> Cow<'n, str> {
if let Some(ref name) = self.params.name {
Cow::Borrowed(name.last().unwrap().as_str())
} else {
Cow::Owned(self.signature.ident.to_string())
}
}
pub(crate) fn arg_list(&self) -> impl Iterator<Item = &syn::FnArg> {
self.signature.inputs.iter()
}
@ -322,7 +347,9 @@ impl ExportedFn {
})
.collect();
let return_span = self.return_type().map(|r| r.span())
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=>
@ -369,11 +396,10 @@ impl ExportedFn {
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 name = self.params.name.as_ref().map_or_else(
|| self.name().to_string(),
|names| names.last().unwrap().clone(),
);
let arg_count = self.arg_count();
let is_method_call = self.mutable_receiver();
@ -494,7 +520,9 @@ impl ExportedFn {
// 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())
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=>
@ -529,522 +557,3 @@ impl ExportedFn {
}
}
}
#[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::<ExportedFn>(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::<ExportedFn>(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::<syn::FnArg>(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::<ExportedFn>(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::<syn::FnArg>(quote! { x: usize }).unwrap()
);
assert_eq!(
item_fn.arg_list().nth(1).unwrap(),
&syn::parse2::<syn::FnArg>(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::<ExportedFn>(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::<syn::Path>(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::<ExportedFn>(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::<ExportedFn>(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::<ExportedFn>(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::<ExportedFn>(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::<ExportedFn>(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::<ExportedFn>(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::<syn::FnArg>(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::<ExportedFn>(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::<syn::FnArg>(quote! { level: usize }).unwrap()
);
assert_eq!(
item_fn.arg_list().nth(1).unwrap(),
&syn::parse2::<syn::FnArg>(quote! { message: &str }).unwrap()
);
}
#[test]
fn private_fn() {
let input_tokens: TokenStream = quote! {
fn do_nothing() { }
};
let item_fn = syn::parse2::<ExportedFn>(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::<ExportedFn>(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::<ExportedFn>(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<Dynamic, Box<EvalAltResult>> {
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<dyn PluginFunction> { 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<EvalAltResult>;
pub fn dynamic_result_fn() -> Result<Dynamic, EvalBox> {
Ok(Dynamic::from(super::do_nothing()))
}
}
};
let item_fn = syn::parse2::<ExportedFn>(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<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 1usize,
"wrong arg count: {} != {}", args.len(), 1usize);
let arg0 = mem::take(args[0usize]).clone().cast::<usize>();
Ok(Dynamic::from(do_something(arg0)))
}
fn is_method_call(&self) -> bool { false }
fn is_varadic(&self) -> bool { false }
fn clone_boxed(&self) -> Box<dyn PluginFunction> { Box::new(Token()) }
fn input_types(&self) -> Box<[TypeId]> {
new_vec![TypeId::of::<usize>()].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<EvalAltResult>;
pub fn dynamic_result_fn(x: usize) -> Result<Dynamic, EvalBox> {
Ok(Dynamic::from(super::do_something(x)))
}
}
};
let item_fn = syn::parse2::<ExportedFn>(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<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 1usize,
"wrong arg count: {} != {}", args.len(), 1usize);
let arg0 = mem::take(args[0usize]).clone().cast::<usize>();
Ok(Dynamic::from(do_something(arg0)))
}
fn is_method_call(&self) -> bool { false }
fn is_varadic(&self) -> bool { false }
fn clone_boxed(&self) -> Box<dyn PluginFunction> { Box::new(MyType()) }
fn input_types(&self) -> Box<[TypeId]> {
new_vec![TypeId::of::<usize>()].into_boxed_slice()
}
}
};
let item_fn = syn::parse2::<ExportedFn>(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<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 2usize,
"wrong arg count: {} != {}", args.len(), 2usize);
let arg0 = mem::take(args[0usize]).clone().cast::<usize>();
let arg1 = mem::take(args[1usize]).clone().cast::<usize>();
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<dyn PluginFunction> { Box::new(Token()) }
fn input_types(&self) -> Box<[TypeId]> {
new_vec![TypeId::of::<usize>(),
TypeId::of::<usize>()].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<EvalAltResult>;
pub fn dynamic_result_fn(x: usize, y: usize) -> Result<Dynamic, EvalBox> {
Ok(Dynamic::from(super::add_together(x, y)))
}
}
};
let item_fn = syn::parse2::<ExportedFn>(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<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 2usize,
"wrong arg count: {} != {}", args.len(), 2usize);
let arg1 = mem::take(args[1usize]).clone().cast::<usize>();
let arg0: &mut _ = &mut args[0usize].write_lock::<usize>().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<dyn PluginFunction> { Box::new(Token()) }
fn input_types(&self) -> Box<[TypeId]> {
new_vec![TypeId::of::<usize>(),
TypeId::of::<usize>()].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<EvalAltResult>;
pub fn dynamic_result_fn(x: &mut usize, y: usize) -> Result<Dynamic, EvalBox> {
Ok(Dynamic::from(super::increment(x, y)))
}
}
};
let item_fn = syn::parse2::<ExportedFn>(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<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 1usize,
"wrong arg count: {} != {}", args.len(), 1usize);
let arg0 = mem::take(args[0usize]).clone().cast::<ImmutableString>();
Ok(Dynamic::from(special_print(&arg0)))
}
fn is_method_call(&self) -> bool { false }
fn is_varadic(&self) -> bool { false }
fn clone_boxed(&self) -> Box<dyn PluginFunction> { Box::new(Token()) }
fn input_types(&self) -> Box<[TypeId]> {
new_vec![TypeId::of::<ImmutableString>()].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<EvalAltResult>;
pub fn dynamic_result_fn(message: &str) -> Result<Dynamic, EvalBox> {
Ok(Dynamic::from(super::special_print(message)))
}
}
};
let item_fn = syn::parse2::<ExportedFn>(input_tokens).unwrap();
assert!(!item_fn.mutable_receiver());
assert_streams_eq(item_fn.generate(), expected_tokens);
}
}

View File

@ -102,6 +102,9 @@ mod module;
mod register;
mod rhai_module;
#[cfg(test)]
mod test;
#[proc_macro_attribute]
pub fn export_fn(
args: proc_macro::TokenStream,
@ -124,10 +127,18 @@ pub fn export_fn(
#[proc_macro_attribute]
pub fn export_module(
_args: proc_macro::TokenStream,
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let module_def = parse_macro_input!(input as module::Module);
let parsed_params = match crate::attrs::outer_item_attributes(args.into(), "export_module") {
Ok(args) => args,
Err(err) => return proc_macro::TokenStream::from(err.to_compile_error()),
};
let mut module_def = parse_macro_input!(input as module::Module);
if let Err(e) = module_def.set_params(parsed_params) {
return e.to_compile_error().into();
}
let tokens = module_def.generate();
proc_macro::TokenStream::from(tokens)
}

File diff suppressed because it is too large Load Diff

View File

@ -2,15 +2,17 @@ use std::collections::HashMap;
use quote::{quote, ToTokens};
use crate::attrs::ExportScope;
use crate::function::ExportedFn;
use crate::module::Module;
pub(crate) type ExportedConst = (String, syn::Expr);
pub(crate) fn generate_body(
fns: &[ExportedFn],
fns: &mut [ExportedFn],
consts: &[ExportedConst],
submodules: &[Module],
submodules: &mut [Module],
parent_scope: &ExportScope,
) -> proc_macro2::TokenStream {
let mut set_fn_stmts: Vec<syn::Stmt> = Vec::new();
let mut set_const_stmts: Vec<syn::Stmt> = Vec::new();
@ -28,6 +30,7 @@ pub(crate) fn generate_body(
}
for itemmod in submodules {
itemmod.update_scope(&parent_scope);
if itemmod.skipped() {
continue;
}
@ -56,6 +59,7 @@ pub(crate) fn generate_body(
// NB: these are token streams, because reparsing messes up "> >" vs ">>"
let mut gen_fn_tokens: Vec<proc_macro2::TokenStream> = Vec::new();
for function in fns {
function.update_scope(&parent_scope);
if function.skipped() {
continue;
}
@ -63,12 +67,12 @@ pub(crate) fn generate_body(
&format!("{}_token", function.name().to_string()),
function.name().span(),
);
let reg_name = function
let reg_names = function
.params()
.name
.clone()
.unwrap_or_else(|| function.name().to_string());
let fn_literal = syn::LitStr::new(&reg_name, proc_macro2::Span::call_site());
.unwrap_or_else(|| vec![function.name().to_string()]);
let fn_input_types: Vec<syn::Expr> = function
.arg_list()
.map(|fnarg| match fnarg {
@ -106,13 +110,17 @@ pub(crate) fn generate_body(
})
.collect();
set_fn_stmts.push(
syn::parse2::<syn::Stmt>(quote! {
m.set_fn(#fn_literal, FnAccess::Public, &[#(#fn_input_types),*],
CallableFunction::from_plugin(#fn_token_name()));
})
.unwrap(),
);
for reg_name in reg_names {
let fn_literal = syn::LitStr::new(&reg_name, proc_macro2::Span::call_site());
set_fn_stmts.push(
syn::parse2::<syn::Stmt>(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)]
@ -151,29 +159,33 @@ pub(crate) fn check_rename_collisions(fns: &Vec<ExportedFn>) -> Result<(), syn::
let mut renames = HashMap::<String, proc_macro2::Span>::new();
let mut names = HashMap::<String, proc_macro2::Span>::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);
if let Some(ref names) = itemfn.params().name {
for name in names {
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();

View File

@ -0,0 +1,518 @@
#[cfg(test)]
mod function_tests {
use crate::function::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::<ExportedFn>(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::<ExportedFn>(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::<syn::FnArg>(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::<ExportedFn>(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::<syn::FnArg>(quote! { x: usize }).unwrap()
);
assert_eq!(
item_fn.arg_list().nth(1).unwrap(),
&syn::parse2::<syn::FnArg>(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::<ExportedFn>(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::<syn::Path>(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::<ExportedFn>(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::<ExportedFn>(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::<ExportedFn>(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::<ExportedFn>(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::<ExportedFn>(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::<ExportedFn>(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::<syn::FnArg>(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::<ExportedFn>(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::<syn::FnArg>(quote! { level: usize }).unwrap()
);
assert_eq!(
item_fn.arg_list().nth(1).unwrap(),
&syn::parse2::<syn::FnArg>(quote! { message: &str }).unwrap()
);
}
#[test]
fn private_fn() {
let input_tokens: TokenStream = quote! {
fn do_nothing() { }
};
let item_fn = syn::parse2::<ExportedFn>(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::<ExportedFn>(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::<ExportedFn>(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 crate::function::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<Dynamic, Box<EvalAltResult>> {
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<dyn PluginFunction> { 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<EvalAltResult>;
pub fn dynamic_result_fn() -> Result<Dynamic, EvalBox> {
Ok(Dynamic::from(super::do_nothing()))
}
}
};
let item_fn = syn::parse2::<ExportedFn>(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<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 1usize,
"wrong arg count: {} != {}", args.len(), 1usize);
let arg0 = mem::take(args[0usize]).clone().cast::<usize>();
Ok(Dynamic::from(do_something(arg0)))
}
fn is_method_call(&self) -> bool { false }
fn is_varadic(&self) -> bool { false }
fn clone_boxed(&self) -> Box<dyn PluginFunction> { Box::new(Token()) }
fn input_types(&self) -> Box<[TypeId]> {
new_vec![TypeId::of::<usize>()].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<EvalAltResult>;
pub fn dynamic_result_fn(x: usize) -> Result<Dynamic, EvalBox> {
Ok(Dynamic::from(super::do_something(x)))
}
}
};
let item_fn = syn::parse2::<ExportedFn>(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<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 1usize,
"wrong arg count: {} != {}", args.len(), 1usize);
let arg0 = mem::take(args[0usize]).clone().cast::<usize>();
Ok(Dynamic::from(do_something(arg0)))
}
fn is_method_call(&self) -> bool { false }
fn is_varadic(&self) -> bool { false }
fn clone_boxed(&self) -> Box<dyn PluginFunction> { Box::new(MyType()) }
fn input_types(&self) -> Box<[TypeId]> {
new_vec![TypeId::of::<usize>()].into_boxed_slice()
}
}
};
let item_fn = syn::parse2::<ExportedFn>(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<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 2usize,
"wrong arg count: {} != {}", args.len(), 2usize);
let arg0 = mem::take(args[0usize]).clone().cast::<usize>();
let arg1 = mem::take(args[1usize]).clone().cast::<usize>();
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<dyn PluginFunction> { Box::new(Token()) }
fn input_types(&self) -> Box<[TypeId]> {
new_vec![TypeId::of::<usize>(),
TypeId::of::<usize>()].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<EvalAltResult>;
pub fn dynamic_result_fn(x: usize, y: usize) -> Result<Dynamic, EvalBox> {
Ok(Dynamic::from(super::add_together(x, y)))
}
}
};
let item_fn = syn::parse2::<ExportedFn>(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<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 2usize,
"wrong arg count: {} != {}", args.len(), 2usize);
let arg1 = mem::take(args[1usize]).clone().cast::<usize>();
let arg0: &mut _ = &mut args[0usize].write_lock::<usize>().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<dyn PluginFunction> { Box::new(Token()) }
fn input_types(&self) -> Box<[TypeId]> {
new_vec![TypeId::of::<usize>(),
TypeId::of::<usize>()].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<EvalAltResult>;
pub fn dynamic_result_fn(x: &mut usize, y: usize) -> Result<Dynamic, EvalBox> {
Ok(Dynamic::from(super::increment(x, y)))
}
}
};
let item_fn = syn::parse2::<ExportedFn>(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<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 1usize,
"wrong arg count: {} != {}", args.len(), 1usize);
let arg0 = mem::take(args[0usize]).clone().cast::<ImmutableString>();
Ok(Dynamic::from(special_print(&arg0)))
}
fn is_method_call(&self) -> bool { false }
fn is_varadic(&self) -> bool { false }
fn clone_boxed(&self) -> Box<dyn PluginFunction> { Box::new(Token()) }
fn input_types(&self) -> Box<[TypeId]> {
new_vec![TypeId::of::<ImmutableString>()].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<EvalAltResult>;
pub fn dynamic_result_fn(message: &str) -> Result<Dynamic, EvalBox> {
Ok(Dynamic::from(super::special_print(message)))
}
}
};
let item_fn = syn::parse2::<ExportedFn>(input_tokens).unwrap();
assert!(!item_fn.mutable_receiver());
assert_streams_eq(item_fn.generate(), expected_tokens);
}
}

2
codegen/src/test/mod.rs Normal file
View File

@ -0,0 +1,2 @@
mod function;
mod module;

1243
codegen/src/test/module.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -221,3 +221,225 @@ fn duplicate_fn_rename_test() -> Result<(), Box<EvalAltResult>> {
assert_eq!(&output_array[1].as_int().unwrap(), &43);
Ok(())
}
mod multiple_fn_rename {
use rhai::plugin::*;
#[export_module]
pub mod my_adds {
use rhai::{FLOAT, INT};
pub fn get_mystic_number() -> FLOAT {
42.0
}
#[rhai_fn(name = "add", name = "+", name = "add_together")]
pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT {
f1 + f2 * 2.0
}
#[rhai_fn(name = "add", name = "+", name = "add_together")]
pub fn add_int(i1: INT, i2: INT) -> INT {
i1 + i2 * 2
}
#[rhai_fn(name = "prop", get = "prop")]
pub fn get_prop(x: FLOAT) -> FLOAT {
x * 2.0
}
#[rhai_fn(name = "idx", index_get)]
pub fn index(x: FLOAT, i: INT) -> FLOAT {
x + (i as FLOAT)
}
}
}
#[test]
fn multiple_fn_rename_test() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
let m = rhai::exported_module!(crate::multiple_fn_rename::my_adds);
engine.load_package(m);
let output_array = engine.eval::<Array>(
r#"
let fx = get_mystic_number();
let fy1 = add(fx, 1.0);
let fy2 = add_together(fx, 1.0);
let fy3 = fx + 1.0;
let p1 = fx.prop;
let p2 = prop(fx);
let idx1 = fx[1];
let idx2 = idx(fx, 1);
let ix = 42;
let iy1 = add(ix, 1);
let iy2 = add_together(ix, 1);
let iy3 = ix + 1;
[fy1, fy2, fy3, iy1, iy2, iy3, p1, p2, idx1, idx2]
"#,
)?;
assert_eq!(&output_array[0].as_float().unwrap(), &44.0);
assert_eq!(&output_array[1].as_float().unwrap(), &44.0);
assert_eq!(&output_array[2].as_float().unwrap(), &44.0);
assert_eq!(&output_array[3].as_int().unwrap(), &44);
assert_eq!(&output_array[4].as_int().unwrap(), &44);
assert_eq!(&output_array[5].as_int().unwrap(), &44);
assert_eq!(&output_array[6].as_float().unwrap(), &84.0);
assert_eq!(&output_array[7].as_float().unwrap(), &84.0);
assert_eq!(&output_array[8].as_float().unwrap(), &43.0);
assert_eq!(&output_array[9].as_float().unwrap(), &43.0);
Ok(())
}
mod export_by_prefix {
use rhai::plugin::*;
#[export_module(export_prefix = "foo_")]
pub mod my_adds {
use rhai::{FLOAT, INT};
#[rhai_fn(name = "foo_add_f")]
pub fn foo_add1(f1: FLOAT, f2: FLOAT) -> FLOAT {
f1 + f2
}
#[rhai_fn(name = "bar_add_i")]
fn foo_add_int(i1: INT, i2: INT) -> INT {
i1 + i2
}
#[rhai_fn(name = "foo_add_float2")]
pub fn add_float2(f1: FLOAT, f2: FLOAT) -> FLOAT {
f1 + f2
}
pub fn foo_m(f1: FLOAT, f2: FLOAT) -> FLOAT {
f1 + f2
}
fn foo_n(i1: INT, i2: INT) -> INT {
i1 + i2
}
pub fn bar_m(f1: FLOAT, f2: FLOAT) -> FLOAT {
f1 + f2
}
}
}
#[test]
fn export_by_prefix_test() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
let m = rhai::exported_module!(crate::export_by_prefix::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::<Array>(
r#"import "Math::Advanced" as math;
let ex = 41.0;
let fx = math::foo_add_f(ex, 1.0);
let gx = math::foo_m(41.0, 1.0);
let ei = 41;
let fi = math::bar_add_i(ei, 1);
let gi = math::foo_n(41, 1);
[fx, gx, fi, gi]
"#,
)?;
assert_eq!(&output_array[0].as_float().unwrap(), &42.0);
assert_eq!(&output_array[1].as_float().unwrap(), &42.0);
assert_eq!(&output_array[2].as_int().unwrap(), &42);
assert_eq!(&output_array[3].as_int().unwrap(), &42);
assert!(matches!(*engine.eval::<FLOAT>(
r#"import "Math::Advanced" as math;
let ex = 41.0;
let fx = math::foo_add_float2(ex, 1.0);
fx
"#).unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(s, p)
if s == "math::foo_add_float2 (f64, f64)"
&& p == rhai::Position::new(3, 23)));
assert!(matches!(*engine.eval::<FLOAT>(
r#"import "Math::Advanced" as math;
let ex = 41.0;
let fx = math::bar_m(ex, 1.0);
fx
"#).unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(s, p)
if s == "math::bar_m (f64, f64)"
&& p == rhai::Position::new(3, 23)));
Ok(())
}
mod export_all {
use rhai::plugin::*;
#[export_module(export_all)]
pub mod my_adds {
use rhai::{FLOAT, INT};
#[rhai_fn(name = "foo_add_f")]
pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT {
f1 + f2
}
#[rhai_fn(name = "foo_add_i")]
fn add_int(i1: INT, i2: INT) -> INT {
i1 + i2
}
#[rhai_fn(skip)]
pub fn add_float2(f1: FLOAT, f2: FLOAT) -> FLOAT {
f1 + f2
}
pub fn foo_m(f1: FLOAT, f2: FLOAT) -> FLOAT {
f1 + f2
}
fn foo_n(i1: INT, i2: INT) -> INT {
i1 + i2
}
#[rhai_fn(skip)]
fn foo_p(i1: INT, i2: INT) -> INT {
i1 * i2
}
}
}
#[test]
fn export_all_test() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
let m = rhai::exported_module!(crate::export_all::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::<Array>(
r#"import "Math::Advanced" as math;
let ex = 41.0;
let fx = math::foo_add_f(ex, 1.0);
let gx = math::foo_m(41.0, 1.0);
let ei = 41;
let fi = math::foo_add_i(ei, 1);
let gi = math::foo_n(41, 1);
[fx, gx, fi, gi]
"#,
)?;
assert_eq!(&output_array[0].as_float().unwrap(), &42.0);
assert_eq!(&output_array[1].as_float().unwrap(), &42.0);
assert_eq!(&output_array[2].as_int().unwrap(), &42);
assert_eq!(&output_array[3].as_int().unwrap(), &42);
assert!(matches!(*engine.eval::<INT>(
r#"import "Math::Advanced" as math;
let ex = 41;
let fx = math::foo_p(ex, 1);
fx
"#).unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(s, p)
if s == "math::foo_p (i64, i64)"
&& p == rhai::Position::new(3, 23)));
Ok(())
}

View File

@ -1,5 +1,5 @@
use rhai::module_resolvers::*;
use rhai::{Engine, EvalAltResult, RegisterFn, FLOAT, INT};
use rhai::{Array, Engine, EvalAltResult, RegisterFn, FLOAT, INT};
pub mod one_fn_module_nested_attr {
use rhai::plugin::*;
@ -70,3 +70,133 @@ fn one_fn_submodule_nested_attr_test() -> Result<(), Box<EvalAltResult>> {
);
Ok(())
}
mod export_nested_by_prefix {
use rhai::plugin::*;
#[export_module(export_prefix = "foo_")]
pub mod my_adds {
pub mod foo_first_adders {
use rhai::{FLOAT, INT};
pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT {
f1 + f2
}
pub fn add_int(i1: INT, i2: INT) -> INT {
i1 + i2
}
}
pub mod foo_second_adders {
use rhai::{FLOAT, INT};
pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT {
f1 + f2
}
pub fn add_int(i1: INT, i2: INT) -> INT {
i1 + i2
}
}
#[rhai_mod(name = "foo_third_adders")]
pub mod baz_third_adders {
use rhai::{FLOAT, INT};
pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT {
f1 + f2
}
pub fn add_int(i1: INT, i2: INT) -> INT {
i1 + i2
}
}
pub mod bar_fourth_adders {
use rhai::{FLOAT, INT};
pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT {
f1 + f2
}
pub fn add_int(i1: INT, i2: INT) -> INT {
i1 + i2
}
}
}
}
#[test]
fn export_nested_by_prefix_test() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
let m = rhai::exported_module!(crate::export_nested_by_prefix::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::<Array>(
r#"import "Math::Advanced" as math;
let ex = 41.0;
let fx = math::foo_first_adders::add_float(ex, 1.0);
let ei = 41;
let fi = math::foo_first_adders::add_int(ei, 1);
let gx = 41.0;
let hx = math::foo_second_adders::add_float(gx, 1.0);
let gi = 41;
let hi = math::foo_second_adders::add_int(gi, 1);
[fx, hx, fi, hi]
"#,
)?;
assert_eq!(&output_array[0].as_float().unwrap(), &42.0);
assert_eq!(&output_array[1].as_float().unwrap(), &42.0);
assert_eq!(&output_array[2].as_int().unwrap(), &42);
assert_eq!(&output_array[3].as_int().unwrap(), &42);
assert!(matches!(*engine.eval::<FLOAT>(
r#"import "Math::Advanced" as math;
let ex = 41.0;
let fx = math::foo_third_adders::add_float(ex, 1.0);
fx
"#).unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(s, p)
if s == "math::foo_third_adders::add_float (f64, f64)"
&& p == rhai::Position::new(3, 41)));
assert!(matches!(*engine.eval::<FLOAT>(
r#"import "Math::Advanced" as math;
let ex = 41;
let fx = math::foo_third_adders::add_int(ex, 1);
fx
"#).unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(s, p)
if s == "math::foo_third_adders::add_int (i64, i64)"
&& p == rhai::Position::new(3, 41)));
assert!(matches!(*engine.eval::<FLOAT>(
r#"import "Math::Advanced" as math;
let ex = 41;
let fx = math::bar_fourth_adders::add_int(ex, 1);
fx
"#).unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(s, p)
if s == "math::bar_fourth_adders::add_int (i64, i64)"
&& p == rhai::Position::new(3, 42)));
assert!(matches!(*engine.eval::<FLOAT>(
r#"import "Math::Advanced" as math;
let ex = 41.0;
let fx = math::bar_fourth_adders::add_float(ex, 1.0);
fx
"#).unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(s, p)
if s == "math::bar_fourth_adders::add_float (f64, f64)"
&& p == rhai::Position::new(3, 42)));
Ok(())
}

View File

@ -1,5 +1,5 @@
error[E0277]: the trait bound `NonClonable: std::clone::Clone` is not satisfied
error[E0277]: the trait bound `NonClonable: 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`
| ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`

View File

@ -1,5 +1,5 @@
error[E0277]: the trait bound `NonClonable: std::clone::Clone` is not satisfied
error[E0277]: the trait bound `NonClonable: 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`
| ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`

View File

@ -0,0 +1,27 @@
use rhai::plugin::*;
struct NonClonable {
a: f32,
b: u32,
c: char,
d: bool,
}
#[export_fn]
pub fn test_fn(input: f32) -> NonClonable {
NonClonable {
a: input,
b: 10,
c: 'a',
d: true,
}
}
fn main() {
let n = test_fn(20.0);
if n.c == 'a' {
println!("yes");
} else {
println!("no");
}
}

View File

@ -0,0 +1,10 @@
error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
--> $DIR/rhai_fn_non_clonable_return.rs:11:8
|
11 | pub fn test_fn(input: f32) -> NonClonable {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
|
::: $WORKSPACE/src/any.rs
|
| pub fn from<T: Variant + Clone>(value: T) -> Self {
| ----- required by this bound in `rhai::Dynamic::from`

View File

@ -0,0 +1,30 @@
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", get = "bar")]
pub fn test_fn(input: Point) -> bool {
input.x > input.y
}
#[rhai_fn(get = "bar")]
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");
}
}

View File

@ -0,0 +1,17 @@
error: duplicate Rhai signature for 'get$bar'
--> $DIR/rhai_fn_rename_collision_oneattr_multiple.rs:17:15
|
17 | #[rhai_fn(get = "bar")]
| ^^^^^^^^^^^
error: duplicated function renamed 'get$bar'
--> $DIR/rhai_fn_rename_collision_oneattr_multiple.rs:12:15
|
12 | #[rhai_fn(name = "foo", get = "bar")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0433]: failed to resolve: use of undeclared type or module `test_module`
--> $DIR/rhai_fn_rename_collision_oneattr_multiple.rs:25:8
|
25 | if test_module::test_fn(n) {
| ^^^^^^^^^^^ use of undeclared type or module `test_module`

View File

@ -0,0 +1,25 @@
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", name = "bar", name = "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");
}
}

View File

@ -0,0 +1,17 @@
error: duplicate Rhai signature for 'foo'
--> $DIR/rhai_fn_rename_collision_with_itself.rs:12:15
|
12 | #[rhai_fn(name = "foo", name = "bar", name = "foo")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: duplicated function renamed 'foo'
--> $DIR/rhai_fn_rename_collision_with_itself.rs:12:15
|
12 | #[rhai_fn(name = "foo", name = "bar", name = "foo")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0433]: failed to resolve: use of undeclared type or module `test_module`
--> $DIR/rhai_fn_rename_collision_with_itself.rs:20:8
|
20 | if test_module::test_fn(n) {
| ^^^^^^^^^^^ use of undeclared type or module `test_module`

View File

@ -0,0 +1,29 @@
use rhai::plugin::*;
struct NonClonable {
a: f32,
b: u32,
c: char,
d: bool,
}
#[export_module]
pub mod test_mod {
pub fn test_fn(input: f32) -> NonClonable {
NonClonable {
a: input,
b: 10,
c: 'a',
d: true,
}
}
}
fn main() {
let n = test_mod::test_fn(20.0);
if n.c == 'a' {
println!("yes");
} else {
println!("no");
}
}

View File

@ -0,0 +1,10 @@
error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
--> $DIR/rhai_mod_non_clonable_return.rs:12:12
|
12 | pub fn test_fn(input: f32) -> NonClonable {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
|
::: $WORKSPACE/src/any.rs
|
| pub fn from<T: Variant + Clone>(value: T) -> Self {
| ----- required by this bound in `rhai::Dynamic::from`

View File

@ -46,8 +46,8 @@ The Rhai Scripting Language
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)
1. [Export a Rust Module](plugins/module.md)
2. [Export a Rust 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)

View File

@ -1,5 +1,5 @@
Create a Plugin Function
========================
Export a Rust Function to Rhai
=============================
{{#include ../links.md}}
@ -11,12 +11,11 @@ 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<Dynamic, Box<EvalAltResult>>` | 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 |
| Macro | Apply to | Behavior |
| ----------------------- | --------------------------------------------------------------- | -------------------------------------------------------- |
| `#[export_fn]` | Rust function defined in a Rust module | Export the function |
| `register_exported_fn!` | [`Engine`] instance, register name string, use path to function | Register function into an [`Engine`] under specific name |
| `set_exported_fn!` | [`Module`] instance, register name string, use path to function | Register function into an [`Module`] under specific name |
`#[export_fn]` and `register_exported_fn!`

View File

@ -3,9 +3,11 @@ Plugins
{{#include ../links.md}}
Rhai contains a robust _plugin_ system that greatly simplifies registration of custom functions.
Rhai contains a robust _plugin_ system that greatly simplifies registration of custom
functionality.
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`].
Instead of using the large `Engine::register_XXX` API or the parallel `Module::set_fn_XXX` API,
a _plugin_ simplifies the work of creating and registering new functionality in an [`Engine`].
Plugins are processed via a set of procedural macros under the `rhai::plugins` module.
Plugins are processed via a set of procedural macros under the `rhai::plugins` module. These
allow registering Rust functions directly in the Engine, or adding Rust modules as packages.

View File

@ -1,43 +1,26 @@
Create a Plugin Module
======================
Export a Rust Module to Rhai
============================
{{#include ../links.md}}
The core of creating a plugin [module] is the `#[export_module]` attribute.
When applied to a Rust module, the `#[export_module]` attribute generates the necessary
code and metadata to allow Rhai access to its public (i.e. marked `pub`) functions. This code
is exactly what would need to be written by hand to achieve the same goal, and is custom fit
to each exported item.
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].
This Rust module can then either be loaded into an [`Engine`] as a normal [module] or
registered as a [custom package]. This is done by using the `exported_module!` macro.
Macros
------
Using`#[export_module]` and `exported_module!`
---------------------------------------------
| 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<Dynamic, Box<EvalAltResult>>` | 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.
Apply `#[export_module]` onto a Rust module to convert all `pub` functions into Rhai plugin
functions.
```rust
use rhai::plugins::*; // import macros
use rhai::plugins::*; // a "prelude" import for macros
#[export_module]
mod my_module {
@ -58,22 +41,24 @@ mod my_module {
42
}
}
```
In order to load this into an [`Engine`], use the `load_package` method on the exported module:
```rust
fn main() {
let mut engine = Engine::new();
// 'exported_module!' creates the plugin module.
// The macro call creates the Rhai module.
let module = exported_module!(my_module);
// A module can simply be loaded as a custom package.
// A module can simply be loaded, registering all public its contents.
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.
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");
@ -89,6 +74,47 @@ increment(x);
x == 43;
```
Registering this as a custom package is almost the same, except that a module resolver must
point to the module, rather than being loaded directly. See the [module] section for more
information.
Function Overloading and Operators
---------------------------------
Operators and overloaded functions can be specified via applying the `#[rhai_fn(name = "...")]`
attribute to individual functions.
The text string given as the `name` parameter to `#[rhai_fn]` is used to register the function with
the [`Engine`], disregarding the actual name of the function.
With `#[rhai_fn(name = "...")]`, multiple functions may be registered under the same name in Rhai, so long as they have different parameters.
Operators (which require function names that are not valid for Rust) can also be registered this way.
```rust
use rhai::plugins::*; // a "prelude" import for 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 {
...
}
}
```
Getters, Setters and Indexers
-----------------------------
@ -97,7 +123,7 @@ Functions can be marked as [getters/setters] and [indexers] for [custom types] v
attribute, which is applied on a function level.
```rust
use rhai::plugins::*; // import macros
use rhai::plugins::*; // a "prelude" import for macros
#[export_module]
mod my_module {
@ -129,43 +155,6 @@ mod my_module {
```
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
------------------
@ -176,7 +165,7 @@ A syntax error is generated if the function with `#[rhai_fn(return_raw)]` does n
have the appropriate return type.
```rust
use rhai::plugins::*; // import macros
use rhai::plugins::*; // a "prelude" import for macros
#[export_module]
mod my_module {
@ -192,3 +181,35 @@ mod my_module {
}
}
```
`#[export_module]` Parameters
----------------------------
Parameters can be applied to the `#[export_module]` attribute to override its default behavior.
| Parameter | Behavior |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| _None_ | Export only public (i.e. `pub`) functions |
| `export_all` | Export all functions (including private, non-`pub` functions); use `#[rhai_fn(skip)]` on individual functions to avoid export |
| `export_prefix = "..."` | Export functions (including private, non-`pub` functions) with names starting with a specific prefix |
Inner Attributes
----------------
Inner attributes can be applied to the inner items of a module to tweak the export process.
`#[rhai_fn]` is applied to functions, while `#[rhai_mod]` is applied to sub-modules.
Parameters should be set on inner attributes to specify the desired behavior.
| Attribute Parameter | Use with | Apply to | Behavior |
| ------------------- | --------------------------- | -------------------------------------------------------- | ----------------------------------------------------- |
| `skip` | `#[rhai_fn]`, `#[rhai_mod]` | Function or sub-module | Do not export this function/sub-module |
| `name = "..."` | `#[rhai_fn]`, `#[rhai_mod]` | Function or sub-module | Register function/sub-module under the specified name |
| `get = "..."` | `#[rhai_fn]` | Function with `&mut` first parameter | Register a getter for the named property |
| `set = "..."` | `#[rhai_fn]` | Function with `&mut` first parameter | Register a setter for the named property |
| `index_get` | `#[rhai_fn]` | Function with `&mut` first parameter | Register an index getter |
| `index_set` | `#[rhai_fn]` | Function with `&mut` first parameter | Register an index setter |
| `return_raw` | `#[rhai_fn]` | Function returning `Result<Dynamic, Box<EvalAltResult>>` | Mark this as a [fallible function] |

View File

@ -1,5 +1,7 @@
cargo-features = ["named-profiles"]
[workspace]
[package]
name = "no_std_test"
version = "0.1.0"

View File

@ -86,24 +86,16 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
#[export_module]
mod array_functions {
#[rhai_fn(name = "len", get = "len")]
#[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)
}
#[rhai_fn(name = "append", name = "+=")]
#[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 {

View File

@ -5,10 +5,13 @@ use crate::plugin::*;
use crate::result::EvalAltResult;
def_package!(crate:EvalPackage:"Disable 'eval'.", lib, {
set_exported_fn!(lib, "eval", eval_override);
lib.combine_flatten(exported_module!(eval_override));
});
#[export_fn(return_raw)]
fn eval_override(_script: ImmutableString) -> Result<Dynamic, Box<EvalAltResult>> {
Err("eval is evil!".into())
#[export_module]
mod eval_override {
#[rhai_fn(return_raw)]
pub fn eval(_script: ImmutableString) -> Result<Dynamic, Box<EvalAltResult>> {
Err("eval is evil!".into())
}
}

View File

@ -8,14 +8,9 @@ def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, {
#[export_module]
mod fn_ptr_functions {
#[rhai_fn(name = "name", get = "name")]
#[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)
}
}

View File

@ -18,33 +18,25 @@ mod map_functions {
pub fn has(map: &mut Map, prop: ImmutableString) -> bool {
map.contains_key(&prop)
}
#[rhai_fn(name = "len", get = "len")]
#[inline(always)]
pub fn len(map: &mut Map) -> INT {
map.len() as INT
}
#[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]
#[inline(always)]
pub fn remove(x: &mut Map, name: ImmutableString) -> Dynamic {
x.remove(&name).unwrap_or_else(|| ().into())
}
#[rhai_fn(name = "mixin", name = "+=")]
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)| {

View File

@ -157,78 +157,46 @@ mod float_functions {
pub fn log10(x: FLOAT) -> FLOAT {
x.log10()
}
#[rhai_fn(name = "floor", get = "floor")]
#[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)
}
#[rhai_fn(name = "ceiling", get = "ceiling")]
#[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)
}
#[rhai_fn(name = "round", get = "round")]
#[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)
}
#[rhai_fn(name = "int", get = "int")]
#[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)
}
#[rhai_fn(name = "fraction", get = "fraction")]
#[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)
}
#[rhai_fn(name = "is_nan", get = "is_nan")]
#[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)
}
#[rhai_fn(name = "is_finite", get = "is_finite")]
#[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)
}
#[rhai_fn(name = "is_infinite", get = "is_infinite")]
#[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<Dynamic, Box<EvalAltResult>> {

View File

@ -140,6 +140,7 @@ mod string_functions {
pub fn add_prepend_unit(_x: (), s: ImmutableString) -> ImmutableString {
s
}
#[rhai_fn(name = "+=")]
#[inline(always)]
pub fn append_char(s: &mut ImmutableString, ch: char) {
@ -151,17 +152,12 @@ mod string_functions {
*s += &add;
}
#[rhai_fn(name = "len", get = "len")]
#[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();

View File

@ -30,7 +30,8 @@ mod time_functions {
pub fn timestamp() -> Instant {
Instant::now()
}
#[rhai_fn(return_raw)]
#[rhai_fn(name = "elapsed", get = "elapsed", return_raw)]
pub fn elapsed(timestamp: &mut Instant) -> Result<Dynamic, Box<EvalAltResult>> {
#[cfg(not(feature = "no_float"))]
{
@ -52,12 +53,6 @@ mod time_functions {
}
}
#[rhai_fn(get = "elapsed", return_raw)]
#[inline(always)]
pub fn elapsed_prop(timestamp: &mut Instant) -> Result<Dynamic, Box<EvalAltResult>> {
elapsed(timestamp)
}
#[rhai_fn(return_raw, name = "-")]
pub fn time_diff(ts1: Instant, ts2: Instant) -> Result<Dynamic, Box<EvalAltResult>> {
#[cfg(not(feature = "no_float"))]

View File

@ -1,6 +1,5 @@
#![cfg(not(feature = "no_function"))]
use rhai::{Engine, EvalAltResult, ParseErrorType, INT};
use std::io::Read;
#[test]
fn test_functions() -> Result<(), Box<EvalAltResult>> {

View File

@ -12,6 +12,8 @@ mod test {
#[cfg(not(feature = "no_object"))]
pub mod feature {
use rhai::{Array, Dynamic, EvalAltResult};
#[rhai_fn(get = "foo", return_raw)]
#[inline(always)]
pub fn foo(array: &mut Array) -> Result<Dynamic, Box<EvalAltResult>> {
@ -19,7 +21,7 @@ mod test {
}
}
#[rhai_fn(name = "test")]
#[rhai_fn(name = "test", name = "hi")]
#[inline(always)]
pub fn len(array: &mut Array, mul: INT) -> INT {
(array.len() as INT) * mul
@ -72,6 +74,8 @@ fn test_plugins_package() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; a.foo")?, 1);
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; test(a, 2)")?, 6);
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; hi(a, 2)")?, 6);
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; test(a, 2)")?, 6);
assert_eq!(engine.eval::<INT>("2 + 2")?, 5);
assert_eq!(