From 706477f1393a606030fc7487a6f84d4726439b6f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 14 Jun 2017 14:08:30 -0700 Subject: [PATCH] replace py_fn! macro with pyfn attribute --- README.md | 20 +- pyo3cls/src/lib.rs | 24 --- pyo3cls/src/method.rs | 4 +- pyo3cls/src/module.rs | 216 +++++++++++++++++++- pyo3cls/src/py_method.rs | 2 +- src/argparse.rs | 406 +------------------------------------ src/function.rs | 141 ------------- src/lib.rs | 25 ++- src/objects/stringutils.rs | 4 +- tests/test_function.rs | 92 --------- 10 files changed, 255 insertions(+), 679 deletions(-) delete mode 100644 src/function.rs delete mode 100644 tests/test_function.rs diff --git a/README.md b/README.md index 3fec64c9..f92228ee 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,17 @@ use pyo3::{py, PyResult, Python, PyModule}; #[py::modinit(rust2py)] fn init_mod(py: Python, m: &PyModule) -> PyResult<()> { m.add(py, "__doc__", "This module is implemented in Rust.")?; - m.add(py, "sum_as_string", py_fn!(py, sum_as_string_py(a: i64, b:i64)))?; + + #[pyfn(m, "sum_as_string")] + // pyo3 aware function. All of our python interface could be + // declared in a separate module. + // Note that the py_fn!() macro automatically converts the arguments from + // Python objects to Rust values; and the Rust return value back into a Python object. + fn sum_as_string_py(_: Python, a:i64, b:i64) -> PyResult { + let out = sum_as_string(a, b); + Ok(out) + } + Ok(()) } @@ -93,14 +103,6 @@ fn sum_as_string(a:i64, b:i64) -> String { format!("{}", a + b).to_string() } -// pyo3 aware function. All of our python interface could be -// declared in a separate module. -// Note that the py_fn!() macro automatically converts the arguments from -// Python objects to Rust values; and the Rust return value back into a Python object. -fn sum_as_string_py(_: Python, a:i64, b:i64) -> PyResult { - let out = sum_as_string(a, b); - Ok(out) -} ``` For `setup.py` integration, see [setuptools-rust](https://github.com/PyO3/setuptools-rust) diff --git a/pyo3cls/src/lib.rs b/pyo3cls/src/lib.rs index 80ca959f..1f96c72a 100644 --- a/pyo3cls/src/lib.rs +++ b/pyo3cls/src/lib.rs @@ -122,30 +122,6 @@ pub fn methods(_: TokenStream, input: TokenStream) -> TokenStream { TokenStream::from_str(s.as_str()).unwrap() } -#[proc_macro_attribute] -// do nothing, if impl block is not wrapped into #[methods] macro -pub fn getter(_: TokenStream, input: TokenStream) -> TokenStream { - input -} - -#[proc_macro_attribute] -// do nothing, if impl block is not wrapped into #[methods] macro -pub fn setter(_: TokenStream, input: TokenStream) -> TokenStream { - input -} - -#[proc_macro_attribute] -// do nothing, if impl block is not wrapped into #[methods] macro -pub fn args(_: TokenStream, input: TokenStream) -> TokenStream { - input -} - -#[proc_macro_attribute] -// do nothing, if impl block is not wrapped into #[methods] macro -pub fn defaults(_: TokenStream, input: TokenStream) -> TokenStream { - input -} - #[proc_macro_attribute] pub fn ptr(attr: TokenStream, input: TokenStream) -> TokenStream { // Construct a string representation of the type definition diff --git a/pyo3cls/src/method.rs b/pyo3cls/src/method.rs index b47bf455..b2b3e660 100644 --- a/pyo3cls/src/method.rs +++ b/pyo3cls/src/method.rs @@ -181,7 +181,9 @@ impl<'a> FnSpec<'a> { } } -fn check_arg_ty_and_optional<'a>(name: &'a syn::Ident, ty: &'a syn::Ty) -> Option<&'a syn::Ty> { +pub fn check_arg_ty_and_optional<'a>(name: &'a syn::Ident, ty: &'a syn::Ty) + -> Option<&'a syn::Ty> +{ match ty { &syn::Ty::Path(_, ref path) => { //if let &Some(ref qs) = qs { diff --git a/pyo3cls/src/module.rs b/pyo3cls/src/module.rs index 7dcdf496..23503b4f 100644 --- a/pyo3cls/src/module.rs +++ b/pyo3cls/src/module.rs @@ -3,12 +3,34 @@ use syn; use quote::Tokens; +use args; +use method; +use py_method; +use utils; + pub fn build_py3_module_init(ast: &mut syn::Item, attr: String) -> Tokens { let modname = &attr.to_string()[1..attr.to_string().len()-1].to_string(); match ast.node { - syn::ItemKind::Fn(_, _, _, _, _, _) => { + syn::ItemKind::Fn(_, _, _, _, _, ref mut block) => { + let mut stmts = Vec::new(); + for stmt in block.stmts.iter_mut() { + match stmt { + &mut syn::Stmt::Item(ref mut item) => { + if let Some(block) = wrap_fn(item) { + for stmt in block.stmts.iter() { + stmts.push(stmt.clone()); + } + continue + } + } + _ => (), + } + stmts.push(stmt.clone()); + } + block.stmts = stmts; + py3_init(&ast.ident, &modname) }, _ => panic!("#[modinit] can only be used with fn block"), @@ -112,3 +134,195 @@ pub fn py2_init(fnname: &syn::Ident, name: &String) -> Tokens { } } } + +fn wrap_fn(item: &mut syn::Item) -> Option> { + let name = item.ident.clone(); + let mut new_attrs = Vec::new(); + let mut fnname = None; + let mut modname = None; + let mut fn_attrs = Vec::new(); + + for attr in item.attrs.iter() { + match attr.value { + syn::MetaItem::List(ref name, ref meta) => { + match name.as_ref() { + "pyfn" => { + if meta.len() >= 2 { + match meta[0] { + syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ref ident)) => { + modname = Some(ident.clone()); + } + _ => modname = None + } + match meta[1] { + syn::NestedMetaItem::Literal(syn::Lit::Str(ref s, _)) => { + fnname = Some(syn::Ident::from(s.as_str())); + } + _ => fnname = None + } + } else { + println!("can not parse 'pyfn' params {:?}", attr); + modname = None + } + if meta.len() >= 3 { + match meta[2] { + syn::NestedMetaItem::Literal(syn::Lit::Str(ref s, _)) => { + fn_attrs = args::parse_arguments(s.as_ref()); + }, + _ => modname = None + } + } + continue; + } + _ => (), + } + } + _ => (), + }; + new_attrs.push(attr.clone()) + } + item.attrs.clear(); + item.attrs.extend(new_attrs); + + if let None = fnname { + return None + } + if let None = modname { + return None + } + + match item.node { + syn::ItemKind::Fn(ref decl, _, _, _, _, _) => { + let mut py = false; + let mut arguments = Vec::new(); + + for input in decl.inputs.iter() { + match input { + &syn::FnArg::SelfRef(_, _) | &syn::FnArg::SelfValue(_) => (), + &syn::FnArg::Captured(ref pat, ref ty) => { + let (mode, ident) = match pat { + &syn::Pat::Ident(ref mode, ref ident, _) => + (mode, ident), + _ => + panic!("unsupported argument: {:?}", pat), + }; + + if !py { + match ty { + &syn::Ty::Path(_, ref path) => + if let Some(segment) = path.segments.last() { + if segment.ident.as_ref() == "Python" { + py = true; + continue; + } + }, + _ => (), + } + } + + let opt = method::check_arg_ty_and_optional(&name, ty); + arguments.push(method::FnArg {name: ident, + mode: mode, + ty: ty, + optional: opt}); + } + &syn::FnArg::Ignored(_) => + panic!("ignored argument: {:?}", name), + } + } + + let ty = match decl.output { + syn::FunctionRetTy::Default => syn::Ty::Infer, + syn::FunctionRetTy::Ty(ref ty) => ty.clone() + }; + + let spec = method::FnSpec { + tp: method::FnType::Fn, + attrs: fn_attrs, + args: arguments, + output: ty, + }; + + let m = modname.unwrap(); + let fnname = fnname.unwrap(); + let wrapper = impl_wrap(&name, &spec); + let item2 = item.clone(); + let doc = utils::get_doc(&item.attrs); + + let tokens = quote! { + fn test() { + #item2 + + { + use std; + use pyo3 as _pyo3; + + #wrapper + + let def = pyo3::class::PyMethodDef { + ml_name: stringify!(#fnname), + ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(wrap), + ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS, + ml_doc: #doc, + }.as_method_def(); + + unsafe { + let func = PyObject::from_owned_ptr_or_panic( + py, pyo3::ffi::PyCFunction_New( + &def as *const _ as *mut _, std::ptr::null_mut())); + std::mem::forget(def); + + #m.add(py, stringify!(#fnname), func)? + } + } + } + }.to_string(); + + println!("TEST {}", tokens); + + let item = syn::parse_item(tokens.as_str()).unwrap(); + match item.node { + syn::ItemKind::Fn(_, _, _, _, _, ref block) => { + return Some(block.clone()) + }, + _ => () + } + }, + _ => (), + } + + None +} + + +/// Generate static method wrapper (PyCFunction, PyCFunctionWithKeywords) +pub fn impl_wrap(name: &syn::Ident, spec: &method::FnSpec) -> Tokens { + let names: Vec<&syn::Ident> = spec.args.iter().map(|item| item.name).collect(); + let cb = quote! {{ + #name(py, #(#names),*) + }}; + + let body = py_method::impl_arg_params(spec, cb); + let output = &spec.output; + + quote! { + #[allow(unused_mut)] + unsafe extern "C" fn wrap(_slf: *mut _pyo3::ffi::PyObject, + args: *mut _pyo3::ffi::PyObject, + kwargs: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject + { + const LOCATION: &'static str = concat!(stringify!(#name), "()"); + + _pyo3::callback::cb_meth(LOCATION, |py| { + let args = _pyo3::PyTuple::from_borrowed_ptr(py, args); + let kwargs = _pyo3::argparse::get_kwargs(py, kwargs); + + let result: #output = { + #body + }; + _pyo3::callback::cb_convert( + _pyo3::callback::PyObjectCallbackConverter, py, result) + }) + } + } +} diff --git a/pyo3cls/src/py_method.rs b/pyo3cls/src/py_method.rs index 8f9a62ee..13c9667e 100644 --- a/pyo3cls/src/py_method.rs +++ b/pyo3cls/src/py_method.rs @@ -290,7 +290,7 @@ fn impl_call(_cls: &Box, fname: &syn::Ident, spec: &FnSpec) -> Tokens { }} } -fn impl_arg_params(spec: &FnSpec, body: Tokens) -> Tokens { +pub fn impl_arg_params(spec: &FnSpec, body: Tokens) -> Tokens { if spec.args.is_empty() { return body } diff --git a/src/argparse.rs b/src/argparse.rs index b304a63f..cba0ca1a 100644 --- a/src/argparse.rs +++ b/src/argparse.rs @@ -1,23 +1,7 @@ -// Copyright (c) 2015 Daniel Grunwald +// Copyright (c) 2017-present PyO3 Project and Contributors // -// Permission is hereby granted, free of charge, to any person obtaining a copy of this -// software and associated documentation files (the "Software"), to deal in the Software -// without restriction, including without limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -// to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython -//! This module contains logic for parsing a python argument list. -//! See also the macros `py_argparse!`, `py_fn!` and `py_method!`. use ffi; use python::Python; @@ -78,6 +62,10 @@ pub fn parse_args<'p>(py: Python<'p>, } }, None => { + if p.kw_only { + return Err(err::PyErr::new::( + py, format!("Required keywordargument ('{}') not found", p.name))); + } if i < nargs { *out = Some(args.get_item(py, i)); } else { @@ -107,253 +95,6 @@ pub fn parse_args<'p>(py: Python<'p>, Ok(()) } -/// This macro is used to parse a parameter list into a set of variables. -/// -/// Syntax: `py_argparse!(py, fname, args, kwargs, (parameter-list) { body })` -/// -/// * `py`: the `Python` token -/// * `fname`: expression of type `Option<&str>`: Name of the function used in error messages. -/// * `args`: expression of type `&PyTuple`: The position arguments -/// * `kwargs`: expression of type `Option<&PyDict>`: The named arguments -/// * `parameter-list`: a comma-separated list of parameter declarations. -/// Parameter declarations have one of these formats: -/// 1. `name` -/// 2. `name: ty` -/// 3. `name: ty = default_value` -/// 4. `*name` -/// 5. `*name : ty` -/// 6. `**name` -/// 7. `**name : ty` -/// -/// The types used must implement the `FromPyObject` trait. -/// If no type is specified, the parameter implicitly uses -/// `&PyObject` (format 1), `&PyTuple` (format 4) or `&PyDict` (format 6). -/// If a default value is specified, it must be a compile-time constant -// of type `ty`. -/// * `body`: expression of type `PyResult<_>`. -/// The extracted argument values are available in this scope. -/// -/// `py_argparse!()` expands to code that extracts values from `args` and `kwargs` and assigns -/// them to the parameters. If the extraction is successful, `py_argparse!()` evaluates -/// the body expression and returns of that evaluation. -/// If extraction fails, `py_argparse!()` returns a failed `PyResult` without evaluating `body`. -/// -/// The `py_argparse!()` macro special-cases reference types (when `ty` starts with a `&` token): -/// In this case, the macro uses the `RefFromPyObject` trait instead of the `FromPyObject` trait. -/// When using at least one reference parameter, the `body` block is placed within a closure, -/// so `return` statements might behave unexpectedly in this case. (this only affects direct use -/// of `py_argparse!`; `py_fn!` is unaffected as the body there is always in a separate function -/// from the generated argument-parsing code). -#[macro_export] -macro_rules! py_argparse { - ($py:expr, $fname:expr, $args:expr, $kwargs:expr, $plist:tt $body:block) => { - py_argparse_parse_plist! { py_argparse_impl { $py, $fname, $args, $kwargs, $body, } $plist } - }; -} - -#[macro_export] -#[doc(hidden)] -macro_rules! py_argparse_parse_plist { - // Parses a parameter-list into a format more suitable for consumption by Rust macros. - // py_argparse_parse_plist! { callback { initial_args } (plist) } - // = callback! { initial_args [{ pname:ptype = [ {**} {default-value} ] } ...] } - // The braces around the *s and the default-value are used even if they are empty. - - // Special-case entry-point for empty parameter list: - { $callback:ident { $($initial_arg:tt)* } ( ) } => { - $callback! { $($initial_arg)* [] } - }; - // Regular entry point for non-empty parameter list: - { $callback:ident $initial_args:tt ( $( $p:tt )+ ) } => { - // add trailing comma to plist so that the parsing step can assume every - // parameter ends with a comma. - py_argparse_parse_plist_impl! { $callback $initial_args [] ( $($p)*, ) } - }; -} - -#[macro_export] -#[doc(hidden)] -macro_rules! py_argparse_parse_plist_impl { - // TT muncher macro that does the main work for py_argparse_parse_plist!. - - // Base case: all parameters handled - { $callback:ident { $($initial_arg:tt)* } $output:tt ( ) } => { - $callback! { $($initial_arg)* $output } - }; - // Kwargs parameter with reference extraction - { $callback:ident $initial_args:tt [ $($output:tt)* ] - ( ** $name:ident : &$t:ty , $($tail:tt)* ) - } => { - py_argparse_parse_plist_impl! { - $callback $initial_args - [ $($output)* { $name:&$t = [ {**} {} {$t} ] } ] - ($($tail)*) - } - }; - // Kwargs parameter - { $callback:ident $initial_args:tt [ $($output:tt)* ] - ( ** $name:ident : $t:ty , $($tail:tt)* ) - } => { - py_argparse_parse_plist_impl! { - $callback $initial_args - [ $($output)* { $name:$t = [ {**} {} {} ] } ] - ($($tail)*) - } - }; - // Kwargs parameter with implicit type - { $callback:ident $initial_args:tt [ $($output:tt)* ] - ( ** $name:ident , $($tail:tt)* ) - } => { - py_argparse_parse_plist_impl! { - $callback $initial_args - [ $($output)* { $name:Option<&$crate::PyDict> = [ {**} {} {} ] } ] - ($($tail)*) - } - }; - // Varargs parameter with reference extraction - { $callback:ident $initial_args:tt [ $($output:tt)* ] - ( * $name:ident : &$t:ty , $($tail:tt)* ) - } => { - py_argparse_parse_plist_impl! { - $callback $initial_args - [ $($output)* { $name:&$t = [ {*} {} {$t} ] } ] - ($($tail)*) - } - }; - // Varargs parameter - { $callback:ident $initial_args:tt [ $($output:tt)* ] - ( * $name:ident : $t:ty , $($tail:tt)* ) - } => { - py_argparse_parse_plist_impl! { - $callback $initial_args - [ $($output)* { $name:$t = [ {*} {} {} ] } ] - ($($tail)*) - } - }; - // Varargs parameter with implicit type - { $callback:ident $initial_args:tt [ $($output:tt)* ] - ( * $name:ident , $($tail:tt)* ) - } => { - py_argparse_parse_plist_impl! { - $callback $initial_args - [ $($output)* { $name:&$crate::PyTuple = [ {*} {} {} ] } ] - ($($tail)*) - } - }; - // Simple parameter with reference extraction - { $callback:ident $initial_args:tt [ $($output:tt)* ] - ( $name:ident : &$t:ty , $($tail:tt)* ) - } => { - py_argparse_parse_plist_impl! { - $callback $initial_args - [ $($output)* { $name:&$t = [ {} {} {$t} ] } ] - ($($tail)*) - } - }; - // Simple parameter - { $callback:ident $initial_args:tt [ $($output:tt)* ] - ( $name:ident : $t:ty , $($tail:tt)* ) - } => { - py_argparse_parse_plist_impl! { - $callback $initial_args - [ $($output)* { $name:$t = [ {} {} {} ] } ] - ($($tail)*) - } - }; - // Simple parameter with implicit type - { $callback:ident $initial_args:tt [ $($output:tt)* ] - ( $name:ident , $($tail:tt)* ) - } => { - py_argparse_parse_plist_impl! { - $callback $initial_args - [ $($output)* { $name:&$crate::PyObject = [ {} {} {} ] } ] - ($($tail)*) - } - }; - // Optional parameter with reference extraction - { $callback:ident $initial_args:tt [ $($output:tt)* ] - ( $name:ident : &$t:ty = $default:expr, $($tail:tt)* ) - } => { - py_argparse_parse_plist_impl! { - $callback $initial_args - [ $($output)* { $name:&$t = [ {} {$default} {$t} ] } ] - ($($tail)*) - } - }; - // Optional parameter - { $callback:ident $initial_args:tt [ $($output:tt)* ] - ( $name:ident : $t:ty = $default:expr , $($tail:tt)* ) - } => { - py_argparse_parse_plist_impl! { - $callback $initial_args - [ $($output)* { $name:$t = [ {} {$default} {} ] } ] - ($($tail)*) - } - }; -} - -// The main py_argparse!() macro, except that it expects the parameter-list -// in the output format of py_argparse_parse_plist!(). -#[macro_export] -#[doc(hidden)] -macro_rules! py_argparse_impl { - // special case: function signature is (*args, **kwargs), - // so we can directly pass along our inputs without calling parse_args(). - ($py:expr, $fname:expr, $args:expr, $kwargs:expr, $body:block, - [ - { $pargs:ident : $pargs_type:ty = [ {*} {} {} ] } - { $pkwargs:ident : $pkwargs_type:ty = [ {**} {} {} ] } - ] - ) => {{ - let _py: $crate::Python = $py; - // TODO: use extract() to be more flexible in which type is expected - let $pargs: $pargs_type = $args; - let $pkwargs: $pkwargs_type = $kwargs; - $body - }}; - - // normal argparse logic - ($py:expr, $fname:expr, $args:expr, $kwargs:expr, $body:block, - [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ] - ) => {{ - const PARAMS: &'static [$crate::argparse::ParamDescription<'static>] = &[ - $( - py_argparse_param_description! { $pname : $ptype = $detail } - ),* - ]; - let py: $crate::Python = $py; - let mut output = [$( py_replace_expr!($pname None) ),*]; - match $crate::argparse::parse_args(py, $fname, PARAMS, $args, $kwargs, false, false, &mut output) { - Ok(()) => { - // Experimental slice pattern syntax would be really nice here (#23121) - //let [$(ref $pname),*] = output; - // We'll use an iterator instead. - let mut _iter = output.iter(); - // We'll have to generate a bunch of nested `match` statements - // (at least until we can use ? + catch, assuming that will be hygienic wrt. macros), - // so use a recursive helper macro for that: - py_argparse_extract!(py, _iter, $body, - [ $( { $pname : $ptype = $detail } )* ]) - }, - Err(e) => Err(e) - } - }}; -} - -// Like py_argparse_impl!(), but accepts `*mut ffi::PyObject` for $args and $kwargs. -#[macro_export] -#[doc(hidden)] -macro_rules! py_argparse_raw { - ($py:ident, $fname:expr, $args:expr, $kwargs:expr, $plist:tt $body:block) => {{ - let args: $crate::PyTuple = $crate::PyTuple::from_borrowed_ptr($py, $args); - let kwargs: Option<$crate::PyDict> = $crate::argparse::get_kwargs($py, $kwargs); - let ret = py_argparse_impl!($py, $fname, &args, kwargs.as_ref(), $body, $plist); - $py.release(kwargs); - $py.release(args); - ret - }}; -} - #[inline] #[doc(hidden)] pub unsafe fn get_kwargs(py: Python, ptr: *mut ffi::PyObject) -> Option { @@ -364,79 +105,6 @@ pub unsafe fn get_kwargs(py: Python, ptr: *mut ffi::PyObject) -> Option } } - -#[macro_export] -#[doc(hidden)] -macro_rules! py_argparse_param_description { - // normal parameter - { $pname:ident : $ptype:ty = [ {} {} $rtype:tt ] } => ( - $crate::argparse::ParamDescription { - name: stringify!($pname), - is_optional: false, - kw_only: false, - } - ); - // optional parameters - { $pname:ident : $ptype:ty = [ {} {$default:expr} {$($rtype:tt)*} ] } => ( - $crate::argparse::ParamDescription { - name: stringify!($pname), - is_optional: true, - kw_only: false, - } - ); -} - -#[macro_export] -#[doc(hidden)] -macro_rules! py_argparse_extract { - // base case - ( $py:expr, $iter:expr, $body:block, [] ) => { $body }; - // normal parameter - ( $py:expr, $iter:expr, $body:block, - [ { $pname:ident : $ptype:ty = [ {} {} {} ] } $($tail:tt)* ] - ) => { - // First unwrap() asserts the iterated sequence is long enough (which should be guaranteed); - // second unwrap() asserts the parameter was not missing (which fn parse_args already checked for). - match <$ptype as $crate::FromPyObject>::extract( - $py, $iter.next().unwrap().as_ref().unwrap()) { - Ok($pname) => py_argparse_extract!($py, $iter, $body, [$($tail)*]), - Err(e) => Err(e) - } - }; - // normal parameter with reference extraction - ( $py:expr, $iter:expr, $body:block, - [ { $pname:ident : $ptype:ty = [ {} {} {$rtype:ty} ] } $($tail:tt)* ] - ) => { - // First unwrap() asserts the iterated sequence is long enough (which should be guaranteed); - // second unwrap() asserts the parameter was not missing (which fn parse_args already checked for). - match <$rtype as $crate::RefFromPyObject>::with_extracted( - $py, $iter.next().unwrap().as_ref().unwrap(), - |$pname: $ptype| py_argparse_extract!($py, $iter, $body, [$($tail)*]) - ) { - Ok(v) => v, - Err(e) => Err(e) - } - }; - // optional parameter - ( $py:expr, $iter:expr, $body:block, - [ { $pname:ident : $ptype:ty = [ {} {$default:expr} {} ] } $($tail:tt)* ] - ) => { - match $iter.next().unwrap().as_ref().map(|obj| obj.extract::<_>($py)).unwrap_or(Ok($default)) { - Ok($pname) => py_argparse_extract!($py, $iter, $body, [$($tail)*]), - Err(e) => Err(e) - } - }; - // optional parameter with reference extraction - ( $py:expr, $iter:expr, $body:block, - [ { $pname:ident : $ptype:ty = [ {} {$default:expr} {$rtype:ty} ] } $($tail:tt)* ] - ) => { - //unwrap() asserts the iterated sequence is long enough (which should be guaranteed); - $crate::argparse::with_extracted_or_default($py, $iter.next().unwrap().as_ref(), - |$pname: $ptype| py_argparse_extract!($py, $iter, $body, [$($tail)*]), - $default) - }; -} - #[doc(hidden)] // used in py_argparse_extract!() macro pub fn with_extracted_or_default<'p, P: ?Sized, R, F>( py: Python, obj: Option<&'p PyObject>, f: F, default: &'static P) -> PyResult @@ -451,65 +119,3 @@ pub fn with_extracted_or_default<'p, P: ?Sized, R, F>( None => f(default) } } - - -#[cfg(test)] -mod test { - use python::Python; - use objects::PyTuple; - use conversion::IntoPyTuple; - - #[test] - pub fn test_parse() { - let gil_guard = Python::acquire_gil(); - let py = gil_guard.python(); - let mut called = false; - let tuple = ("abc", 42).into_tuple(py); - py_argparse!(py, None, &tuple, None, (x: &str, y: i32) { - assert_eq!(x, "abc"); - assert_eq!(y, 42); - called = true; - Ok(()) - }).unwrap(); - assert!(called); - } - - #[test] - pub fn test_default_param_type() { - let gil_guard = Python::acquire_gil(); - let py = gil_guard.python(); - let mut called = false; - let tuple = ("abc",).into_tuple(py); - py_argparse!(py, None, &tuple, None, (x) { - assert_eq!(*x, tuple.get_item(py, 0)); - called = true; - Ok(()) - }).unwrap(); - assert!(called); - } - - #[test] - pub fn test_default_value() { - let gil_guard = Python::acquire_gil(); - let py = gil_guard.python(); - let mut called = false; - let tuple = (0, "foo").into_tuple(py); - py_argparse!(py, None, &tuple, None, (x: usize = 42, y: &str = "abc") { - assert_eq!(x, 0); - assert_eq!(y, "foo"); - called = true; - Ok(()) - }).unwrap(); - assert!(called); - - let mut called = false; - let tuple = PyTuple::empty(py); - py_argparse!(py, None, &tuple, None, (x: usize = 42, y: &str = "abc") { - assert_eq!(x, 42); - assert_eq!(y, "abc"); - called = true; - Ok(()) - }).unwrap(); - assert!(called); - } -} diff --git a/src/function.rs b/src/function.rs deleted file mode 100644 index 6434f408..00000000 --- a/src/function.rs +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) 2015 Daniel Grunwald -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this -// software and associated documentation files (the "Software"), to deal in the Software -// without restriction, including without limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -// to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use std::ptr; - -use ffi; -use python::Python; -use objects::PyObject; - -#[macro_export] -#[doc(hidden)] -macro_rules! py_method_def { - ($name: expr, $flags: expr, $wrap: expr) => {{ - static mut METHOD_DEF: $crate::ffi::PyMethodDef = $crate::ffi::PyMethodDef { - //ml_name: bytes!(stringify!($name), "\0"), - ml_name: 0 as *const $crate::c_char, - ml_meth: None, - ml_flags: $crate::ffi::METH_VARARGS | $crate::ffi::METH_KEYWORDS | $flags, - ml_doc: 0 as *const $crate::c_char - }; - METHOD_DEF.ml_name = concat!($name, "\0").as_ptr() as *const _; - METHOD_DEF.ml_meth = Some( - ::std::mem::transmute::<$crate::ffi::PyCFunctionWithKeywords, - $crate::ffi::PyCFunction>($wrap) - ); - &mut METHOD_DEF - }} -} - -/// Creates a Python callable object that invokes a Rust function. -/// -/// There are two forms of this macro: -/// -/// 1. `py_fn!(py, f(parameter_list))` -/// 1. `py_fn!(py, f(parameter_list) -> PyResult { body })` -/// -/// both forms return a value of type `PyObject`. -/// This python object is a callable object that invokes -/// the Rust function when called. -/// -/// When called, the arguments are converted into -/// the Rust types specified in the parameter list. -/// See `py_argparse!()` for details on argument parsing. -/// -/// Form 1: -/// -/// * `py` must be an expression of type `Python` -/// * `f` must be the name of a function that is compatible with the specified -/// parameter list, except that a single parameter of type `Python` is prepended. -/// The function must return `PyResult` for some `T` that implements `ToPyObject`. -/// -/// Form 2: -/// -/// * `py` must be an identifier refers to a `Python` value. -/// The function body will also have access to a `Python` variable of this name. -/// * `f` must be an identifier. -/// * The function return type must be `PyResult` for some `T` that -/// implements `ToPyObject`. -/// -/// # Example -/// ``` -/// #[macro_use] extern crate pyo3; -/// use pyo3::{exc, Python, PyResult, PyErr, PyDict}; -/// -/// fn multiply(py: Python, lhs: i32, rhs: i32) -> PyResult { -/// match lhs.checked_mul(rhs) { -/// Some(val) => Ok(val), -/// None => Err(PyErr::new_lazy_init(py.get_type::(), None)) -/// } -/// } -/// -/// fn main() { -/// let gil = Python::acquire_gil(); -/// let py = gil.python(); -/// let dict = PyDict::new(py); -/// dict.set_item(py, "multiply", py_fn!(py, multiply(lhs: i32, rhs: i32))).unwrap(); -/// py.run("print(multiply(6, 7))", None, Some(&dict)).unwrap(); -/// } -/// ``` -#[macro_export] -macro_rules! py_fn { - ($py:expr, $f:ident $plist:tt ) => { - py_argparse_parse_plist! { py_fn_impl { $py, $f } $plist } - }; - ($py:ident, $f:ident $plist:tt -> $ret:ty { $($body:tt)* } ) => { - py_argparse_parse_plist! { py_fn_impl { $py, $f, $ret, { $($body)* } } $plist } - }; -} - -#[macro_export] -#[doc(hidden)] -macro_rules! py_fn_impl { - // Form 1: reference existing function - { $py:expr, $f:ident [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ] } => {{ - unsafe extern "C" fn wrap( - _slf: *mut $crate::ffi::PyObject, - args: *mut $crate::ffi::PyObject, - kwargs: *mut $crate::ffi::PyObject) - -> *mut $crate::ffi::PyObject - { - $crate::callback::handle( - stringify!($f), $crate::callback::PyObjectCallbackConverter, |py| - { - py_argparse_raw!(py, Some(stringify!($f)), args, kwargs, - [ $( { $pname : $ptype = $detail } )* ] - { $f(py $(, $pname )* ) }) - }) - } - unsafe { - $crate::function::py_fn_impl($py, - py_method_def!(stringify!($f), 0, wrap)) - } - }}; - // Form 2: inline function definition - { $py:ident, $f:ident, $ret:ty, $body:block [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ] } => {{ - fn $f($py: $crate::Python $( , $pname : $ptype )* ) -> $ret $body - py_fn_impl!($py, $f [ $( { $pname : $ptype = $detail } )* ]) - }} -} - - -#[allow(dead_code)] -pub unsafe fn py_fn_impl<'p>(py: Python<'p>, - method_def: *mut ffi::PyMethodDef) -> PyObject { - PyObject::from_owned_ptr_or_panic(py, ffi::PyCFunction_New(method_def, ptr::null_mut())) -} diff --git a/src/lib.rs b/src/lib.rs index 97f38fdf..23ab4aef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,23 +63,33 @@ //! This function will be called when the module is imported, and is responsible //! for adding the module's members. //! +//! To creates a Python callable object that invokes a Rust function, specify rust +//! function and decroate it with `#[pyfn()]` attribute. `pyfn()` accepts three parameters. +//! +//! 1. `m`: The module name. +//! 2. function name, name of function visible to Python code. +//! 3. arguments description string, i.e. "param1, param2=None, *, param3='default'" +//! +//! //! # Example //! ``` //! #![feature(proc_macro)] //! #[macro_use] extern crate pyo3; -//! use pyo3::{py, Python, PyResult, PyObject, PyModule}; +//! use pyo3::{py, Python, PyResult, PyObject, PyModule, PyString}; //! //! #[py::modinit(hello)] //! fn init_module(py: Python, m: &PyModule) -> PyResult<()> { //! m.add(py, "__doc__", "Module documentation string")?; -//! m.add(py, "run", py_fn!(py, run()))?; +//! +//! #[pyfn(m, "run_rust_func")] +//! fn run(py: Python, name: PyString) -> PyResult { +//! println!("Rust says: Hello {} of Python!", name); +//! Ok(py.None()) +//! } +//! //! Ok(()) //! } //! -//! fn run(py: Python) -> PyResult { -//! println!("Rust says: Hello Python!"); -//! Ok(py.None()) -//! } //! # fn main() {} //! ``` //! @@ -105,7 +115,7 @@ //! //! ```python //! >>> import hello -//! >>> hello.run() +//! >>> hello.run_rust_func("test") //! Rust says: Hello Python! //! ``` @@ -184,7 +194,6 @@ mod pythonrun; pub mod callback; pub mod typeob; pub mod argparse; -pub mod function; pub mod buffer; pub mod freelist; diff --git a/src/objects/stringutils.rs b/src/objects/stringutils.rs index 25b58433..5b9db30d 100644 --- a/src/objects/stringutils.rs +++ b/src/objects/stringutils.rs @@ -44,8 +44,8 @@ impl IntoPyObject for String { } } -// /// Allows extracting strings from Python objects. -// /// Accepts Python `str` and `unicode` objects. +/// Allows extracting strings from Python objects. +/// Accepts Python `str` and `unicode` objects. pyobject_extract!(py, obj to Cow<'source, str> => { try!(obj.cast_as::(py)).to_string(py) }); diff --git a/tests/test_function.rs b/tests/test_function.rs deleted file mode 100644 index 38db488c..00000000 --- a/tests/test_function.rs +++ /dev/null @@ -1,92 +0,0 @@ -#[macro_use] extern crate pyo3; - -use pyo3::{PyResult, Python, NoArgs, ObjectProtocol, PyDict}; -use std::sync::atomic; -use std::sync::atomic::Ordering::Relaxed; - -#[test] -fn no_args() { - static CALL_COUNT: atomic::AtomicUsize = atomic::ATOMIC_USIZE_INIT; - - fn f(_py: Python) -> PyResult { - Ok(CALL_COUNT.fetch_add(1, Relaxed)) - } - - let gil = Python::acquire_gil(); - let py = gil.python(); - let obj = py_fn!(py, f()); - - assert_eq!(CALL_COUNT.load(Relaxed), 0); - assert_eq!(obj.call(py, NoArgs, None).unwrap().extract::(py).unwrap(), 0); - assert_eq!(obj.call(py, NoArgs, None).unwrap().extract::(py).unwrap(), 1); - assert_eq!(CALL_COUNT.load(Relaxed), 2); - assert!(obj.call(py, (1,), None).is_err()); - assert_eq!(CALL_COUNT.load(Relaxed), 2); - assert_eq!(obj.call(py, NoArgs, Some(&PyDict::new(py))) - .unwrap().extract::(py).unwrap(), 2); - assert_eq!(CALL_COUNT.load(Relaxed), 3); - let dict = PyDict::new(py); - dict.set_item(py, "param", 42).unwrap(); - assert!(obj.call(py, NoArgs, Some(&dict)).is_err()); - assert_eq!(CALL_COUNT.load(Relaxed), 3); -} - -#[test] -fn one_arg() { - fn f(_py: Python, i: usize) -> PyResult { - Ok(i * 2) - } - - let gil = Python::acquire_gil(); - let py = gil.python(); - let obj = py_fn!(py, f(i: usize)); - - assert!(obj.call(py, NoArgs, None).is_err()); - assert_eq!(obj.call(py, (1,), None).unwrap().extract::(py).unwrap(), 2); - assert!(obj.call(py, (1, 2), None).is_err()); - - let dict = PyDict::new(py); - dict.set_item(py, "i", 42).unwrap(); - assert_eq!(obj.call(py, NoArgs, Some(&dict)).unwrap().extract::(py).unwrap(), 84); - assert!(obj.call(py, (1,), Some(&dict)).is_err()); - dict.set_item(py, "j", 10).unwrap(); - assert!(obj.call(py, NoArgs, Some(&dict)).is_err()); -} - -#[test] -fn inline_two_args() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let obj = py_fn!(py, f(a: i32, b: i32) -> PyResult { - drop(py); // avoid unused variable warning - Ok(a * b) - }); - - assert!(obj.call(py, NoArgs, None).is_err()); - assert_eq!(obj.call(py, (6, 7), None).unwrap().extract::(py).unwrap(), 42); -} - -/* TODO: reimplement flexible sig support -#[test] -fn flexible_sig() { - fn f(py: Python, args: &PyTuple, kwargs: &PyDict) -> PyResult { - Ok(args.len(py) + 100 * kwargs.map_or(0, |kwargs| kwargs.len(py))) - } - - let gil = Python::acquire_gil(); - let py = gil.python(); - let obj = py_fn!(f(*args, **kwargs)).to_py_object(py); - - assert_eq!(obj.call(py, NoArgs, None).unwrap().extract::(py).unwrap(), 0); - assert_eq!(obj.call(py, (1,), None).unwrap().extract::(py).unwrap(), 1); - assert_eq!(obj.call(py, (1,2), None).unwrap().extract::(py).unwrap(), 2); - - let dict = PyDict::new(py); - dict.set_item(py, "i", 42).unwrap(); - assert_eq!(obj.call(py, NoArgs, Some(&dict)).unwrap().extract::(py).unwrap(), 100); - assert_eq!(obj.call(py, (1,2), Some(&dict)).unwrap().extract::(py).unwrap(), 102); - dict.set_item(py, "j", 10).unwrap(); - assert_eq!(obj.call(py, (1,2,3), Some(&dict)).unwrap().extract::(py).unwrap(), 203); -} -*/ -