Merge pull request #30 from jhwgh1968/plugins
export_fn: add return_raw attribute
This commit is contained in:
commit
f98cea4f20
@ -1,10 +1,14 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::{parse::Parse, parse::ParseStream, spanned::Spanned};
|
||||
use syn::{parse::Parse, parse::ParseStream, parse::Parser, spanned::Spanned};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct ExportedFnParams {
|
||||
pub name: Option<String>,
|
||||
pub return_raw: bool,
|
||||
}
|
||||
|
||||
impl Parse for ExportedFnParams {
|
||||
@ -12,25 +16,68 @@ impl Parse for ExportedFnParams {
|
||||
if args.is_empty() {
|
||||
return Ok(ExportedFnParams::default());
|
||||
}
|
||||
let assignment: syn::ExprAssign = args.parse()?;
|
||||
|
||||
let attr_name: syn::Ident = match assignment.left.as_ref() {
|
||||
syn::Expr::Path(syn::ExprPath { path: attr_path, .. }) => attr_path.get_ident().cloned()
|
||||
.ok_or_else(|| syn::Error::new(attr_path.span(), "expecting attribute name"))?,
|
||||
let arg_list = args.call(
|
||||
syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_separated_nonempty,
|
||||
)?;
|
||||
|
||||
let mut attrs: HashMap<proc_macro2::Ident, Option<syn::LitStr>> = HashMap::new();
|
||||
for arg in arg_list {
|
||||
let (left, right) = match arg {
|
||||
syn::Expr::Assign(syn::ExprAssign {
|
||||
ref left,
|
||||
ref right,
|
||||
..
|
||||
}) => {
|
||||
let attr_name: syn::Ident = match left.as_ref() {
|
||||
syn::Expr::Path(syn::ExprPath {
|
||||
path: attr_path, ..
|
||||
}) => attr_path.get_ident().cloned().ok_or_else(|| {
|
||||
syn::Error::new(attr_path.span(), "expecting attribute name")
|
||||
})?,
|
||||
x => return Err(syn::Error::new(x.span(), "expecting attribute name")),
|
||||
};
|
||||
if &attr_name != "name" {
|
||||
return Err(syn::Error::new(attr_name.span(), format!("unknown attribute '{}'", &attr_name)));
|
||||
let attr_value = match right.as_ref() {
|
||||
syn::Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(string),
|
||||
..
|
||||
}) => string.clone(),
|
||||
x => return Err(syn::Error::new(x.span(), "expecting string literal")),
|
||||
};
|
||||
(attr_name, Some(attr_value))
|
||||
}
|
||||
syn::Expr::Path(syn::ExprPath {
|
||||
path: attr_path, ..
|
||||
}) => attr_path
|
||||
.get_ident()
|
||||
.cloned()
|
||||
.map(|a| (a, None))
|
||||
.ok_or_else(|| syn::Error::new(attr_path.span(), "expecting attribute name"))?,
|
||||
x => return Err(syn::Error::new(x.span(), "expecting identifier")),
|
||||
};
|
||||
attrs.insert(left, right);
|
||||
}
|
||||
|
||||
let attr_value: String = match assignment.right.as_ref() {
|
||||
syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(string), .. }) => string.value(),
|
||||
x => return Err(syn::Error::new(x.span(), "expecting string literal value")),
|
||||
};
|
||||
let mut name = None;
|
||||
let mut return_raw = false;
|
||||
for (ident, value) in attrs.drain() {
|
||||
match (ident.to_string().as_ref(), value) {
|
||||
("name", Some(s)) => name = Some(s.value()),
|
||||
("name", None) => return Err(syn::Error::new(ident.span(), "requires value")),
|
||||
("return_raw", None) => return_raw = true,
|
||||
("return_raw", Some(s)) => {
|
||||
return Err(syn::Error::new(s.span(), "extraneous value"))
|
||||
}
|
||||
(attr, _) => {
|
||||
return Err(syn::Error::new(
|
||||
ident.span(),
|
||||
format!("unknown attribute '{}'", attr),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ExportedFnParams {
|
||||
name: Some(attr_value),
|
||||
})
|
||||
Ok(ExportedFnParams { name, return_raw })
|
||||
}
|
||||
}
|
||||
|
||||
@ -361,6 +408,19 @@ impl ExportedFn {
|
||||
unpack_stmts.push(arg0);
|
||||
}
|
||||
|
||||
// Handle "raw returns", aka cases where the result is a dynamic or an error.
|
||||
//
|
||||
// This allows skipping the Dynamic::from wrap.
|
||||
let return_expr = if !self.params.return_raw {
|
||||
quote! {
|
||||
Ok(Dynamic::from(#name(#(#unpack_exprs),*)))
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#name(#(#unpack_exprs),*)
|
||||
}
|
||||
};
|
||||
|
||||
let type_name = syn::Ident::new(on_type_name, proc_macro2::Span::call_site());
|
||||
quote! {
|
||||
impl PluginFunction for #type_name {
|
||||
@ -373,7 +433,7 @@ impl ExportedFn {
|
||||
args.len(), #arg_count), Position::none())));
|
||||
}
|
||||
#(#unpack_stmts)*
|
||||
Ok(Dynamic::from(#name(#(#unpack_exprs),*)))
|
||||
#return_expr
|
||||
}
|
||||
|
||||
fn is_method_call(&self) -> bool { #is_method_call }
|
||||
|
@ -230,3 +230,44 @@ fn duplicate_fn_rename_test() -> Result<(), Box<EvalAltResult>> {
|
||||
assert_eq!(&output_array[1].as_int().unwrap(), &43);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub mod raw_returning_fn {
|
||||
use rhai::plugin::*;
|
||||
use rhai::FLOAT;
|
||||
|
||||
#[export_fn(return_raw)]
|
||||
pub fn distance_function(
|
||||
x1: FLOAT,
|
||||
y1: FLOAT,
|
||||
x2: FLOAT,
|
||||
y2: FLOAT,
|
||||
) -> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
Ok(Dynamic::from(
|
||||
((y2 - y1).abs().powf(2.0) + (x2 - x1).abs().powf(2.0)).sqrt(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_returning_fn_test() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("get_mystic_number", || 42 as FLOAT);
|
||||
let mut m = Module::new();
|
||||
rhai::register_exported_fn!(
|
||||
m,
|
||||
"euclidean_distance".to_string(),
|
||||
raw_returning_fn::distance_function
|
||||
);
|
||||
let mut r = StaticModuleResolver::new();
|
||||
r.insert("Math::Advanced".to_string(), m);
|
||||
engine.set_module_resolver(Some(r));
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<FLOAT>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"#
|
||||
)?,
|
||||
41.0
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ struct Point {
|
||||
y: f32,
|
||||
}
|
||||
|
||||
#[export_fn(unknown = true)]
|
||||
#[export_fn(unknown = "thing")]
|
||||
pub fn test_fn(input: Point) -> bool {
|
||||
input.x > input.y
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
error: unknown attribute 'unknown'
|
||||
--> $DIR/export_fn_bad_attr.rs:9:13
|
||||
|
|
||||
9 | #[export_fn(unknown = true)]
|
||||
9 | #[export_fn(unknown = "thing")]
|
||||
| ^^^^^^^
|
||||
|
||||
error[E0425]: cannot find function `test_fn` in this scope
|
||||
|
@ -1,4 +1,4 @@
|
||||
error: expecting string literal value
|
||||
error: expecting string literal
|
||||
--> $DIR/export_fn_bad_value.rs:9:20
|
||||
|
|
||||
9 | #[export_fn(name = true)]
|
||||
|
24
codegen/ui_tests/export_fn_extra_value.rs
Normal file
24
codegen/ui_tests/export_fn_extra_value.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use rhai::plugin::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Point {
|
||||
x: f32,
|
||||
y: f32,
|
||||
}
|
||||
|
||||
#[export_fn(return_raw = "yes")]
|
||||
pub fn test_fn(input: Point) -> bool {
|
||||
input.x > input.y
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let n = Point {
|
||||
x: 0.0,
|
||||
y: 10.0,
|
||||
};
|
||||
if test_fn(n) {
|
||||
println!("yes");
|
||||
} else {
|
||||
println!("no");
|
||||
}
|
||||
}
|
11
codegen/ui_tests/export_fn_extra_value.stderr
Normal file
11
codegen/ui_tests/export_fn_extra_value.stderr
Normal file
@ -0,0 +1,11 @@
|
||||
error: extraneous value
|
||||
--> $DIR/export_fn_extra_value.rs:9:26
|
||||
|
|
||||
9 | #[export_fn(return_raw = "yes")]
|
||||
| ^^^^^
|
||||
|
||||
error[E0425]: cannot find function `test_fn` in this scope
|
||||
--> $DIR/export_fn_extra_value.rs:19:8
|
||||
|
|
||||
19 | if test_fn(n) {
|
||||
| ^^^^^^^ not found in this scope
|
@ -1,4 +1,4 @@
|
||||
error: expected assignment expression
|
||||
error: expecting identifier
|
||||
--> $DIR/export_fn_junk_arg.rs:9:13
|
||||
|
|
||||
9 | #[export_fn("wheeeee")]
|
||||
|
24
codegen/ui_tests/export_fn_missing_value.rs
Normal file
24
codegen/ui_tests/export_fn_missing_value.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use rhai::plugin::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Point {
|
||||
x: f32,
|
||||
y: f32,
|
||||
}
|
||||
|
||||
#[export_fn(name)]
|
||||
pub fn test_fn(input: Point) -> bool {
|
||||
input.x > input.y
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let n = Point {
|
||||
x: 0.0,
|
||||
y: 10.0,
|
||||
};
|
||||
if test_fn(n) {
|
||||
println!("yes");
|
||||
} else {
|
||||
println!("no");
|
||||
}
|
||||
}
|
11
codegen/ui_tests/export_fn_missing_value.stderr
Normal file
11
codegen/ui_tests/export_fn_missing_value.stderr
Normal file
@ -0,0 +1,11 @@
|
||||
error: requires value
|
||||
--> $DIR/export_fn_missing_value.rs:9:13
|
||||
|
|
||||
9 | #[export_fn(name)]
|
||||
| ^^^^
|
||||
|
||||
error[E0425]: cannot find function `test_fn` in this scope
|
||||
--> $DIR/export_fn_missing_value.rs:19:8
|
||||
|
|
||||
19 | if test_fn(n) {
|
||||
| ^^^^^^^ not found in this scope
|
24
codegen/ui_tests/export_fn_path_attr.rs
Normal file
24
codegen/ui_tests/export_fn_path_attr.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use rhai::plugin::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Point {
|
||||
x: f32,
|
||||
y: f32,
|
||||
}
|
||||
|
||||
#[export_fn(rhai::name = "thing")]
|
||||
pub fn test_fn(input: Point) -> bool {
|
||||
input.x > input.y
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let n = Point {
|
||||
x: 0.0,
|
||||
y: 10.0,
|
||||
};
|
||||
if test_fn(n) {
|
||||
println!("yes");
|
||||
} else {
|
||||
println!("no");
|
||||
}
|
||||
}
|
11
codegen/ui_tests/export_fn_path_attr.stderr
Normal file
11
codegen/ui_tests/export_fn_path_attr.stderr
Normal file
@ -0,0 +1,11 @@
|
||||
error: expecting attribute name
|
||||
--> $DIR/export_fn_path_attr.rs:9:13
|
||||
|
|
||||
9 | #[export_fn(rhai::name = "thing")]
|
||||
| ^^^^^^^^^^
|
||||
|
||||
error[E0425]: cannot find function `test_fn` in this scope
|
||||
--> $DIR/export_fn_path_attr.rs:19:8
|
||||
|
|
||||
19 | if test_fn(n) {
|
||||
| ^^^^^^^ not found in this scope
|
Loading…
Reference in New Issue
Block a user