commit
ecce9c5477
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -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
|
||||
|
10
Cargo.toml
10
Cargo.toml
@ -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"
|
||||
|
@ -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
|
||||
------------
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
@ -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(®_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(®_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();
|
||||
|
518
codegen/src/test/function.rs
Normal file
518
codegen/src/test/function.rs
Normal 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
2
codegen/src/test/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
mod function;
|
||||
mod module;
|
1243
codegen/src/test/module.rs
Normal file
1243
codegen/src/test/module.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -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(())
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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`
|
||||
|
@ -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`
|
||||
|
27
codegen/ui_tests/rhai_fn_non_clonable_return.rs
Normal file
27
codegen/ui_tests/rhai_fn_non_clonable_return.rs
Normal 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");
|
||||
}
|
||||
}
|
10
codegen/ui_tests/rhai_fn_non_clonable_return.stderr
Normal file
10
codegen/ui_tests/rhai_fn_non_clonable_return.stderr
Normal 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`
|
@ -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");
|
||||
}
|
||||
}
|
@ -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`
|
25
codegen/ui_tests/rhai_fn_rename_collision_with_itself.rs
Normal file
25
codegen/ui_tests/rhai_fn_rename_collision_with_itself.rs
Normal 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");
|
||||
}
|
||||
}
|
17
codegen/ui_tests/rhai_fn_rename_collision_with_itself.stderr
Normal file
17
codegen/ui_tests/rhai_fn_rename_collision_with_itself.stderr
Normal 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`
|
29
codegen/ui_tests/rhai_mod_non_clonable_return.rs
Normal file
29
codegen/ui_tests/rhai_mod_non_clonable_return.rs
Normal 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");
|
||||
}
|
||||
}
|
10
codegen/ui_tests/rhai_mod_non_clonable_return.stderr
Normal file
10
codegen/ui_tests/rhai_mod_non_clonable_return.stderr
Normal 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`
|
@ -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)
|
||||
|
@ -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!`
|
||||
|
@ -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.
|
||||
|
@ -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] |
|
||||
|
@ -1,5 +1,7 @@
|
||||
cargo-features = ["named-profiles"]
|
||||
|
||||
[workspace]
|
||||
|
||||
[package]
|
||||
name = "no_std_test"
|
||||
version = "0.1.0"
|
||||
|
@ -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 {
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)| {
|
||||
|
@ -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>> {
|
||||
|
@ -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();
|
||||
|
@ -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"))]
|
||||
|
@ -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>> {
|
||||
|
@ -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!(
|
||||
|
Loading…
Reference in New Issue
Block a user