From addbbc821026caddd688ef0ad52a1af37804e319 Mon Sep 17 00:00:00 2001 From: Nikolay Mladenov Date: Tue, 20 Apr 2004 21:46:34 +0000 Subject: [PATCH] first attempt to implement docstrings with nice signatures [SVN r22675] --- .../boost/python/converter/python_type.hpp | 39 +++ src/converter/builtin_converters.cpp | 10 + src/converter/registry.cpp | 72 ++++ src/dict.cpp | 9 + src/list.cpp | 12 +- src/object/class.cpp | 7 +- src/object/function.cpp | 320 +++++++++++++++--- src/str.cpp | 10 + src/tuple.cpp | 11 + 9 files changed, 439 insertions(+), 51 deletions(-) create mode 100755 include/boost/python/converter/python_type.hpp diff --git a/include/boost/python/converter/python_type.hpp b/include/boost/python/converter/python_type.hpp new file mode 100755 index 00000000..01a899e1 --- /dev/null +++ b/include/boost/python/converter/python_type.hpp @@ -0,0 +1,39 @@ +#ifndef BOOST_PYTHON_OBJECT_PYTHON_TYPE_H +#define BOOST_PYTHON_OBJECT_PYTHON_TYPE_H + +#include + +namespace boost {namespace python {namespace converter{ +namespace detail{ + inline void * identity (PyObject *p){return p;} + namespace strip_type_info{ + BOOST_PYTHON_DECL void insert(boost::python::type_info, boost::python::type_info); + BOOST_PYTHON_DECL type_info query(boost::python::type_info); + } + +}// namespace converter::detail + + +template struct python_class : PyObject +{ + typedef python_class this_type; + typedef T type; + static void register_() + { + static bool first_time = true; + + if ( !first_time ) return; + first_time = false; + converter::registry::insert(&detail::identity, boost::python::type_id()); + converter::detail::strip_type_info::insert(boost::python::type_id(), boost::python::type_id()); + converter::detail::strip_type_info::insert(boost::python::type_id(), boost::python::type_id()); + converter::detail::strip_type_info::insert(boost::python::type_id(), boost::python::type_id()); + } +private: + python_class(); +}; + + +}}} //namespace boost :: python :: converter + +#endif //BOOST_PYTHON_OBJECT_PYTHON_TYPE_H diff --git a/src/converter/builtin_converters.cpp b/src/converter/builtin_converters.cpp index f1a614c3..47f8730a 100644 --- a/src/converter/builtin_converters.cpp +++ b/src/converter/builtin_converters.cpp @@ -58,6 +58,7 @@ namespace , &slot_rvalue_from_python::construct , type_id() ); + const_cast(registry::query(type_id()))->m_class_object = SlotPolicy::get_class_object(); } private: @@ -94,6 +95,7 @@ namespace return (PyInt_Check(obj) || PyLong_Check(obj)) ? &number_methods->nb_int : 0; } + static PyTypeObject * get_class_object() { return &PyInt_Type;} }; template @@ -129,6 +131,7 @@ namespace return (PyInt_Check(obj) || PyLong_Check(obj)) ? &py_object_identity : 0; } + static PyTypeObject * get_class_object() { return &PyInt_Type;} }; template @@ -167,6 +170,7 @@ namespace else return 0; } + static PyTypeObject * get_class_object() { return &PyInt_Type;} }; struct long_long_rvalue_from_python : long_long_rvalue_from_python_base @@ -222,6 +226,7 @@ namespace { return PyObject_IsTrue(intermediate); } + static PyTypeObject * get_class_object() { return &PyInt_Type;} }; // A SlotPolicy for extracting floating types from Python objects. @@ -253,6 +258,7 @@ namespace return PyFloat_AS_DOUBLE(intermediate); } } + static PyTypeObject * get_class_object() { return &PyFloat_Type;} }; // A SlotPolicy for extracting C++ strings from Python objects. @@ -270,6 +276,7 @@ namespace { return std::string(PyString_AsString(intermediate),PyString_Size(intermediate)); } + static PyTypeObject * get_class_object() { return &PyString_Type;} }; // encode_string_unaryfunc/py_encode_string -- manufacture a unaryfunc @@ -308,6 +315,8 @@ namespace throw_error_already_set(); return result; } + + static PyTypeObject * get_class_object() { return &PyUnicode_Type;} }; #endif @@ -404,6 +413,7 @@ void initialize_builtin_converters() // Add an lvalue converter for char which gets us char const* registry::insert(convert_to_cstring,type_id()); + const_cast(registry::lookup(type_id())).m_class_object = &PyString_Type; # ifndef BOOST_NO_STD_WSTRING // Register by-value converters to std::string, std::wstring diff --git a/src/converter/registry.cpp b/src/converter/registry.cpp index f8d217c2..daf89c56 100644 --- a/src/converter/registry.cpp +++ b/src/converter/registry.cpp @@ -6,8 +6,10 @@ #include #include #include +#include #include +#include #include #include @@ -243,4 +245,74 @@ namespace registry } } // namespace registry + + +namespace detail +{ + namespace strip_type_info{ + struct type_info_map : std::map + { + type_info_map(){ + (*this)[boost::python::type_id()] = boost::python::type_id(); + (*this)[boost::python::type_id()] = boost::python::type_id(); + + (*this)[boost::python::type_id()] = boost::python::type_id(); + (*this)[boost::python::type_id()] = boost::python::type_id(); + + (*this)[boost::python::type_id()] = boost::python::type_id(); + (*this)[boost::python::type_id()] = boost::python::type_id(); + (*this)[boost::python::type_id()] = boost::python::type_id(); + (*this)[boost::python::type_id()] = boost::python::type_id(); + + (*this)[boost::python::type_id()] = boost::python::type_id(); + (*this)[boost::python::type_id()] = boost::python::type_id(); + (*this)[boost::python::type_id()] = boost::python::type_id(); + (*this)[boost::python::type_id()] = boost::python::type_id(); + + (*this)[boost::python::type_id()] = boost::python::type_id(); + (*this)[boost::python::type_id()] = boost::python::type_id(); + (*this)[boost::python::type_id()] = boost::python::type_id(); + (*this)[boost::python::type_id()] = boost::python::type_id(); + + (*this)[boost::python::type_id()] = boost::python::type_id(); + (*this)[boost::python::type_id()] = boost::python::type_id(); + + (*this)[boost::python::type_id()] = boost::python::type_id(); + (*this)[boost::python::type_id()] = boost::python::type_id(); + + (*this)[boost::python::type_id()] = boost::python::type_id(); + (*this)[boost::python::type_id()] = boost::python::type_id(); + + (*this)[boost::python::type_id()] = boost::python::type_id(); + (*this)[boost::python::type_id()] = boost::python::type_id(); + +# ifndef BOOST_NO_STD_WSTRING + (*this)[boost::python::type_id()] = boost::python::type_id(); + (*this)[boost::python::type_id()] = boost::python::type_id(); +# endif + } + }; + + static std::map &type_info_remap_registry() + { + static type_info_map res; + return res; + } + BOOST_PYTHON_DECL void insert(boost::python::type_info t, boost::python::type_info pt) + { + if(t==pt) + return; + type_info_remap_registry()[pt] = t; + } + + BOOST_PYTHON_DECL type_info query(boost::python::type_info pt) + { + std::map :: iterator i + = type_info_remap_registry().find(pt); + return i == type_info_remap_registry().end()? pt : (*i).second; + } + } +} + + }}} // namespace boost::python::converter diff --git a/src/dict.cpp b/src/dict.cpp index f1b8df84..4453fc8e 100644 --- a/src/dict.cpp +++ b/src/dict.cpp @@ -167,5 +167,14 @@ list dict_base::values() const return assume_list(this->attr("values")()); } } +static struct register_dict_pytype_ptr +{ + register_dict_pytype_ptr() + { + const_cast( + converter::registry::lookup(boost::python::type_id()) + ).m_class_object = &PyDict_Type; + } +}register_dict_pytype_ptr_; }}} // namespace boost::python diff --git a/src/list.cpp b/src/list.cpp index 30ee78a0..98b51f8c 100644 --- a/src/list.cpp +++ b/src/list.cpp @@ -4,6 +4,8 @@ // "as is" without express or implied warranty, and with no claim as // to its suitability for any purpose. #include +#include +#include namespace boost { namespace python { namespace detail { @@ -136,5 +138,13 @@ long list_base::count(object_cref value) const throw_error_already_set(); return result; } - +static struct register_list_pytype_ptr +{ + register_list_pytype_ptr() + { + const_cast( + converter::registry::lookup(boost::python::type_id()) + ).m_class_object = &PyList_Type; + } +}register_list_pytype_ptr_; }}} // namespace boost::python diff --git a/src/object/class.cpp b/src/object/class.cpp index 0cc1d59e..29c2c832 100644 --- a/src/object/class.cpp +++ b/src/object/class.cpp @@ -613,14 +613,15 @@ namespace objects namespace { - PyObject* callable_check(PyObject* callable) + PyObject* callable_check(PyObject* callable, const char *name) { if (PyCallable_Check(expect_non_null(callable))) return callable; ::PyErr_Format( PyExc_TypeError - , "staticmethod expects callable object; got an object of type %s, which is not callable" + , "staticmethod expects callable object; got an object \"%s\" of type %s, which is not callable" + , name , callable->ob_type->tp_name ); @@ -638,7 +639,7 @@ namespace objects this->attr(method_name) = object( handle<>( - PyStaticMethod_New((callable_check)(method.ptr()) ) + PyStaticMethod_New((callable_check)(method.ptr(), method_name) ) )); } diff --git a/src/object/function.cpp b/src/object/function.cpp index 8c1452fd..9a1c59e7 100644 --- a/src/object/function.cpp +++ b/src/object/function.cpp @@ -16,6 +16,8 @@ #include #include +#include + #include #include #include @@ -24,6 +26,7 @@ #include #include +#include #if BOOST_PYTHON_DEBUG_ERROR_MESSAGES # include @@ -226,6 +229,269 @@ PyObject* function::call(PyObject* args, PyObject* keywords) const return 0; } +inline function const * function::overloads() const +{ + return this->m_overloads.get(); +} + + +class function_signature_generator{ +public: + static bool arity_cmp( function const *f1, function const *f2 ) + { + return f1->m_fn.max_arity() < f2->m_fn.max_arity(); + } + + static bool are_seq_overloads( function const *f1, function const *f2 , bool check_docs) + { + py_function const & impl1 = f1->m_fn; + py_function const & impl2 = f2->m_fn; + + //the number of parameters differs by 1 + if (impl2.max_arity()-impl1.max_arity() != 1) + return false; + + // if check docs then f1 shold not have docstring or have the same docstring as f2 + if (check_docs && f2->doc() != f1->doc() && f1->doc()) + return false; + + python::detail::signature_element const* s1 = impl1.signature(); + python::detail::signature_element const* s2 = impl2.signature(); + + size_t size = impl1.max_arity(); + + for (size_t i = 0; i != size; ++i) + { + //check if the argument types are the same + if (s1[i].basename() != s2[i].basename()) + return false; + + //return type + if (!i) continue; + + //check if the argument default values are the same + if (f1->m_arg_names && f2->m_arg_names && f2->m_arg_names[i-1]!=f1->m_arg_names[i-1] + || bool( f1->m_arg_names ) != bool( f2->m_arg_names + )) + return false; + } + return true; + } + + static std::vector flatten(function const *f) + { + object name = f->name(); + + std::vector res; + + while (f) { + + //this if takes out the not_implemented_function + if (f->name() == name) + res.push_back(f); + + f=f->overloads(); + } + + std::sort(res.begin(),res.end(), &arity_cmp); + + return res; + } + static std::vector split_seq_overloads( const std::vector &funcs, bool split_on_doc_change) + { + std::vector res; + + std::vector::const_iterator fi = funcs.begin(); + + function const * last = *fi; + + while (++fi != funcs.end()){ + + //check if fi starts a new chain of overloads + if (!are_seq_overloads( last, *fi, split_on_doc_change )) + res.push_back(last); + + last = *fi; + } + + if (last) + res.push_back(last); + + return res; + } + + static object raw_function_pretty_signature(function const *f, size_t n_overloads, bool cpp_types = false) + { + str res("object"); + + res = str("%s %s(%s)" % make_tuple( res, f->m_name, str("tuple args, dict kwds")) ); + + return res; + } + + static const char * py_type_str(const python::detail::signature_element &s) + { + if (s.basename()==std::string("void")){ + static const char * none = "None"; + return none; + } + const converter::registration *r=0; + + if ( (r = converter::registry::query(s.tid) ) && r->m_class_object) + return r->m_class_object->tp_name; + else if ( (r = converter::registry::query(converter::detail::strip_type_info::query(s.tid) ) ) && r->m_class_object ) + return r->m_class_object->tp_name; + else{ + static const char * object = "object"; + return object; + } + } + + static object parameter_string(const python::detail::signature_element *s, size_t n, object arg_names, bool cpp_types) + { + str param; + + if (cpp_types) + { + if (s[n].basename() == 0) + { + return str("..."); + } + + param = str(s[n].tid.name()); + + if (s[n].lvalue) + param += " {lvalue}"; + + } + else + { + if (n) //we are processing an argument and trying to come up with a name for it + { + object kv; + if ( arg_names && (kv = arg_names[n-1]) ) + param = str( " (%s)%s" % make_tuple(py_type_str(s[n]),kv[0]) ); + else + param = str(" (%s)%s%d" % make_tuple(py_type_str(s[n]),"arg", n) ); + } + else //we are processing the return type - how should we handle it??? + param = py_type_str(s[n]); + } + + //an argument - check for default value and append it + if(n && arg_names) + { + object kv(arg_names[n-1]); + if (kv && len(kv) == 2) + { + param = str("%s=%r" % make_tuple(param, kv[1])); + } + } + return param; + } + + static object pretty_signature(function const *f, size_t n_overloads, bool cpp_types = false) + { + py_function + const& impl = f->m_fn; + ; + + python::detail::signature_element + const* s = impl.signature() + ; + + size_t arity = impl.max_arity(); + + if(arity == size_t(-1))// is this the proper raw function test? + { + return raw_function_pretty_signature(f,n_overloads,cpp_types); + } + + list formal_params; + + size_t n_extra_default_args=0; + + for (unsigned n = 0; n <= arity; ++n) + { + str param; + + formal_params.append( + parameter_string(s, n, f->m_arg_names, cpp_types) + ); + + // find all the arguments with default values preceeding the arity-n_overloads + if (n && f->m_arg_names) + { + object kv(f->m_arg_names[n-1]); + + if (kv && len(kv) == 2) + { + //default argument preceeding the arity-n_overloads + if( n <= arity-n_overloads) + ++n_extra_default_args; + } + else + //argument without default, preceeding the arity-n_overloads + if( n <= arity-n_overloads) + n_extra_default_args = 0; + } + } + + if (!arity && cpp_types) + formal_params.append("void"); + + str ret_type (formal_params.pop(0)); + if ( !cpp_types ) + ret_type = str("-> "+ret_type); + + n_overloads+=n_extra_default_args; + + return str( + "%s %s(%s%s%s%s) %s" + % make_tuple + ( cpp_types?ret_type:str("") + , f->m_name + , str(",").join(formal_params.slice(0,arity-n_overloads)) + , n_overloads ? (n_overloads!=arity?str(" [,"):str("[ ")) : str() + , str(" [,").join(formal_params.slice(arity-n_overloads,arity)) + , std::string(n_overloads,']') + , cpp_types?str(""):ret_type + )); + + } + + static list function_signatures( function const * f, bool cpp_types, bool docs) + { + list signatures; + std::vector funcs = function_signature_generator::flatten( f); + std::vector split_funcs = function_signature_generator::split_seq_overloads( funcs, docs); + std::vector::const_iterator sfi=split_funcs.begin(), fi; + size_t n_overloads=0; + for (fi=funcs.begin(); fi!=funcs.end(); ++fi) + { + if(*sfi == *fi){ + signatures.append( + (*fi)->doc() && docs + ? function_signature_generator::pretty_signature(*fi, n_overloads,cpp_types)+ " : " +(*fi)->doc() + : function_signature_generator::pretty_signature(*fi, n_overloads,cpp_types) + ); + ++sfi; + n_overloads = 0; + }else + ++n_overloads ; + } + return signatures; + } +}; + + +object function::pretty_signature(bool cpp_types, size_t n_optional_trailing_args )const +{ + function const *f = this; + return function_signature_generator::pretty_signature(this, n_optional_trailing_args, cpp_types); + +} + void function::argument_error(PyObject* args, PyObject* keywords) const { static handle<> exception( @@ -243,47 +509,7 @@ void function::argument_error(PyObject* args, PyObject* keywords) const message += str(", ").join(actual_args); message += ")\ndid not match C++ signature:\n "; - list signatures; - for (function const* f = this; f; f = f->m_overloads.get()) - { - py_function const& impl = f->m_fn; - - python::detail::signature_element const* s - = impl.signature() + 1; // skip over return type - - list formal_params; - if (impl.max_arity() == 0) - formal_params.append("void"); - - for (unsigned n = 0; n < impl.max_arity(); ++n) - { - if (s[n].basename == 0) - { - formal_params.append("..."); - break; - } - - str param(s[n].basename); - if (s[n].lvalue) - param += " {lvalue}"; - - if (f->m_arg_names) // None or empty tuple will test false - { - object kv(f->m_arg_names[n]); - if (kv) - { - char const* const fmt = len(kv) > 1 ? " %s=%r" : " %s"; - param += fmt % kv; - } - } - - formal_params.append(param); - } - - signatures.append( - "%s(%s)" % make_tuple(f->m_name, str(", ").join(formal_params)) - ); - } + list signatures = function_signature_generator::function_signatures(this, true/*cpp types*/, false/*no docs*/); message += str("\n ").join(signatures); @@ -302,10 +528,6 @@ void function::add_overload(handle const& overload_) parent = parent->m_overloads.get(); parent->m_overloads = overload_; - - // If we have no documentation, get the docs from the overload - if (!m_doc) - m_doc = overload_->m_doc; } namespace @@ -545,8 +767,11 @@ extern "C" // static PyObject* function_get_doc(PyObject* op, void*) { - function* f = downcast(op); - return python::incref(f->doc().ptr()); + function const * f = downcast(op); + + list signatures = function_signature_generator::function_signatures(f, false/*pythonic args*/, true/*+docs*/); + + return python::incref( str("\n ").join(signatures).ptr()); } static int function_set_doc(PyObject* op, PyObject* doc, void*) @@ -640,6 +865,7 @@ handle<> function_handle_impl(py_function const& f) new function(f, 0, 0))); } + } // namespace objects namespace detail diff --git a/src/str.cpp b/src/str.cpp index c2d41a8e..1864c0db 100644 --- a/src/str.cpp +++ b/src/str.cpp @@ -328,5 +328,15 @@ BOOST_PYTHON_DEFINE_STR_METHOD(title, 0) BOOST_PYTHON_DEFINE_STR_METHOD(translate, 1) BOOST_PYTHON_DEFINE_STR_METHOD(translate, 2) BOOST_PYTHON_DEFINE_STR_METHOD(upper, 0) + +static struct register_str_pytype_ptr +{ + register_str_pytype_ptr() + { + const_cast( + converter::registry::lookup(boost::python::type_id()) + ).m_class_object = &PyString_Type; + } +}register_str_pytype_ptr_; }}} // namespace boost::python diff --git a/src/tuple.cpp b/src/tuple.cpp index 2762ab27..76576734 100644 --- a/src/tuple.cpp +++ b/src/tuple.cpp @@ -17,4 +17,15 @@ tuple_base::tuple_base(object_cref sequence) : object(call(sequence)) {} +static struct register_tuple_pytype_ptr +{ + register_tuple_pytype_ptr() + { + const_cast( + converter::registry::lookup(boost::python::type_id()) + ).m_class_object = &PyTuple_Type; + } +}register_tuple_pytype_ptr_; + + }}} // namespace boost::python