export_fn: add return_raw attribute

This commit is contained in:
J Henry Waugh 2020-08-09 14:19:39 -05:00
parent c5937f990e
commit 07a4541949
12 changed files with 227 additions and 21 deletions

View File

@ -1,10 +1,14 @@
#![allow(unused)] #![allow(unused)]
use std::collections::HashMap;
use quote::{quote, quote_spanned}; 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)] #[derive(Debug, Default)]
pub(crate) struct ExportedFnParams { pub(crate) struct ExportedFnParams {
pub name: Option<String>, pub name: Option<String>,
pub return_raw: bool,
} }
impl Parse for ExportedFnParams { impl Parse for ExportedFnParams {
@ -12,25 +16,68 @@ impl Parse for ExportedFnParams {
if args.is_empty() { if args.is_empty() {
return Ok(ExportedFnParams::default()); return Ok(ExportedFnParams::default());
} }
let assignment: syn::ExprAssign = args.parse()?;
let attr_name: syn::Ident = match assignment.left.as_ref() { let arg_list = args.call(
syn::Expr::Path(syn::ExprPath { path: attr_path, .. }) => attr_path.get_ident().cloned() syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_separated_nonempty,
.ok_or_else(|| syn::Error::new(attr_path.span(), "expecting attribute name"))?, )?;
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")), x => return Err(syn::Error::new(x.span(), "expecting attribute name")),
}; };
if &attr_name != "name" { let attr_value = match right.as_ref() {
return Err(syn::Error::new(attr_name.span(), format!("unknown attribute '{}'", &attr_name))); 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() { let mut name = None;
syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(string), .. }) => string.value(), let mut return_raw = false;
x => return Err(syn::Error::new(x.span(), "expecting string literal value")), 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 { Ok(ExportedFnParams { name, return_raw })
name: Some(attr_value),
})
} }
} }
@ -361,6 +408,19 @@ impl ExportedFn {
unpack_stmts.push(arg0); 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()); let type_name = syn::Ident::new(on_type_name, proc_macro2::Span::call_site());
quote! { quote! {
impl PluginFunction for #type_name { impl PluginFunction for #type_name {
@ -373,7 +433,7 @@ impl ExportedFn {
args.len(), #arg_count), Position::none()))); args.len(), #arg_count), Position::none())));
} }
#(#unpack_stmts)* #(#unpack_stmts)*
Ok(Dynamic::from(#name(#(#unpack_exprs),*))) #return_expr
} }
fn is_method_call(&self) -> bool { #is_method_call } fn is_method_call(&self) -> bool { #is_method_call }

View File

@ -230,3 +230,44 @@ fn duplicate_fn_rename_test() -> Result<(), Box<EvalAltResult>> {
assert_eq!(&output_array[1].as_int().unwrap(), &43); assert_eq!(&output_array[1].as_int().unwrap(), &43);
Ok(()) 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(())
}

View File

@ -6,7 +6,7 @@ struct Point {
y: f32, y: f32,
} }
#[export_fn(unknown = true)] #[export_fn(unknown = "thing")]
pub fn test_fn(input: Point) -> bool { pub fn test_fn(input: Point) -> bool {
input.x > input.y input.x > input.y
} }

View File

@ -1,7 +1,7 @@
error: unknown attribute 'unknown' error: unknown attribute 'unknown'
--> $DIR/export_fn_bad_attr.rs:9:13 --> $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 error[E0425]: cannot find function `test_fn` in this scope

View File

@ -1,4 +1,4 @@
error: expecting string literal value error: expecting string literal
--> $DIR/export_fn_bad_value.rs:9:20 --> $DIR/export_fn_bad_value.rs:9:20
| |
9 | #[export_fn(name = true)] 9 | #[export_fn(name = true)]

View 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");
}
}

View 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

View File

@ -1,4 +1,4 @@
error: expected assignment expression error: expecting identifier
--> $DIR/export_fn_junk_arg.rs:9:13 --> $DIR/export_fn_junk_arg.rs:9:13
| |
9 | #[export_fn("wheeeee")] 9 | #[export_fn("wheeeee")]

View 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");
}
}

View 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

View 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");
}
}

View 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