commit 261a4e63d20b2a6ed0b12c8073d88c125a11909b Author: Ullrich Köthe Date: Fri Oct 13 13:49:34 2000 +0000 initial import of py_cpp (wrapping C++ into python) [SVN r7931] diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..3e84d7c7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,96 @@ +* text=auto !eol svneol=native#text/plain +*.gitattributes text svneol=native#text/plain + +# Scriptish formats +*.bat text svneol=native#text/plain +*.bsh text svneol=native#text/x-beanshell +*.cgi text svneol=native#text/plain +*.cmd text svneol=native#text/plain +*.js text svneol=native#text/javascript +*.php text svneol=native#text/x-php +*.pl text svneol=native#text/x-perl +*.pm text svneol=native#text/x-perl +*.py text svneol=native#text/x-python +*.sh eol=lf svneol=LF#text/x-sh +configure eol=lf svneol=LF#text/x-sh + +# Image formats +*.bmp binary svneol=unset#image/bmp +*.gif binary svneol=unset#image/gif +*.ico binary svneol=unset#image/ico +*.jpeg binary svneol=unset#image/jpeg +*.jpg binary svneol=unset#image/jpeg +*.png binary svneol=unset#image/png +*.tif binary svneol=unset#image/tiff +*.tiff binary svneol=unset#image/tiff +*.svg text svneol=native#image/svg%2Bxml + +# Data formats +*.pdf binary svneol=unset#application/pdf +*.avi binary svneol=unset#video/avi +*.doc binary svneol=unset#application/msword +*.dsp text svneol=crlf#text/plain +*.dsw text svneol=crlf#text/plain +*.eps binary svneol=unset#application/postscript +*.gz binary svneol=unset#application/gzip +*.mov binary svneol=unset#video/quicktime +*.mp3 binary svneol=unset#audio/mpeg +*.ppt binary svneol=unset#application/vnd.ms-powerpoint +*.ps binary svneol=unset#application/postscript +*.psd binary svneol=unset#application/photoshop +*.rdf binary svneol=unset#text/rdf +*.rss text svneol=unset#text/xml +*.rtf binary svneol=unset#text/rtf +*.sln text svneol=native#text/plain +*.swf binary svneol=unset#application/x-shockwave-flash +*.tgz binary svneol=unset#application/gzip +*.vcproj text svneol=native#text/xml +*.vcxproj text svneol=native#text/xml +*.vsprops text svneol=native#text/xml +*.wav binary svneol=unset#audio/wav +*.xls binary svneol=unset#application/vnd.ms-excel +*.zip binary svneol=unset#application/zip + +# Text formats +.htaccess text svneol=native#text/plain +*.bbk text svneol=native#text/xml +*.cmake text svneol=native#text/plain +*.css text svneol=native#text/css +*.dtd text svneol=native#text/xml +*.htm text svneol=native#text/html +*.html text svneol=native#text/html +*.ini text svneol=native#text/plain +*.log text svneol=native#text/plain +*.mak text svneol=native#text/plain +*.qbk text svneol=native#text/plain +*.rst text svneol=native#text/plain +*.sql text svneol=native#text/x-sql +*.txt text svneol=native#text/plain +*.xhtml text svneol=native#text/xhtml%2Bxml +*.xml text svneol=native#text/xml +*.xsd text svneol=native#text/xml +*.xsl text svneol=native#text/xml +*.xslt text svneol=native#text/xml +*.xul text svneol=native#text/xul +*.yml text svneol=native#text/plain +boost-no-inspect text svneol=native#text/plain +CHANGES text svneol=native#text/plain +COPYING text svneol=native#text/plain +INSTALL text svneol=native#text/plain +Jamfile text svneol=native#text/plain +Jamroot text svneol=native#text/plain +Jamfile.v2 text svneol=native#text/plain +Jamrules text svneol=native#text/plain +Makefile* text svneol=native#text/plain +README text svneol=native#text/plain +TODO text svneol=native#text/plain + +# Code formats +*.c text svneol=native#text/plain +*.cpp text svneol=native#text/plain +*.h text svneol=native#text/plain +*.hpp text svneol=native#text/plain +*.ipp text svneol=native#text/plain +*.tpp text svneol=native#text/plain +*.jam text svneol=native#text/plain +*.java text svneol=native#text/plain diff --git a/base_object.h b/base_object.h new file mode 100644 index 00000000..e9183178 --- /dev/null +++ b/base_object.h @@ -0,0 +1,62 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef BASE_OBJECT_DWA051600_H_ +# define BASE_OBJECT_DWA051600_H_ + +# include "pyconfig.h" +# include "signatures.h" // really just for Type<> +# include "wrap_python.h" +# include + +namespace py { + +// BaseObject - adds a constructor and non-virtual destructor to a +// base Python type (e.g. PyObject, PyTypeObject). +template +struct BaseObject : PythonType +{ + typedef PythonType BasePythonType; + + // Initializes type and reference count. All other fields of BasePythonType are 0 + BaseObject(PyTypeObject* type_object); + + // Decrements reference count on the type + ~BaseObject(); +}; + +// Easy typedefs for common usage +typedef BaseObject PythonObject; +typedef BaseObject PythonType; + + +// +// Class template member function implementations +// +template +BaseObject::BaseObject(PyTypeObject* type_object) +{ + BasePythonType* bp = this; +#if !defined(_MSC_VER) || defined(__STLPORT) + std:: +#endif + memset(bp, 0, sizeof(BasePythonType)); + ob_refcnt = 1; + ob_type = type_object; + Py_INCREF(type_object); +} + +template +inline BaseObject::~BaseObject() +{ + Py_DECREF(ob_type); +} + +} + +#endif BASE_OBJECT_DWA051600_H_ diff --git a/callback.h b/callback.h new file mode 100644 index 00000000..a88b03f8 --- /dev/null +++ b/callback.h @@ -0,0 +1,83 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file was generated for 5-argument python callbacks by gen_callback.py + +#ifndef CALLBACK_DWA_052100_H_ +# define CALLBACK_DWA_052100_H_ + +# include "pyconfig.h" +# include "py.h" + +namespace py { + +// Just like the above, except we decrement p's reference count instead of returning it. +void expect_and_absorb_non_null(PyObject* p); + +// Calling Python from C++ +template +struct Callback +{ + static R call_method(PyObject* self, const char* name) + { return from_python(expect_non_null(PyEval_CallMethod(self, const_cast(name), const_cast("()"))), Type()); } + + template + static R call_method(PyObject* self, const char* name, const A1& a1) + { return from_python(expect_non_null(PyEval_CallMethod(self, const_cast(name), const_cast("(N)"), to_python(a1))), Type()); } + + template + static R call_method(PyObject* self, const char* name, const A1& a1, const A2& a2) + { return from_python(expect_non_null(PyEval_CallMethod(self, const_cast(name), const_cast("(NN)"), to_python(a1), to_python(a2))), Type()); } + + template + static R call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3) + { return from_python(expect_non_null(PyEval_CallMethod(self, const_cast(name), const_cast("(NNN)"), to_python(a1), to_python(a2), to_python(a3))), Type()); } + + template + static R call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4) + { return from_python(expect_non_null(PyEval_CallMethod(self, const_cast(name), const_cast("(NNNN)"), to_python(a1), to_python(a2), to_python(a3), to_python(a4))), Type()); } + + template + static R call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) + { return from_python(expect_non_null(PyEval_CallMethod(self, const_cast(name), const_cast("(NNNNN)"), to_python(a1), to_python(a2), to_python(a3), to_python(a4), to_python(a5))), Type()); } + +}; + +// This specialization wouldn't be needed, but MSVC6 doesn't correctly allow the following: +// void g(); +// void f() { return g(); } +template <> +struct Callback +{ static void call_method(PyObject* self, const char* name) + { expect_and_absorb_non_null(PyEval_CallMethod(self, const_cast(name), const_cast("()"))); } + + template + static void call_method(PyObject* self, const char* name, const A1& a1) + { expect_and_absorb_non_null(PyEval_CallMethod(self, const_cast(name), const_cast("(N)"), to_python(a1))); } + + template + static void call_method(PyObject* self, const char* name, const A1& a1, const A2& a2) + { expect_and_absorb_non_null(PyEval_CallMethod(self, const_cast(name), const_cast("(NN)"), to_python(a1), to_python(a2))); } + + template + static void call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3) + { expect_and_absorb_non_null(PyEval_CallMethod(self, const_cast(name), const_cast("(NNN)"), to_python(a1), to_python(a2), to_python(a3))); } + + template + static void call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4) + { expect_and_absorb_non_null(PyEval_CallMethod(self, const_cast(name), const_cast("(NNNN)"), to_python(a1), to_python(a2), to_python(a3), to_python(a4))); } + + template + static void call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) + { expect_and_absorb_non_null(PyEval_CallMethod(self, const_cast(name), const_cast("(NNNNN)"), to_python(a1), to_python(a2), to_python(a3), to_python(a4), to_python(a5))); } + +}; + +} // namespace py + +#endif // CALLBACK_DWA_052100_H_ diff --git a/caller.h b/caller.h new file mode 100644 index 00000000..7a2e74ca --- /dev/null +++ b/caller.h @@ -0,0 +1,507 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file generated for 5-argument member functions and 5-argument free +// functions by gen_caller.py + +#ifndef CALLER_DWA05090_H_ +# define CALLER_DWA05090_H_ + +# include "pyconfig.h" +# include "wrap_python.h" +# include +# include "signatures.h" +# include "none.h" + +namespace py { + +// Calling C++ from Python +template +struct Caller +{ + template + static PyObject* call(R (T::*pmf)(), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + if (!PyArg_ParseTuple(args, const_cast("O"), &self)) + return 0; + T& target = from_python(self, Type()); + return to_python((target.*pmf)()); + } + + template + static PyObject* call(R (T::*pmf)(A1), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + if (!PyArg_ParseTuple(args, const_cast("OO"), &self, &a1)) + return 0; + T& target = from_python(self, Type()); + return to_python((target.*pmf)(from_python(a1, Type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + if (!PyArg_ParseTuple(args, const_cast("OOO"), &self, &a1, &a2)) + return 0; + T& target = from_python(self, Type()); + return to_python((target.*pmf)(from_python(a1, Type()), + from_python(a2, Type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + if (!PyArg_ParseTuple(args, const_cast("OOOO"), &self, &a1, &a2, &a3)) + return 0; + T& target = from_python(self, Type()); + return to_python((target.*pmf)(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + if (!PyArg_ParseTuple(args, const_cast("OOOOO"), &self, &a1, &a2, &a3, &a4)) + return 0; + T& target = from_python(self, Type()); + return to_python((target.*pmf)(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()), + from_python(a4, Type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4, A5), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + if (!PyArg_ParseTuple(args, const_cast("OOOOOO"), &self, &a1, &a2, &a3, &a4, &a5)) + return 0; + T& target = from_python(self, Type()); + return to_python((target.*pmf)(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()), + from_python(a4, Type()), + from_python(a5, Type()))); + } + + + template + static PyObject* call(R (T::*pmf)() const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + if (!PyArg_ParseTuple(args, const_cast("O"), &self)) + return 0; + T& target = from_python(self, Type()); + return to_python((target.*pmf)()); + } + + template + static PyObject* call(R (T::*pmf)(A1) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + if (!PyArg_ParseTuple(args, const_cast("OO"), &self, &a1)) + return 0; + T& target = from_python(self, Type()); + return to_python((target.*pmf)(from_python(a1, Type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + if (!PyArg_ParseTuple(args, const_cast("OOO"), &self, &a1, &a2)) + return 0; + T& target = from_python(self, Type()); + return to_python((target.*pmf)(from_python(a1, Type()), + from_python(a2, Type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + if (!PyArg_ParseTuple(args, const_cast("OOOO"), &self, &a1, &a2, &a3)) + return 0; + T& target = from_python(self, Type()); + return to_python((target.*pmf)(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + if (!PyArg_ParseTuple(args, const_cast("OOOOO"), &self, &a1, &a2, &a3, &a4)) + return 0; + T& target = from_python(self, Type()); + return to_python((target.*pmf)(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()), + from_python(a4, Type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4, A5) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + if (!PyArg_ParseTuple(args, const_cast("OOOOOO"), &self, &a1, &a2, &a3, &a4, &a5)) + return 0; + T& target = from_python(self, Type()); + return to_python((target.*pmf)(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()), + from_python(a4, Type()), + from_python(a5, Type()))); + } + + // Free functions + static PyObject* call(R (*f)(), PyObject* args, PyObject* /* keywords */ ) { + if (!PyArg_ParseTuple(args, const_cast(""))) + return 0; + return to_python(f()); + } + + template + static PyObject* call(R (*f)(A1), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + if (!PyArg_ParseTuple(args, const_cast("O"), &a1)) + return 0; + return to_python(f(from_python(a1, Type()))); + } + + template + static PyObject* call(R (*f)(A1, A2), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + if (!PyArg_ParseTuple(args, const_cast("OO"), &a1, &a2)) + return 0; + return to_python(f(from_python(a1, Type()), + from_python(a2, Type()))); + } + + template + static PyObject* call(R (*f)(A1, A2, A3), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + if (!PyArg_ParseTuple(args, const_cast("OOO"), &a1, &a2, &a3)) + return 0; + return to_python(f(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()))); + } + + template + static PyObject* call(R (*f)(A1, A2, A3, A4), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + if (!PyArg_ParseTuple(args, const_cast("OOOO"), &a1, &a2, &a3, &a4)) + return 0; + return to_python(f(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()), + from_python(a4, Type()))); + } + + template + static PyObject* call(R (*f)(A1, A2, A3, A4, A5), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + if (!PyArg_ParseTuple(args, const_cast("OOOOO"), &a1, &a2, &a3, &a4, &a5)) + return 0; + return to_python(f(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()), + from_python(a4, Type()), + from_python(a5, Type()))); + } + +}; + +template <> +struct Caller +{ + template + static PyObject* call(void (T::*pmf)(), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + if (!PyArg_ParseTuple(args, const_cast("O"), &self)) + return 0; + T& target = from_python(self, Type()); + (target.*pmf)(); + return none(); + } + + template + static PyObject* call(void (T::*pmf)(A1), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + if (!PyArg_ParseTuple(args, const_cast("OO"), &self, &a1)) + return 0; + T& target = from_python(self, Type()); + (target.*pmf)(from_python(a1, Type())); + return none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + if (!PyArg_ParseTuple(args, const_cast("OOO"), &self, &a1, &a2)) + return 0; + T& target = from_python(self, Type()); + (target.*pmf)(from_python(a1, Type()), + from_python(a2, Type())); + return none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + if (!PyArg_ParseTuple(args, const_cast("OOOO"), &self, &a1, &a2, &a3)) + return 0; + T& target = from_python(self, Type()); + (target.*pmf)(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type())); + return none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + if (!PyArg_ParseTuple(args, const_cast("OOOOO"), &self, &a1, &a2, &a3, &a4)) + return 0; + T& target = from_python(self, Type()); + (target.*pmf)(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()), + from_python(a4, Type())); + return none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4, A5), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + if (!PyArg_ParseTuple(args, const_cast("OOOOOO"), &self, &a1, &a2, &a3, &a4, &a5)) + return 0; + T& target = from_python(self, Type()); + (target.*pmf)(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()), + from_python(a4, Type()), + from_python(a5, Type())); + return none(); + } + + + template + static PyObject* call(void (T::*pmf)() const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + if (!PyArg_ParseTuple(args, const_cast("O"), &self)) + return 0; + T& target = from_python(self, Type()); + (target.*pmf)(); + return none(); + } + + template + static PyObject* call(void (T::*pmf)(A1) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + if (!PyArg_ParseTuple(args, const_cast("OO"), &self, &a1)) + return 0; + T& target = from_python(self, Type()); + (target.*pmf)(from_python(a1, Type())); + return none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + if (!PyArg_ParseTuple(args, const_cast("OOO"), &self, &a1, &a2)) + return 0; + T& target = from_python(self, Type()); + (target.*pmf)(from_python(a1, Type()), + from_python(a2, Type())); + return none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + if (!PyArg_ParseTuple(args, const_cast("OOOO"), &self, &a1, &a2, &a3)) + return 0; + T& target = from_python(self, Type()); + (target.*pmf)(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type())); + return none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + if (!PyArg_ParseTuple(args, const_cast("OOOOO"), &self, &a1, &a2, &a3, &a4)) + return 0; + T& target = from_python(self, Type()); + (target.*pmf)(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()), + from_python(a4, Type())); + return none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4, A5) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + if (!PyArg_ParseTuple(args, const_cast("OOOOOO"), &self, &a1, &a2, &a3, &a4, &a5)) + return 0; + T& target = from_python(self, Type()); + (target.*pmf)(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()), + from_python(a4, Type()), + from_python(a5, Type())); + return none(); + } + + + // Free functions + static PyObject* call(void (*f)(), PyObject* args, PyObject* /* keywords */ ) { + if (!PyArg_ParseTuple(args, const_cast(""))) + return 0; + f(); + return none(); + } + + template + static PyObject* call(void (*f)(A1), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + if (!PyArg_ParseTuple(args, const_cast("O"), &a1)) + return 0; + f(from_python(a1, Type())); + return none(); + } + + template + static PyObject* call(void (*f)(A1, A2), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + if (!PyArg_ParseTuple(args, const_cast("OO"), &a1, &a2)) + return 0; + f(from_python(a1, Type()), + from_python(a2, Type())); + return none(); + } + + template + static PyObject* call(void (*f)(A1, A2, A3), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + if (!PyArg_ParseTuple(args, const_cast("OOO"), &a1, &a2, &a3)) + return 0; + f(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type())); + return none(); + } + + template + static PyObject* call(void (*f)(A1, A2, A3, A4), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + if (!PyArg_ParseTuple(args, const_cast("OOOO"), &a1, &a2, &a3, &a4)) + return 0; + f(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()), + from_python(a4, Type())); + return none(); + } + + template + static PyObject* call(void (*f)(A1, A2, A3, A4, A5), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + if (!PyArg_ParseTuple(args, const_cast("OOOOO"), &a1, &a2, &a3, &a4, &a5)) + return 0; + f(from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()), + from_python(a4, Type()), + from_python(a5, Type())); + return none(); + } + +}; + +} + +#endif diff --git a/cast.h b/cast.h new file mode 100644 index 00000000..d9f1bd16 --- /dev/null +++ b/cast.h @@ -0,0 +1,79 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef CAST_DWA052500_H_ +# define CAST_DWA052500_H_ + +# include "wrap_python.h" +# include + +namespace py { + +// The default way of converting a PyObject* or PyTypeObject* to a T* +template +struct DowncastTraits +{ + template + static T* cast(U* p) { return static_cast(p); } +}; + +inline PyTypeObject* as_base_object(const PyTypeObject*, PyObject* p) +{ + return reinterpret_cast(p); +} + +inline PyObject* as_base_object(const PyObject*, PyObject* p) +{ + return p; +} + +inline const PyTypeObject* as_base_object(const PyTypeObject*, const PyObject* p) +{ + return reinterpret_cast(p); +} + +inline const PyObject* as_base_object(const PyObject*, const PyObject* p) +{ + return p; +} + +// Convert a pointer to any type derived from PyObject or PyTypeObject to a PyObject* +inline PyObject* as_object(PyObject* p) { return p; } +inline PyObject* as_object(PyTypeObject* p) { return reinterpret_cast(p); } + +// If I didn't have to support stupid MSVC6 we could just use a simple template function: +// template T* downcast(PyObject*). +template +struct Downcast : boost::dereferenceable, T*> +{ + Downcast(PyObject* p) + : m_p(DowncastTraits::cast(as_base_object((T*)0, p))) + {} + + Downcast(const PyObject* p) + : m_p(DowncastTraits::cast(as_base_object((const T*)0, p))) + {} + + Downcast(PyTypeObject* p) + : m_p(DowncastTraits::cast(p)) + {} + + Downcast(const PyTypeObject* p) + : m_p(DowncastTraits::cast(p)) + {} + + operator T*() const { return m_p; } + T* get() const { return m_p; } + T& operator*() const { return *m_p; } +private: + T* m_p; +}; + +} + +#endif // CAST_DWA052500_H_ diff --git a/class_wrapper.h b/class_wrapper.h new file mode 100644 index 00000000..f0c342c2 --- /dev/null +++ b/class_wrapper.h @@ -0,0 +1,87 @@ +#ifndef CLASS_WRAPPER_DWA101000_H_ +# define CLASS_WRAPPER_DWA101000_H_ + +#include "extclass.h" +#include "module.h" +#include "py.h" +#include "cast.h" + +namespace py { + +namespace detail { +struct EmptyBase {}; +} + +// Syntactic sugar to make wrapping classes more convenient +template > +class ClassWrapper + : PyExtensionClassConverters // Works around MSVC6.x/GCC2.95.2 bug described below +{ + public: + ClassWrapper(Module& module, const char* name) + : m_class(new ExtensionClass(name)) +#if 0 // def PY_MSVC6_OR_EARLIER + , m_msvc_hack(name) +#endif + { + module.add(Ptr(as_object(m_class.get()), Ptr::new_ref), name); + } + + // define constructors + template + void def(Constructor signature) + { m_class->def(signature); } + + // define member functions. In fact this works for free functions, too - + // they act like static member functions, or if they start with the + // appropriate self argument (as a pointer), they can be used just like + // ordinary member functions -- just like Python! + template + void def(Fn fn, const char* name) + { m_class->def(fn, name); } + + // Define a virtual member function with a default implementation. + // default_fn should be a function which provides the default implementation. + // Be careful that default_fn does not in fact call fn virtually! + template + void def(Fn fn, const char* name, DefaultFn default_fn) + { m_class->def(fn, name, default_fn); } + + // Provide a function which implements x., reading from the given + // member (pm) of the T instance + template + void def_getter(MemberType T::*pm, const char* name) + { m_class->def_getter(pm, name); } + + // Provide a function which implements assignment to x., writing to + // the given member (pm) of the T instance + template + void def_setter(MemberType T::*pm, const char* name) + { m_class->def_getter(pm, name); } + + // Expose the given member (pm) of the T instance as a read-only attribute + template + void def_readonly(MemberType T::*pm, const char* name) + { m_class->def_readonly(pm, name); } + + // Expose the given member (pm) of the T instance as a read/write attribute + template + void def_read_write(MemberType T::*pm, const char* name) + { m_class->def_read_write(pm, name); } + private: + PyPtr > m_class; +#if 0 // def PY_MSVC6_OR_EARLIER + PyExtensionClassConverters m_msvc_hack; +#endif +}; + +// The bug mentioned at the top of this file is that on certain compilers static +// global functions declared within the body of a class template will only be +// generated when the class template is constructed, and when (for some reason) +// the construction does not occur via a new-expression. Otherwise, we could +// rely on the initialization of the m_class data member to cause all of the +// to_/from_python functions to come into being. + +} + +#endif // CLASS_WRAPPER_DWA101000_H_ diff --git a/demo.bdf b/demo.bdf new file mode 100644 index 00000000..638c16e5 --- /dev/null +++ b/demo.bdf @@ -0,0 +1,4 @@ +TargetName=demo +TargetType=dll +SourceFiles=extclass_demo.cpp +Libs=py_cpp python utils diff --git a/demo_d.bdf b/demo_d.bdf new file mode 100644 index 00000000..92699e0c --- /dev/null +++ b/demo_d.bdf @@ -0,0 +1,4 @@ +TargetName=demo_d +TargetType=dll +SourceFiles=extclass_demo_d.cpp +Libs=py_cpp_d python_d utils diff --git a/doctest.py b/doctest.py new file mode 100644 index 00000000..892f2ab5 --- /dev/null +++ b/doctest.py @@ -0,0 +1,1112 @@ +# Module doctest version 0.9.4 +# Released to the public domain 27-Mar-1999, +# by Tim Peters (tim_one@email.msn.com). + +# Provided as-is; use at your own risk; no warranty; no promises; enjoy! + +"""Module doctest -- a framework for running examples in docstrings. + +NORMAL USAGE + +In normal use, end each module M with: + +def _test(): + import doctest, M # replace M with your module's name + return doctest.testmod(M) # ditto + +if __name__ == "__main__": + _test() + +Then running the module as a script will cause the examples in the +docstrings to get executed and verified: + +python M.py + +This won't display anything unless an example fails, in which case +the failing example(s) and the cause(s) of the failure(s) are printed +to stdout (why not stderr? because stderr is a lame hack <0.2 wink>), +and the final line of output is "Test failed.". + +Run it with the -v switch instead: + +python M.py -v + +and a detailed report of all examples tried is printed to stdout, along +with assorted summaries at the end. + +You can force verbose mode by passing "verbose=1" to testmod, or prohibit +it by passing "verbose=0". In either of those cases, sys.argv is not +examined by testmod. + +In any case, testmod returns a 2-tuple of ints (f, t), where f is the +number of docstring examples that failed and t is the total number of +docstring examples attempted. + + +WHICH DOCSTRINGS ARE EXAMINED? + ++ M.__doc__. + ++ f.__doc__ for all functions f in M.__dict__.values(), except those + with private names. + ++ C.__doc__ for all classes C in M.__dict__.values(), except those with + private names. + ++ If M.__test__ exists and "is true", it must be a dict, and + each entry maps a (string) name to a function object, class object, or + string. Function and class object docstrings found from M.__test__ + are searched even if the name is private, and strings are searched + directly as if they were docstrings. In output, a key K in M.__test__ + appears with name + .__test__.K + +Any classes found are recursively searched similarly, to test docstrings +in their contained methods and nested classes. Private names reached +from M's globals are skipped, but all names reached from M.__test__ are +searched. + +By default, a name is considered to be private if it begins with an +underscore (like "_my_func") but doesn't both begin and end with (at +least) two underscores (like "__init__"). You can change the default +by passing your own "isprivate" function to testmod. + +If you want to test docstrings in objects with private names too, stuff +them into an M.__test__ dict, or see ADVANCED USAGE below (e.g., pass your +own isprivate function to Tester's constructor, or call the rundoc method +of a Tester instance). + +Warning: imports can cause trouble; e.g., if you do + +from XYZ import XYZclass + +then XYZclass is a name in M.__dict__ too, and doctest has no way to +know that XYZclass wasn't *defined* in M. So it may try to execute the +examples in XYZclass's docstring, and those in turn may require a +different set of globals to work correctly. I prefer to do "import *"- +friendly imports, a la + +import XYY +_XYZclass = XYZ.XYZclass +del XYZ + +and then the leading underscore stops testmod from going nuts. You may +prefer the method in the next section. + + +WHAT'S THE EXECUTION CONTEXT? + +By default, each time testmod finds a docstring to test, it uses a +*copy* of M's globals (so that running tests on a module doesn't change +the module's real globals, and so that one test in M can't leave behind +crumbs that accidentally allow another test to work). This means +examples can freely use any names defined at top-level in M. It also +means that sloppy imports (see above) can cause examples in external +docstrings to use globals inappropriate for them. + +You can force use of your own dict as the execution context by passing +"globs=your_dict" to testmod instead. Presumably this would be a copy +of M.__dict__ merged with the globals from other imported modules. + + +WHAT IF I WANT TO TEST A WHOLE PACKAGE? + +Piece o' cake, provided the modules do their testing from docstrings. +Here's the test.py I use for the world's most elaborate Rational/ +floating-base-conversion pkg (which I'll distribute some day): + +from Rational import Cvt +from Rational import Format +from Rational import machprec +from Rational import Rat +from Rational import Round +from Rational import utils + +modules = (Cvt, + Format, + machprec, + Rat, + Round, + utils) + +def _test(): + import doctest + import sys + verbose = "-v" in sys.argv + for mod in modules: + doctest.testmod(mod, verbose=verbose, report=0) + doctest.master.summarize() + +if __name__ == "__main__": + _test() + +IOW, it just runs testmod on all the pkg modules. testmod remembers the +names and outcomes (# of failures, # of tries) for each item it's seen, +and passing "report=0" prevents it from printing a summary in verbose +mode. Instead, the summary is delayed until all modules have been +tested, and then "doctest.master.summarize()" forces the summary at the +end. + +So this is very nice in practice: each module can be tested individually +with almost no work beyond writing up docstring examples, and collections +of modules can be tested too as a unit with no more work than the above. + + +WHAT ABOUT EXCEPTIONS? + +No problem, as long as the only output generated by the example is the +traceback itself. For example: + + >>> 1/0 + Traceback (innermost last): + File "", line 1, in ? + ZeroDivisionError: integer division or modulo + >>> + +Note that only the exception type and value are compared (specifically, +only the last line in the traceback). + + +ADVANCED USAGE + +doctest.testmod() captures the testing policy I find most useful most +often. You may want other policies. + +testmod() actually creates a local instance of class doctest.Tester, +runs appropriate methods of that class, and merges the results into +global Tester instance doctest.master. + +You can create your own instances of doctest.Tester, and so build your +own policies, or even run methods of doctest.master directly. See +doctest.Tester.__doc__ for details. + + +SO WHAT DOES A DOCSTRING EXAMPLE LOOK LIKE ALREADY!? + +Oh ya. It's easy! In most cases a copy-and-paste of an interactive +console session works fine -- just make sure the leading whitespace +is rigidly consistent (you can mix tabs and spaces if you're too lazy +to do it right, but doctest is not in the business of guessing what +you think a tab means). + + >>> # comments are ignored + >>> x = 12 + >>> x + 12 + >>> if x == 13: + ... print "yes" + ... else: + ... print "no" + ... print "NO" + ... print "NO!!!" + ... + no + NO + NO!!! + >>> + +Any expected output must immediately follow the final ">>>" or "..." +line containing the code, and the expected output (if any) extends +to the next ">>>" or all-whitespace line. That's it. + +Bummers: + ++ Expected output cannot contain an all-whitespace line, since such a + line is taken to signal the end of expected output. + ++ Output to stdout is captured, but not output to stderr (exception + tracebacks are captured via a different means). + ++ If you continue a line via backslashing in an interactive session, + or for any other reason use a backslash, you need to double the + backslash in the docstring version. This is simply because you're + in a string, and so the backslash must be escaped for it to survive + intact. Like: + +>>> if "yes" == \\ +... "y" + \\ +... "es": # in the source code you'll see the doubled backslashes +... print 'yes' +yes + +The starting column doesn't matter: + +>>> assert "Easy!" + >>> import math + >>> math.floor(1.9) + 1.0 + +and as many leading whitespace characters are stripped from the expected +output as appeared in the initial ">>>" line that triggered it. + +If you execute this very file, the examples above will be found and +executed, leading to this output in verbose mode: + +Running doctest.__doc__ +Trying: 1/0 +Expecting: +Traceback (innermost last): + File "", line 1, in ? +ZeroDivisionError: integer division or modulo +ok +Trying: x = 12 +Expecting: nothing +ok +Trying: x +Expecting: 12 +ok +Trying: +if x == 13: + print "yes" +else: + print "no" + print "NO" + print "NO!!!" +Expecting: +no +NO +NO!!! +ok +... and a bunch more like that, with this summary at the end: + +5 items had no tests: + doctest.Tester.__init__ + doctest.Tester.run__test__ + doctest.Tester.summarize + doctest.run_docstring_examples + doctest.testmod +12 items passed all tests: + 8 tests in doctest + 6 tests in doctest.Tester + 10 tests in doctest.Tester.merge + 7 tests in doctest.Tester.rundict + 3 tests in doctest.Tester.rundoc + 3 tests in doctest.Tester.runstring + 2 tests in doctest.__test__._TestClass + 2 tests in doctest.__test__._TestClass.__init__ + 2 tests in doctest.__test__._TestClass.get + 1 tests in doctest.__test__._TestClass.square + 2 tests in doctest.__test__.string + 7 tests in doctest.is_private +53 tests in 17 items. +53 passed and 0 failed. +Test passed. +""" + +# 0,0,1 06-Mar-1999 +# initial version posted +# 0,0,2 06-Mar-1999 +# loosened parsing: +# cater to stinkin' tabs +# don't insist on a blank after PS2 prefix +# so trailing "... " line from a compound stmt no longer +# breaks if the file gets whitespace-trimmed +# better error msgs for inconsistent leading whitespace +# 0,9,1 08-Mar-1999 +# exposed the Tester class and added client methods +# plus docstring examples of their use (eww - head-twisting!) +# fixed logic error in reporting total # of tests & failures +# added __test__ support to testmod (a pale reflection of Christian +# Tismer's vision ...) +# removed the "deep" argument; fiddle __test__ instead +# simplified endcase logic for extracting tests, and running them. +# before, if no output was expected but some was produced +# anyway via an eval'ed result, the discrepancy wasn't caught +# made TestClass private and used __test__ to get at it +# many doc updates +# speed _SpoofOut for long expected outputs +# 0,9,2 09-Mar-1999 +# throw out comments from examples, enabling use of the much simpler +# exec compile(... "single") ... +# for simulating the runtime; that barfs on comment-only lines +# used the traceback module to do a much better job of reporting +# exceptions +# run __doc__ values thru str(), "just in case" +# privateness of names now determined by an overridable "isprivate" +# function +# by default a name now considered to be private iff it begins with +# an underscore but doesn't both begin & end with two of 'em; so +# e.g. Class.__init__ etc are searched now -- as they always +# should have been +# 0,9,3 18-Mar-1999 +# added .flush stub to _SpoofOut (JPython buglet diagnosed by +# Hugh Emberson) +# repaired ridiculous docs about backslashes in examples +# minor internal changes +# changed source to Unix line-end conventions +# moved __test__ logic into new Tester.run__test__ method +# 0,9,4 27-Mar-1999 +# report item name and line # in failing examples +# 0,9,5 29-Jun-1999 +# allow straightforward exceptions in examples - thanks to Mark Hammond! +# 0,9,5,1 31-Mar-2000 +# break cyclic references to functions which are defined in docstrings, +# avoiding cyclic trash +# 0,9,5,2 11-Apr-2000 +# made module argument to testmod optional; it runs testmod on the __main__ +# module in that case. + +__version__ = 0, 9, 5 + +import types +_FunctionType = types.FunctionType +_ClassType = types.ClassType +_ModuleType = types.ModuleType +_StringType = types.StringType +del types + +import string +_string_find = string.find +_string_join = string.join +_string_split = string.split +_string_rindex = string.rindex +del string + +import re +PS1 = ">>>" +PS2 = "..." +_isPS1 = re.compile(r"(\s*)" + re.escape(PS1)).match +_isPS2 = re.compile(r"(\s*)" + re.escape(PS2)).match +_isEmpty = re.compile(r"\s*$").match +_isComment = re.compile(r"\s*#").match +del re + +# Extract interactive examples from a string. Return a list of triples, +# (source, outcome, lineno). "source" is the source code, and ends +# with a newline iff the source spans more than one line. "outcome" is +# the expected output if any, else an empty string. When not empty, +# outcome always ends with a newline. "lineno" is the line number, +# 0-based wrt the start of the string, of the first source line. + +def _extract_examples(s): + isPS1, isPS2 = _isPS1, _isPS2 + isEmpty, isComment = _isEmpty, _isComment + examples = [] + lines = _string_split(s, "\n") + i, n = 0, len(lines) + while i < n: + line = lines[i] + i = i + 1 + m = isPS1(line) + if m is None: + continue + j = m.end(0) # beyond the prompt + if isEmpty(line, j) or isComment(line, j): + # a bare prompt or comment -- not interesting + continue + lineno = i - 1 + if line[j] != " ": + raise ValueError("line " + `lineno` + " of docstring lacks " + "blank after " + PS1 + ": " + line) + j = j + 1 + blanks = m.group(1) + nblanks = len(blanks) + # suck up this and following PS2 lines + source = [] + while 1: + source.append(line[j:]) + line = lines[i] + m = isPS2(line) + if m: + if m.group(1) != blanks: + raise ValueError("inconsistent leading whitespace " + "in line " + `i` + " of docstring: " + line) + i = i + 1 + else: + break + if len(source) == 1: + source = source[0] + else: + # get rid of useless null line from trailing empty "..." + if source[-1] == "": + del source[-1] + source = _string_join(source, "\n") + "\n" + # suck up response + if isPS1(line) or isEmpty(line): + expect = "" + else: + expect = [] + while 1: + if line[:nblanks] != blanks: + raise ValueError("inconsistent leading whitespace " + "in line " + `i` + " of docstring: " + line) + expect.append(line[nblanks:]) + i = i + 1 + line = lines[i] + if isPS1(line) or isEmpty(line): + break + expect = _string_join(expect, "\n") + "\n" + examples.append( (source, expect, lineno) ) + return examples + +# Capture stdout when running examples. + +class _SpoofOut: + def __init__(self): + self.clear() + def write(self, s): + self.buf.append(s) + def get(self): + return _string_join(self.buf, "") + def clear(self): + self.buf = [] + def flush(self): + # JPython calls flush + pass + +# Display some tag-and-msg pairs nicely, keeping the tag and its msg +# on the same line when that makes sense. + +def _tag_out(printer, *tag_msg_pairs): + for tag, msg in tag_msg_pairs: + printer(tag + ":") + msg_has_nl = msg[-1:] == "\n" + msg_has_two_nl = msg_has_nl and \ + _string_find(msg, "\n") < len(msg) - 1 + if len(tag) + len(msg) < 76 and not msg_has_two_nl: + printer(" ") + else: + printer("\n") + printer(msg) + if not msg_has_nl: + printer("\n") + +# Run list of examples, in context globs. "out" can be used to display +# stuff to "the real" stdout, and fakeout is an instance of _SpoofOut +# that captures the examples' std output. Return (#failures, #tries). + +def _run_examples_inner(out, fakeout, examples, globs, verbose, name): + import sys, traceback + OK, BOOM, FAIL = range(3) + NADA = "nothing" + stderr = _SpoofOut() + failures = 0 + for source, want, lineno in examples: + if verbose: + _tag_out(out, ("Trying", source), + ("Expecting", want or NADA)) + fakeout.clear() + try: + exec compile(source, "", "single") in globs + got = fakeout.get() + state = OK + except: + # See whether the exception was expected. + if _string_find(want, "Traceback (innermost last):\n") == 0: + # Only compare exception type and value - the rest of + # the traceback isn't necessary. + want = _string_split(want, '\n')[-2] + '\n' + exc_type, exc_val, exc_tb = sys.exc_info() + got = traceback.format_exception_only(exc_type, exc_val)[0] + state = OK + else: + # unexpected exception + stderr.clear() + traceback.print_exc(file=stderr) + state = BOOM + + if state == OK: + if got == want: + if verbose: + out("ok\n") + continue + state = FAIL + + assert state in (FAIL, BOOM) + failures = failures + 1 + out("*" * 65 + "\n") + _tag_out(out, ("Failure in example", source)) + out("from line #" + `lineno` + " of " + name + "\n") + if state == FAIL: + _tag_out(out, ("Expected", want or NADA), ("Got", got)) + else: + assert state == BOOM + _tag_out(out, ("Exception raised", stderr.get())) + return failures, len(examples) + +# Run list of examples, in context globs. Return (#failures, #tries). + +def _run_examples(examples, globs, verbose, name): + import sys + saveout = sys.stdout + try: + sys.stdout = fakeout = _SpoofOut() + x = _run_examples_inner(saveout.write, fakeout, examples, + globs, verbose, name) + finally: + sys.stdout = saveout + return x + +def run_docstring_examples(f, globs, verbose=0, name="NoName"): + """f, globs, verbose=0, name="NoName" -> run examples from f.__doc__. + + Use dict globs as the globals for execution. + Return (#failures, #tries). + + If optional arg verbose is true, print stuff even if there are no + failures. + Use string name in failure msgs. + """ + + try: + doc = f.__doc__ + if not doc: + # docstring empty or None + return 0, 0 + # just in case CT invents a doc object that has to be forced + # to look like a string <0.9 wink> + doc = str(doc) + except: + return 0, 0 + + e = _extract_examples(doc) + if not e: + return 0, 0 + return _run_examples(e, globs, verbose, name) + +def is_private(prefix, base): + """prefix, base -> true iff name prefix + "." + base is "private". + + Prefix may be an empty string, and base does not contain a period. + Prefix is ignored (although functions you write conforming to this + protocol may make use of it). + Return true iff base begins with an (at least one) underscore, but + does not both begin and end with (at least) two underscores. + + >>> is_private("a.b", "my_func") + 0 + >>> is_private("____", "_my_func") + 1 + >>> is_private("someclass", "__init__") + 0 + >>> is_private("sometypo", "__init_") + 1 + >>> is_private("x.y.z", "_") + 1 + >>> is_private("_x.y.z", "__") + 0 + >>> is_private("", "") # senseless but consistent + 0 + """ + + return base[:1] == "_" and not base[:2] == "__" == base[-2:] + +class Tester: + """Class Tester -- runs docstring examples and accumulates stats. + +In normal use, function doctest.testmod() hides all this from you, +so use that if you can. Create your own instances of Tester to do +fancier things. + +Methods: + runstring(s, name) + Search string s for examples to run; use name for logging. + Return (#failures, #tries). + + rundoc(object, name=None) + Search object.__doc__ for examples to run; use name (or + object.__name__) for logging. Return (#failures, #tries). + + rundict(d, name) + Search for examples in docstrings in all of d.values(); use name + for logging. Return (#failures, #tries). + + run__test__(d, name) + Treat dict d like module.__test__. Return (#failures, #tries). + + summarize(verbose=None) + Display summary of testing results, to stdout. Return + (#failures, #tries). + + merge(other) + Merge in the test results from Tester instance "other". + +>>> from doctest import Tester +>>> t = Tester(globs={'x': 42}, verbose=0) +>>> t.runstring(r''' +... >>> x = x * 2 +... >>> print x +... 42 +... ''', 'XYZ') +***************************************************************** +Failure in example: print x +from line #2 of XYZ +Expected: 42 +Got: 84 +(1, 2) +>>> t.runstring(">>> x = x * 2\\n>>> print x\\n84\\n", 'example2') +(0, 2) +>>> t.summarize() +1 items had failures: + 1 of 2 in XYZ +***Test Failed*** 1 failures. +(1, 4) +>>> t.summarize(verbose=1) +1 items passed all tests: + 2 tests in example2 +1 items had failures: + 1 of 2 in XYZ +4 tests in 2 items. +3 passed and 1 failed. +***Test Failed*** 1 failures. +(1, 4) +>>> +""" + + def __init__(self, mod=None, globs=None, verbose=None, + isprivate=None): + """mod=None, globs=None, verbose=None, isprivate=None + +See doctest.__doc__ for an overview. + +Optional keyword arg "mod" is a module, whose globals are used for +executing examples. If not specified, globs must be specified. + +Optional keyword arg "globs" gives a dict to be used as the globals +when executing examples; if not specified, use the globals from +module mod. + +In either case, a copy of the dict is used for each docstring +examined. + +Optional keyword arg "verbose" prints lots of stuff if true, only +failures if false; by default, it's true iff "-v" is in sys.argv. + +Optional keyword arg "isprivate" specifies a function used to determine +whether a name is private. The default function is doctest.is_private; +see its docs for details. +""" + + if mod is None and globs is None: + raise TypeError("Tester.__init__: must specify mod or globs") + if mod is not None and type(mod) is not _ModuleType: + raise TypeError("Tester.__init__: mod must be a module; " + + `mod`) + if globs is None: + globs = mod.__dict__ + self.globs = globs + + if verbose is None: + import sys + verbose = "-v" in sys.argv + self.verbose = verbose + + if isprivate is None: + isprivate = is_private + self.isprivate = isprivate + + self.name2ft = {} # map name to (#failures, #trials) pair + + def runstring(self, s, name): + """ + s, name -> search string s for examples to run, logging as name. + + Use string name as the key for logging the outcome. + Return (#failures, #examples). + + >>> t = Tester(globs={}, verbose=1) + >>> test = r''' + ... # just an example + ... >>> x = 1 + 2 + ... >>> x + ... 3 + ... ''' + >>> t.runstring(test, "Example") + Running string Example + Trying: x = 1 + 2 + Expecting: nothing + ok + Trying: x + Expecting: 3 + ok + 0 of 2 examples failed in string Example + (0, 2) + """ + + if self.verbose: + print "Running string", name + f = t = 0 + e = _extract_examples(s) + if e: + globs = self.globs.copy() + f, t = _run_examples(e, globs, self.verbose, name) + globs.clear() # DWA - break cyclic references to functions defined in docstrings + if self.verbose: + print f, "of", t, "examples failed in string", name + self.__record_outcome(name, f, t) + return f, t + + def rundoc(self, object, name=None): + """ + object, name=None -> search object.__doc__ for examples to run. + + Use optional string name as the key for logging the outcome; + by default use object.__name__. + Return (#failures, #examples). + If object is a class object, search recursively for method + docstrings too. + object.__doc__ is examined regardless of name, but if object is + a class, whether private names reached from object are searched + depends on the constructor's "isprivate" argument. + + >>> t = Tester(globs={}, verbose=0) + >>> def _f(): + ... '''Trivial docstring example. + ... >>> assert 2 == 2 + ... ''' + ... return 32 + ... + >>> t.rundoc(_f) # expect 0 failures in 1 example + (0, 1) + """ + + if name is None: + try: + name = object.__name__ + except AttributeError: + raise ValueError("Tester.rundoc: name must be given " + "when object.__name__ doesn't exist; " + `object`) + if self.verbose: + print "Running", name + ".__doc__" + globs = self.globs.copy() + f, t = run_docstring_examples(object, globs, + self.verbose, name) + globs.clear() # DWA - break cyclic references to functions defined in docstrings + + if self.verbose: + print f, "of", t, "examples failed in", name + ".__doc__" + self.__record_outcome(name, f, t) + if type(object) is _ClassType: + f2, t2 = self.rundict(object.__dict__, name) + f = f + f2 + t = t + t2 + return f, t + + def rundict(self, d, name): + """ + d. name -> search for docstring examples in all of d.values(). + + For k, v in d.items() such that v is a function or class, + do self.rundoc(v, name + "." + k). Whether this includes + objects with private names depends on the constructor's + "isprivate" argument. + Return aggregate (#failures, #examples). + + >>> def _f(): + ... '''>>> assert 1 == 1 + ... ''' + >>> def g(): + ... '''>>> assert 2 != 1 + ... ''' + >>> d = {"_f": _f, "g": g} + >>> t = Tester(globs={}, verbose=0) + >>> t.rundict(d, "rundict_test") # _f is skipped + (0, 1) + >>> t = Tester(globs={}, verbose=0, isprivate=lambda x,y: 0) + >>> t.rundict(d, "rundict_test_pvt") # both are searched + (0, 2) + """ + + if not hasattr(d, "items"): + raise TypeError("Tester.rundict: d must support .items(); " + + `d`) + f = t = 0 + for thisname, value in d.items(): + if type(value) in (_FunctionType, _ClassType): + f2, t2 = self.__runone(value, name + "." + thisname) + f = f + f2 + t = t + t2 + return f, t + + def run__test__(self, d, name): + """d, name -> Treat dict d like module.__test__. + + Return (#failures, #tries). + See testmod.__doc__ for details. + """ + + failures = tries = 0 + prefix = name + "." + savepvt = self.isprivate + try: + self.isprivate = lambda *args: 0 + for k, v in d.items(): + thisname = prefix + k + if type(v) is _StringType: + f, t = self.runstring(v, thisname) + elif type(v) in (_FunctionType, _ClassType): + f, t = self.rundoc(v, thisname) + else: + raise TypeError("Tester.run__test__: values in " + "dict must be strings, functions " + "or classes; " + `v`) + failures = failures + f + tries = tries + t + finally: + self.isprivate = savepvt + return failures, tries + + def summarize(self, verbose=None): + """ + verbose=None -> summarize results, return (#failures, #tests). + + Print summary of test results to stdout. + Optional arg 'verbose' controls how wordy this is. By + default, use the verbose setting established by the + constructor. + """ + + if verbose is None: + verbose = self.verbose + notests = [] + passed = [] + failed = [] + totalt = totalf = 0 + for x in self.name2ft.items(): + name, (f, t) = x + assert f <= t + totalt = totalt + t + totalf = totalf + f + if t == 0: + notests.append(name) + elif f == 0: + passed.append( (name, t) ) + else: + failed.append(x) + if verbose: + if notests: + print len(notests), "items had no tests:" + notests.sort() + for thing in notests: + print " ", thing + if passed: + print len(passed), "items passed all tests:" + passed.sort() + for thing, count in passed: + print " %3d tests in %s" % (count, thing) + if failed: + print len(failed), "items had failures:" + failed.sort() + for thing, (f, t) in failed: + print " %3d of %3d in %s" % (f, t, thing) + if verbose: + print totalt, "tests in", len(self.name2ft), "items." + print totalt - totalf, "passed and", totalf, "failed." + if totalf: + print "***Test Failed***", totalf, "failures." + elif verbose: + print "Test passed." + return totalf, totalt + + def merge(self, other): + """ + other -> merge in test results from the other Tester instance. + + If self and other both have a test result for something + with the same name, the (#failures, #tests) results are + summed, and a warning is printed to stdout. + + >>> from doctest import Tester + >>> t1 = Tester(globs={}, verbose=0) + >>> t1.runstring(''' + ... >>> x = 12 + ... >>> print x + ... 12 + ... ''', "t1example") + (0, 2) + >>> + >>> t2 = Tester(globs={}, verbose=0) + >>> t2.runstring(''' + ... >>> x = 13 + ... >>> print x + ... 13 + ... ''', "t2example") + (0, 2) + >>> common = ">>> assert 1 + 2 == 3\\n" + >>> t1.runstring(common, "common") + (0, 1) + >>> t2.runstring(common, "common") + (0, 1) + >>> t1.merge(t2) + *** Tester.merge: 'common' in both testers; summing outcomes. + >>> t1.summarize(1) + 3 items passed all tests: + 2 tests in common + 2 tests in t1example + 2 tests in t2example + 6 tests in 3 items. + 6 passed and 0 failed. + Test passed. + (0, 6) + >>> + """ + + d = self.name2ft + for name, (f, t) in other.name2ft.items(): + if d.has_key(name): + print "*** Tester.merge: '" + name + "' in both" \ + " testers; summing outcomes." + f2, t2 = d[name] + f = f + f2 + t = t + t2 + d[name] = f, t + + def __record_outcome(self, name, f, t): + if self.name2ft.has_key(name): + print "*** Warning: '" + name + "' was tested before;", \ + "summing outcomes." + f2, t2 = self.name2ft[name] + f = f + f2 + t = t + t2 + self.name2ft[name] = f, t + + def __runone(self, target, name): + if "." in name: + i = _string_rindex(name, ".") + prefix, base = name[:i], name[i+1:] + else: + prefix, base = "", base + if self.isprivate(prefix, base): + return 0, 0 + return self.rundoc(target, name) + +master = None + +def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None, + report=1): + """m=None, name=None, globs=None, verbose=None, isprivate=None, report=1 + + Test examples in docstrings in functions and classes reachable from + module m, starting with m.__doc__. Private names are skipped. + + Also test examples reachable from dict m.__test__ if it exists and is + not None. m.__dict__ maps names to functions, classes and strings; + function and class docstrings are tested even if the name is private; + strings are tested directly, as if they were docstrings. + + Return (#failures, #tests). + + See doctest.__doc__ for an overview. + + Optional keyword arg "name" gives the name of the module; by default + use m.__name__. + + Optional keyword arg "globs" gives a dict to be used as the globals + when executing examples; by default, use m.__dict__. A copy of this + dict is actually used for each docstring, so that each docstring's + examples start with a clean slate. + + Optional keyword arg "verbose" prints lots of stuff if true, prints + only failures if false; by default, it's true iff "-v" is in sys.argv. + + Optional keyword arg "isprivate" specifies a function used to + determine whether a name is private. The default function is + doctest.is_private; see its docs for details. + + Optional keyword arg "report" prints a summary at the end when true, + else prints nothing at the end. In verbose mode, the summary is + detailed, else very brief (in fact, empty if all tests passed). + + Advanced tomfoolery: testmod runs methods of a local instance of + class doctest.Tester, then merges the results into (or creates) + global Tester instance doctest.master. Methods of doctest.master + can be called directly too, if you want to do something unusual. + Passing report=0 to testmod is especially useful then, to delay + displaying a summary. Invoke doctest.master.summarize(verbose) + when you're done fiddling. + """ + + global master + + if m is None: + import sys + # DWA - m will still be None if this wasn't invoked from the command + # line, in which case the following TypeError is about as good an error + # as we should expect + m = sys.modules.get('__main__') + + if type(m) is not _ModuleType: + raise TypeError("testmod: module required; " + `m`) + if name is None: + name = m.__name__ + tester = Tester(m, globs=globs, verbose=verbose, isprivate=isprivate) + failures, tries = tester.rundoc(m, name) + f, t = tester.rundict(m.__dict__, name) + failures = failures + f + tries = tries + t + if hasattr(m, "__test__"): + testdict = m.__test__ + if testdict: + if not hasattr(testdict, "items"): + raise TypeError("testmod: module.__test__ must support " + ".items(); " + `testdict`) + f, t = tester.run__test__(testdict, name + ".__test__") + failures = failures + f + tries = tries + t + if report: + tester.summarize() + if master is None: + master = tester + else: + master.merge(tester) + return failures, tries + +class _TestClass: + """ + A pointless class, for sanity-checking of docstring testing. + + Methods: + square() + get() + + >>> _TestClass(13).get() + _TestClass(-12).get() + 1 + >>> hex(_TestClass(13).square().get()) + '0xa9' + """ + + def __init__(self, val): + """val -> _TestClass object with associated value val. + + >>> t = _TestClass(123) + >>> print t.get() + 123 + """ + + self.val = val + + def square(self): + """square() -> square TestClass's associated value + + >>> _TestClass(13).square().get() + 169 + """ + + self.val = self.val ** 2 + return self + + def get(self): + """get() -> return TestClass's associated value. + + >>> x = _TestClass(-42) + >>> print x.get() + -42 + """ + + return self.val + +__test__ = {"_TestClass": _TestClass, + "string": r""" + Example of a string object, searched as-is. + >>> x = 1; y = 2 + >>> x + y, x * y + (3, 2) + """ + } + +def _test(): + import doctest + return doctest.testmod(doctest) + +if __name__ == "__main__": + _test() diff --git a/doxyfile b/doxyfile new file mode 100644 index 00000000..5b7bbd25 --- /dev/null +++ b/doxyfile @@ -0,0 +1,708 @@ +# Doxyfile 1.2.2 + +# This file describes the settings to be used by doxygen for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# General configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = py_cpp + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 0.9 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = ./docs + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Dutch, French, Italian, Czech, Swedish, German, Finnish, Japanese, +# Korean, Hungarian, Spanish, Romanian, Russian, Croatian, Polish, and +# Portuguese. + +OUTPUT_LANGUAGE = English + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these class will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. It is allowed to use relative paths in the argument list. + +STRIP_FROM_PATH = + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a class diagram (in Html and LaTeX) for classes with base or +# super classes. Setting the tag to NO turns the diagrams off. + +CLASS_DIAGRAMS = YES + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower case letters. If set to YES upper case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# users are adviced to set this option to NO. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the JAVADOC_AUTOBRIEF tag is set to YES (the default) then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the Javadoc-style will +# behave just like the Qt-style comments. + +JAVADOC_AUTOBRIEF = YES + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# reimplements. + +INHERIT_DOCS = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# The ENABLE_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. + +WARN_FORMAT = "$file:$line: $text" + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = c:/prj/manhattan/py_cpp + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +FILE_PATTERNS = *.cpp *.h *.py *.hpp + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = example1.cpp test_extclass.py extclass_demo.cpp extclass_demo.h + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. + +INPUT_FILTER = + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimised for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = YES + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using a WORD or other. +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assigments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. Warning: This feature +# is still experimental and very incomplete. + +GENERATE_XML = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_PREDEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_PREDEF_ONLY tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tagfiles. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = c:\dragon\ntbin\perl.exe + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the ENABLE_PREPROCESSING, INCLUDE_GRAPH, and HAVE_DOT tags are set to +# YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other +# documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, INCLUDED_BY_GRAPH, and HAVE_DOT tags are set to +# YES then doxygen will generate a graph for each documented header file showing +# the documented files that directly or indirectly include this file + +INCLUDED_BY_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found on the path. + +DOT_PATH = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO + +# The CGI_NAME tag should be the name of the CGI script that +# starts the search engine (doxysearch) with the correct parameters. +# A script with this name will be generated by doxygen. + +CGI_NAME = search.cgi + +# The CGI_URL tag should be the absolute URL to the directory where the +# cgi binaries are located. See the documentation of your http daemon for +# details. + +CGI_URL = + +# The DOC_URL tag should be the absolute URL to the directory where the +# documentation is located. If left blank the absolute path to the +# documentation, with file:// prepended to it, will be used. + +DOC_URL = + +# The DOC_ABSPATH tag should be the absolute path to the directory where the +# documentation is located. If left blank the directory on the local machine +# will be used. + +DOC_ABSPATH = + +# The BIN_ABSPATH tag must point to the directory where the doxysearch binary +# is installed. + +BIN_ABSPATH = C:\tools\doxygen-1.2.2\bin + +# The EXT_DOC_PATHS tag can be used to specify one or more paths to +# documentation generated for other projects. This allows doxysearch to search +# the documentation for these projects as well. + +EXT_DOC_PATHS = diff --git a/errors.h b/errors.h new file mode 100644 index 00000000..8dee0fd0 --- /dev/null +++ b/errors.h @@ -0,0 +1,30 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef ERRORS_DWA052500_H_ +# define ERRORS_DWA052500_H_ + +namespace py { + +struct ErrorAlreadySet {}; +struct ArgumentError : ErrorAlreadySet {}; + +// Handles exceptions caught just before returning to Python code. +void handle_exception(); + +template +T* expect_non_null(T* x) +{ + if (x == 0) + throw ErrorAlreadySet(); + return x; +} + +} // namespace py + +#endif // ERRORS_DWA052500_H_ diff --git a/example1.bdf b/example1.bdf new file mode 100644 index 00000000..72fdc159 --- /dev/null +++ b/example1.bdf @@ -0,0 +1,4 @@ +TargetName=example1 +TargetType=dll +SourceFiles=example1.cpp +Libs=py_cpp python utils diff --git a/example1.cpp b/example1.cpp new file mode 100644 index 00000000..11f8f683 --- /dev/null +++ b/example1.cpp @@ -0,0 +1,54 @@ +#include + +namespace hello { + class world + { + public: + world(int) {} + ~world() {} + const char* get() const { return "hi, world"; } + }; + + size_t length(const world& x) { return strlen(x.get()); } +} + +#include + +// Python requires an exported function called init in every +// extension module. This is where we build the module contents. +extern "C" +#ifdef _WIN32 +__declspec(dllexport) +#endif +void inithello() +{ + try + { + // create an object representing this extension module + py::Module hello("hello"); + + // Create the Python type object for our extension class + py::ClassWrapper world_class(hello, "world"); + + // Add the __init__ function + world_class.def(py::Constructor()); + // Add a regular member function + world_class.def(&hello::world::get, "get"); + + // Add a regular function to the module + hello.def(hello::length, "length"); + } + catch(...) + { + py::handle_exception(); // Deal with the exception for Python + } +} + +// Win32 DLL boilerplate +#if defined(_WIN32) +#include +extern "C" BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID) +{ + return 1; +} +#endif // _WIN32 diff --git a/extclass.cpp b/extclass.cpp new file mode 100644 index 00000000..a1734760 --- /dev/null +++ b/extclass.cpp @@ -0,0 +1,343 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#include "extclass.h" +#include + +namespace py { + +ExtensionInstance* get_extension_instance(PyObject* p) +{ + // The object's type will just be some Class object, + // but if its meta-type is right, then it is an ExtensionInstance. + if (p->ob_type->ob_type != extension_meta_class()) + { + PyErr_SetString(PyExc_TypeError, p->ob_type->tp_name); + throw py::ArgumentError(); + } + return static_cast(p); +} + +void +ExtensionInstance::add_implementation(std::auto_ptr holder) +{ + for (WrappedObjects::const_iterator p = m_wrapped_objects.begin(); + p != m_wrapped_objects.end(); ++p) + { + if (typeid(*holder) == typeid(**p)) + { + PyErr_SetString(PyExc_RuntimeError, "Base class already initialized"); + throw ErrorAlreadySet(); + } + } + m_wrapped_objects.push_back(holder.release()); +} + +ExtensionInstance::ExtensionInstance(PyTypeObject* class_) + : Instance(class_) +{ +} + +ExtensionInstance::~ExtensionInstance() +{ + for (WrappedObjects::const_iterator p = m_wrapped_objects.begin(), + finish = m_wrapped_objects.end(); + p != finish; ++p) + { + delete *p; + } +} + +MetaClass* extension_meta_class() +{ + static MetaClass result; + return &result; +} + +typedef Class ExtClass; + +bool is_subclass(const ExtClass* derived, + const PyObject* possible_base) +{ + + Tuple bases = derived->bases(); + + for (std::size_t i = 0, size = bases.size(); i < size; ++i) + { + const PyObject* base = bases[i].get(); + + if (base == possible_base) + return true; + + if (base->ob_type == extension_meta_class()) + { + const ExtClass* base_class = Downcast(base); + if (is_subclass(base_class, possible_base)) + return true; + } + } + return false; +} + +// Return true iff instance is an instance of target_class +bool is_instance(ExtensionInstance* instance, + Class* target_class) +{ + if (instance->ob_type == target_class) + return true; + else + { + return is_subclass( + Downcast >(instance->ob_type).get(), + as_object(target_class)); + } +} + +void two_string_error(PyObject* exception_object, const char* format, const char* s1, const char* s2) +{ + char buffer[256]; + std::size_t format_length = PY_CSTD_::strlen(format); + std::size_t length1 = PY_CSTD_::strlen(s1); + std::size_t length2 = PY_CSTD_::strlen(s2); + + std::size_t additional_length = length1 + length2; + if (additional_length + format_length > format_length - 1) + { + std::size_t difference = sizeof(buffer) - 1 - additional_length; + length1 -= difference / 2; + additional_length -= difference / 2; + } + + sprintf(buffer, format, length1, s1, length2, s2); + + PyErr_SetString(exception_object, buffer); + if (exception_object == PyExc_TypeError) + throw ArgumentError(); + else + throw ErrorAlreadySet(); +} + +// This is called when an attempt has been made to convert the given instance to +// a C++ type for which it doesn't have any instance data. In that case, either +// the instance was not derived from the target_class, or the appropriate +// __init__ function wasn't called to initialize the instance data of the target class. +void report_missing_instance_data( + ExtensionInstance* instance, // The object being converted + Class* target_class, // the extension class of the C++ type + const std::type_info& target_typeid, // The typeid of the C++ type + bool target_is_ptr) +{ + char buffer[256]; + if (is_instance(instance, target_class)) + { + if (target_is_ptr) + { + two_string_error(PyExc_RuntimeError, + "Object of extension class '%.*s' does not wrap <%.*s>.", + instance->ob_type->tp_name, target_typeid.name()); + } + else + { + const char message[] = "__init__ function for extension class '%.*s' was never called."; + sprintf(buffer, message, sizeof(buffer) - sizeof(message) - 1, + target_class->tp_name); + } + PyErr_SetString(PyExc_RuntimeError, buffer); + } + else if (target_class == 0) + { + const char message[] = "Cannot convert to <%.*s>; its Python class was never created or has been deleted."; + sprintf(buffer, message, sizeof(buffer) - sizeof(message) - 1, target_typeid.name()); + PyErr_SetString(PyExc_RuntimeError, buffer); + } + else + { + two_string_error(PyExc_TypeError, "extension class '%.*s' is not derived from '%.*s'.", + instance->ob_type->tp_name, target_class->tp_name); + } +} + +void report_missing_instance_data( + ExtensionInstance* instance, // The object being converted + Class* target_class, // the extension class of the C++ type + const std::type_info& target_typeid) // The typeid of the C++ type +{ + report_missing_instance_data(instance, target_class, target_typeid, false); +} + +void report_missing_ptr_data( + ExtensionInstance* instance, // The object being converted + Class* target_class, // the extension class of the C++ type + const std::type_info& target_typeid) // The typeid of the C++ type +{ + report_missing_instance_data(instance, target_class, target_typeid, true); +} + +void report_missing_class_object(const std::type_info& info) +{ + char buffer[256]; + const char message[] = "Cannot convert <%.*s> to python; its Python class was never created or has been deleted."; + sprintf(buffer, message, sizeof(buffer) - sizeof(message) - 1, info.name()); + PyErr_SetString(PyExc_RuntimeError, buffer); + throw ErrorAlreadySet(); +} + +void report_released_smart_pointer(const std::type_info& info) +{ + char buffer[256]; + const char message[] = "Converting from python, pointer or smart pointer to <%.*s> is NULL."; + sprintf(buffer, message, sizeof(buffer) - sizeof(message) - 1, info.name()); + PyErr_SetString(PyExc_RuntimeError, buffer); + throw ArgumentError(); +} + +ReadOnlySetattrFunction::ReadOnlySetattrFunction(const char* name) + : m_name(name) +{ +} + +PyObject* ReadOnlySetattrFunction::do_call(PyObject* /*args*/, PyObject* /*keywords*/) const +{ + PyErr_SetObject(PyExc_AttributeError, ("'" + m_name + "' attribute is read-only").get()); + return 0; +} + +const char* ReadOnlySetattrFunction::description() const +{ + return "uncallable"; +} + +ExtensionClassBase::ExtensionClassBase(const char* name) + : Class( + extension_meta_class(), String(name), Tuple(), Dict()) +{ +} + +void ExtensionClassBase::add_method(Function* method, const char* name) +{ + add_method(PyPtr(method), name); +} + +void ExtensionClassBase::add_method(PyPtr method, const char* name) +{ + // If we have created a special Python base class which wraps C++ classes + // derived from T, the method should really be added there. Target will be + // that Python class object. + Class* target = (bases().size() == 0) + ? this + : Downcast >(bases()[0].get()).get(); + + // Add the attribute to the computed target + Function::add_to_namespace(method, name, target->dict().get()); + + // If it is a special member function it should be enabled both here and there. + enable_named_method(this, name); +} + +void ExtensionClassBase::add_default_method(Function* method, const char* name) +{ + add_default_method(PyPtr(method), name); +} + +// A rather complicated thing is going on here in order to make a very specific +// class of cases work. When wrapping the following C++: +// +// struct Base { +// Base(); // will be constructed from Python +// virtual int f() const // might be called from C++ +// { return 1; } // default implementation +// }; +// +// struct Derived : Base { +// int f() const { return 0; } // overridden in C++ +// }; +// +// boost::shared_ptr factory(bool selector) { +// return boost::shared_ptr(selector ? new Base : new Derived); +// } +// +// Normally we would use the same Python ExtensionClass object to represent both +// Base and boost::shared_ptr, since they have essentially the same +// operations (see the comment on InstanceHolder in extclass_pygen.h for +// details). If there was no need to override Base::f() in Python, that would +// work fine. In this case, since f() is virtual, the programmer must provide a +// subclass of Base which calls back into Python: +// +// struct BaseCallback : Base { +// BaseCallback(PyObject* self) : m_self(self) {} +// int f() const { return py::Callback::call_method(m_self, "f"); } +// static int default_f(const Base* self) const { self->Base::f(); } +// }; +// +// default_f() is what gets registered under the name "f" in the "Base" +// ExtensionClass' attribute dict. When C++ calls f() on a wrapped instance of +// Base, we call back into Python to find the "f" attribute, which calls +// default_f() (unless it has been overridden in Python) and in turn the default +// implementation (Base::f()) is called. +// +// Now consider what happens when the Python programmer writes +// >>> factory(0).f() +// +// The shared_ptr which is created on the C++ side is converted by +// to_python() into a Python instance of the "Base" ExtensionClass. Then Python +// looks up the "f" attribute, and finds the wrapper for default_f(), which it +// calls. That calls Base::f(), returning 1. What we really wanted was a call to +// Derived::f(), returning 0. +// +// In this case we actually need a different Python ExtensionClass to represent +// C++ subclasses of Base. When the first default method implementation is added +// to an ExtensionClass, we "push" all of the non-default methods up into a +// newly-created base class of the "Base" ExtensionClass, called +// "Base_base". "Base's" attribute dict contains only default method +// implementations. +// +// A Python call to factory() then results in an object of class "Base_base", +// whose "f" method is bound to Base::f() - since this is a virtual function +// pointer, the member function actually called is determined by the +// most-derived class that implements f(). +// +// A Python call to Base() results in an object of class "Base" wrapping a +// BaseCallback object, whose "f" method is bound to BaseCallback::default_f() +// ...which calls Base::f() explicitly. +void ExtensionClassBase::add_default_method(PyPtr method, const char* name) +{ + if (bases().size() == 0) + { + Class* new_base + = new Class( + extension_meta_class(), this->name() + String("_base"), Tuple(), + dict()); + + add_base(Ptr(as_object(new_base))); + + // We have transferred everything in our dict into the base class, so + // clear our dict now. It will henceforth contain only default method + // implementations. + dict() = Dict(); + } + Function::add_to_namespace(method, name, dict().get()); +} + +void ExtensionClassBase::add_constructor_object(Function* init_function) +{ + add_method(init_function, "__init__"); +} + +void ExtensionClassBase::add_setter_method(Function* setter_, const char* name) +{ + PyPtr setter(setter_); + add_method(setter, (detail::setattr_string() + name + "__").c_str()); +} + +void ExtensionClassBase::add_getter_method(Function* getter_, const char* name) +{ + PyPtr getter(getter_); + add_method(getter, (detail::getattr_string() + name + "__").c_str()); +} + +} // namespace py diff --git a/extclass.h b/extclass.h new file mode 100644 index 00000000..3a3be054 --- /dev/null +++ b/extclass.h @@ -0,0 +1,439 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef EXTENSION_CLASS_DWA052000_H_ +# define EXTENSION_CLASS_DWA052000_H_ + +# include "pyconfig.h" +# include "subclass.h" +# include +# include "none.h" +# include "objects.h" +# include "functions.h" +# include +# include "init_function.h" +# include +# include + +namespace py { + +// forward declarations +class ExtensionInstance; +template class InstanceHolder; +template class InstanceValueHolder; +template class InstancePtrHolder; + +MetaClass* extension_meta_class(); +ExtensionInstance* get_extension_instance(PyObject* p); +void report_missing_instance_data(ExtensionInstance*, Class*, const std::type_info&); +void report_missing_ptr_data(ExtensionInstance*, Class*, const std::type_info&); +void report_missing_class_object(const std::type_info&); +void report_released_smart_pointer(const std::type_info&); + +template +struct ExtensionClassFromPython +{ +}; + +template +T* check_non_null(T* p) +{ + if (p == 0) + report_released_smart_pointer(typeid(T)); + return p; +} + +template class HeldInstance; + +class ExtensionClassBase : public Class +{ + public: + ExtensionClassBase(const char* name); + protected: + void add_method(PyPtr method, const char* name); + void add_default_method(PyPtr method, const char* name); + void add_method(Function* method, const char* name); + void add_default_method(Function* method, const char* name); + + void add_constructor_object(Function*); + void add_setter_method(Function*, const char* name); + void add_getter_method(Function*, const char* name); +}; + +template +class ClassRegistry +{ + public: + static Class* class_object() + { return static_class_object; } + static void register_class(py::Class*); + static void unregister_class(py::Class*); + private: + static py::Class* static_class_object; +}; + +#ifdef PY_NO_INLINE_FRIENDS_IN_NAMESPACE // back to global namespace for this GCC bug +} +#endif + +// This class' only job is to define from_python and to_python converters for T +// and U. T is the class the user really intends to wrap. U is a class derived +// from T with some virtual function overriding boilerplate, or if there are no +// virtual functions, U = HeldInstance. +template > +class PyExtensionClassConverters +{ + public: +#ifdef BOOST_MSVC + // Convert return values of type T to python objects. What happens if T is + // not copyable? Apparently there is no problem with g++ or MSVC unless this + // is actually used. With a conforming compiler we will have a problem. + friend PyObject* to_python(const T& x) + { + py::PyPtr result(create_instance(false)); + result->add_implementation( + std::auto_ptr( + new py::InstanceValueHolder(result.get(), x))); + return result.release(); + } +#else + friend py::Type py_holder_type(const T&) + { return py::Type(); } +#endif + + PyExtensionClassConverters() {} + + // Convert to T* + friend T* from_python(PyObject* obj, py::Type) + { + // Downcast to an ExtensionInstance, then find the actual T + py::ExtensionInstance* self = py::get_extension_instance(obj); + typedef std::vector::const_iterator Iterator; + for (Iterator p = self->wrapped_objects().begin(); + p != self->wrapped_objects().end(); ++p) + { + py::InstanceHolder* held = dynamic_cast*>(*p); + if (held != 0) + return held->target(); + } + py::report_missing_instance_data(self, py::ClassRegistry::class_object(), typeid(T)); + throw py::ArgumentError(); + } + + // Convert to PtrType, where PtrType can be dereferenced to obtain a T. + template + static PtrType& ptr_from_python(PyObject* obj, py::Type) + { + // Downcast to an ExtensionInstance, then find the actual T + py::ExtensionInstance* self = py::get_extension_instance(obj); + typedef std::vector::const_iterator Iterator; + for (Iterator p = self->wrapped_objects().begin(); + p != self->wrapped_objects().end(); ++p) + { + py::InstancePtrHolder* held = + dynamic_cast*>(*p); + if (held != 0) + return held->ptr(); + } + py::report_missing_ptr_data(self, py::ClassRegistry::class_object(), typeid(T)); + throw py::ArgumentError(); + } + + template + static PyObject* ptr_to_python(PtrType x) + { + py::PyPtr result(create_instance(true)); + result->add_implementation( + std::auto_ptr( + new py::InstancePtrHolder(x))); + return result.release(); + } + + static py::PyPtr create_instance(bool seek_base) + { + if (py::ClassRegistry::class_object() == 0) + py::report_missing_class_object(typeid(T)); + + py::Class* class_ + = seek_base && py::ClassRegistry::class_object()->bases().size() > 0 + ? py::Downcast >( + py::ClassRegistry::class_object()->bases()[0].get()).get() + : py::ClassRegistry::class_object(); + + return py::PyPtr(new py::ExtensionInstance(class_)); + } + + + // Convert to const T* + friend const T* from_python(PyObject* p, py::Type) + { return from_python(p, py::Type()); } + + // Convert to T& + friend T& from_python(PyObject* p, py::Type) + { return *py::check_non_null(from_python(p, py::Type())); } + + // Convert to const T& + friend const T& from_python(PyObject* p, py::Type) + { return from_python(p, py::Type()); } + + // Convert to T + friend const T& from_python(PyObject* p, py::Type) + { return from_python(p, py::Type()); } + + friend std::auto_ptr& from_python(PyObject* p, py::Type&>) + { return ptr_from_python(p, py::Type >()); } + + friend std::auto_ptr& from_python(PyObject* p, py::Type >) + { return ptr_from_python(p, py::Type >()); } + + friend const std::auto_ptr& from_python(PyObject* p, py::Type&>) + { return ptr_from_python(p, py::Type >()); } + + friend PyObject* to_python(std::auto_ptr x) + { return ptr_to_python(x); } + + friend boost::shared_ptr& from_python(PyObject* p, py::Type&>) + { return ptr_from_python(p, py::Type >()); } + + friend boost::shared_ptr& from_python(PyObject* p, py::Type >) + { return ptr_from_python(p, py::Type >()); } + + friend const boost::shared_ptr& from_python(PyObject* p, py::Type&>) + { return ptr_from_python(p, py::Type >()); } + + friend PyObject* to_python(boost::shared_ptr x) + { return ptr_to_python(x); } +}; + +#ifndef BOOST_MSVC +template +py::InstanceHolderBase* +py_copy_to_new_value_holder(py::ExtensionInstance* p, const T& x, py::Type) +{ + return new py::InstanceValueHolder(p, x); +} + +template +PyObject* to_python(const T& x) +{ + py::PyPtr result( + PyExtensionClassConverters::create_instance(false)); + result->add_implementation( + std::auto_ptr( + py_copy_to_new_value_holder(result.get(), x, py_holder_type(x)))); + return result.release(); +} +#endif + +#ifdef PY_NO_INLINE_FRIENDS_IN_NAMESPACE // back from global namespace for this GCC bug +namespace py { +using ::PyExtensionClassConverters; +#endif + +template class InstanceHolder; + +class ReadOnlySetattrFunction : public Function +{ + public: + ReadOnlySetattrFunction(const char* name); + PyObject* do_call(PyObject* args, PyObject* keywords) const; + const char* description() const; + private: + String m_name; +}; + +// An easy way to make an extension base class which wraps T. Note that Python +// subclasses of this class will simply be Class objects. +// +// U should be a class derived from T which overrides virtual functions with +// boilerplate code to call back into Python. See extclass_demo.h for examples. +// +// U is optional, but you won't be able to override any member functions in +// Python which are called from C++ if you don't supply it. If you just want to +// be able to use T in python without overriding member functions, you can omit +// U. +template > +class ExtensionClass + : public PyExtensionClassConverters, // This generates the to_python/from_python functions + public ExtensionClassBase +{ + public: + typedef T WrappedType; + typedef U CallbackType; + + // Construct with a name that comes from typeid(T).name(). The name only + // affects the objects of this class are represented through repr() + ExtensionClass(); + + // Construct with the given name. The name only affects the objects of this + // class are represented through repr() + ExtensionClass(const char* name); + + ~ExtensionClass(); + + // define constructors + template + void def(Constructor) + { add_constructor( // the following incantation builds a Signature1, + prepend(Type::Id(), // Signature2, ... constructor. It _should_ all + prepend(Type::Id(), // get optimized away. Just another workaround + prepend(Type::Id(), // for the lack of partial specialization in MSVC + prepend(Type::Id(), + prepend(Type::Id(), + Signature0())))))); + } + + // define member functions. In fact this works for free functions, too - + // they act like static member functions, or if they start with the + // appropriate self argument (as a pointer), they can be used just like + // ordinary member functions -- just like Python! + template + void def(Fn fn, const char* name) + { + this->add_method(new_wrapped_function(fn), name); + } + + // Define a virtual member function with a default implementation. + // default_fn should be a function which provides the default implementation. + // Be careful that default_fn does not in fact call fn virtually! + template + void def(Fn fn, const char* name, DefaultFn default_fn) + { + this->add_default_method(new_wrapped_function(default_fn), name); + this->add_method(new_wrapped_function(fn), name); + } + + // Provide a function which implements x., reading from the given + // member (pm) of the T instance + template + void def_getter(MemberType T::*pm, const char* name) + { + this->add_getter_method(new GetterFunction(pm), name); + } + + // Provide a function which implements assignment to x., writing to + // the given member (pm) of the T instance + template + void def_setter(MemberType T::*pm, const char* name) + { + this->add_setter_method(new SetterFunction(pm), name); + } + + // Expose the given member (pm) of the T instance as a read-only attribute + template + void def_readonly(MemberType T::*pm, const char* name) + { + this->add_setter_method(new ReadOnlySetattrFunction(name), name); + this->def_getter(pm, name); + } + + // Expose the given member (pm) of the T instance as a read/write attribute + template + void def_read_write(MemberType T::*pm, const char* name) + { + this->def_getter(pm, name); + this->def_setter(pm, name); + } + + private: + typedef InstanceValueHolder Holder; + + template + void add_constructor(Signature sig) + { + this->add_constructor_object(InitFunction::create(sig)); + } +}; + +#include "extclass_pygen.h" + +template +class InstancePtrHolder : public InstanceHolder +{ + public: + HeldType* target() { return &*m_ptr; } + PtrType& ptr() { return m_ptr; } + + InstancePtrHolder(PtrType ptr) : m_ptr(ptr) {} + private: + PtrType m_ptr; +}; + +class ExtensionInstance : public Instance +{ + public: + ExtensionInstance(PyTypeObject* class_); + ~ExtensionInstance(); + + void add_implementation(std::auto_ptr holder); + + typedef std::vector WrappedObjects; + const WrappedObjects& wrapped_objects() const + { return m_wrapped_objects; } + private: + WrappedObjects m_wrapped_objects; +}; + +// +// Template function implementations +// + +template +ExtensionClass::ExtensionClass() + : ExtensionClassBase(typeid(T).name()) +{ + ClassRegistry::register_class(this); +} + +template +ExtensionClass::ExtensionClass(const char* name) + : ExtensionClassBase(name) +{ + ClassRegistry::register_class(this); +} + +template +ExtensionClass::~ExtensionClass() +{ + ClassRegistry::unregister_class(this); +} + +#ifdef PY_NO_INLINE_FRIENDS_IN_NAMESPACE // Back to the global namespace for this GCC bug +} +#endif + +// +// Static data member declaration. +// +#ifdef PY_NO_INLINE_FRIENDS_IN_NAMESPACE // Back from the global namespace for this GCC bug +namespace py { +#endif + +template +Class* ClassRegistry::static_class_object; + +template +inline void ClassRegistry::register_class(Class* p) +{ + // You're not expected to create more than one of these! + assert(static_class_object == 0); + static_class_object = p; +} + +template +inline void ClassRegistry::unregister_class(Class* p) +{ + // The user should be destroying the same object they created. + assert(static_class_object == p); + (void)p; // unused in shipping version + static_class_object = 0; +} + +} // namespace py + +#endif // EXTENSION_CLASS_DWA052000_H_ diff --git a/extclass_d.cpp b/extclass_d.cpp new file mode 100644 index 00000000..6963607e --- /dev/null +++ b/extclass_d.cpp @@ -0,0 +1,3 @@ +#define DEBUG_PYTHON +#include "extclass.cpp" + diff --git a/extclass_demo.cpp b/extclass_demo.cpp new file mode 100644 index 00000000..dd6c13f5 --- /dev/null +++ b/extclass_demo.cpp @@ -0,0 +1,382 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +#include "extclass_demo.h" +#include "class_wrapper.h" +#include // used for portability on broken compilers + +namespace extclass_demo { + +FooCallback::FooCallback(PyObject* self, int x) + : Foo(x), m_self(self) +{ +} + +int FooCallback::add_len(const char* x) const +{ + // Try to call the "add_len" method on the corresponding Python object. + return py::Callback::call_method(m_self, "add_len", x); +} + +// A function which Python can call in case bar is not overridden from +// Python. In true Python style, we use a free function taking an initial self +// parameter. This function anywhere needn't be a static member of the callback +// class. The only reason to do it this way is that Foo::add_len is private, and +// FooCallback is already a friend of Foo. +int FooCallback::default_add_len(const Foo* self, const char* x) +{ + // Don't forget the Foo:: qualification, or you'll get an infinite + // recursion! + return self->Foo::add_len(x); +} + +// Since Foo::pure() is pure virtual, we don't need a corresponding +// default_pure(). A failure to override it in Python will result in an +// exception at runtime when pure() is called. +const char* FooCallback::pure() const +{ + return py::Callback::call_method(m_self, "pure"); +} + +// The initializer for ExtensionClass is entirely optional. It +// only affects the way that instances of this class _print_ in Python. If you +// need an absolutely predictable name for the type, use the +// initializer. Otherwise, C++ will generate an implementation-dependent +// representation of the type name, usually something like "class +// extclass_demo::Foo". I've supplied it here in part so that I can write +// doctests that exactly anticipate the generated error messages. +Foo::PythonClass::PythonClass() + : py::ExtensionClass("Foo") // optional +{ + def(py::Constructor()); + def(&Foo::mumble, "mumble"); + def(&Foo::set, "set"); + def(&Foo::call_pure, "call_pure"); + def(&Foo::call_add_len, "call_add_len"); + + // This is the way we add a virtual function that has a default implementation. + def(&Foo::add_len, "add_len", &FooCallback::default_add_len); + + // Since pure() is pure virtual, we are leaving it undefined. +} + +BarPythonClass::BarPythonClass() + : py::ExtensionClass("Bar") // optional +{ + def(py::Constructor()); + def(&Bar::first, "first"); + def(&Bar::second, "second"); + def(&Bar::pass_baz, "pass_baz"); +} + +BazPythonClass::BazPythonClass() + : py::ExtensionClass("Baz") // optional +{ + def(py::Constructor()); + def(&Baz::pass_bar, "pass_bar"); + def(&Baz::clone, "clone"); + def(&Baz::create_foo, "create_foo"); + def(&Baz::get_foo_value, "get_foo_value"); + def(&Baz::eat_baz, "eat_baz"); +} + +StringMapPythonClass::StringMapPythonClass() + : py::ExtensionClass("StringMap") +{ + def(py::Constructor()); + def(&StringMap::size, "__len__"); + def(&get_item, "__getitem__"); + def(&set_item, "__setitem__"); + def(&del_item, "__delitem__"); +} + +int get_first(const IntPair& p) +{ + return p.first; +} + +void set_first(IntPair& p, int value) +{ + p.first = -value; +} + +void del_first(const IntPair&) +{ + PyErr_SetString(PyExc_AttributeError, "first can't be deleted!"); + throw py::ErrorAlreadySet(); +} + +IntPairPythonClass::IntPairPythonClass() + : py::ExtensionClass("IntPair") +{ + def(py::Constructor()); + def(&getattr, "__getattr__"); + def(&setattr, "__setattr__"); + def(&delattr, "__delattr__"); + def(&get_first, "__getattr__first__"); + def(&set_first, "__setattr__first__"); + def(&del_first, "__delattr__first__"); +} + +void IntPairPythonClass::setattr(IntPair& x, const std::string& name, int value) +{ + if (name == "second") + { + x.second = value; + } + else + { + PyErr_SetString(PyExc_AttributeError, name.c_str()); + throw py::ErrorAlreadySet(); + } +} + +void IntPairPythonClass::delattr(IntPair&, const char*) +{ + PyErr_SetString(PyExc_AttributeError, "Attributes can't be deleted!"); + throw py::ErrorAlreadySet(); +} + +int IntPairPythonClass::getattr(const IntPair& p, const std::string& s) +{ + if (s == "second") + { + return p.second; + } + else + { + PyErr_SetString(PyExc_AttributeError, s.c_str()); + throw py::ErrorAlreadySet(); + } +#if defined(__MWERKS__) && __MWERKS__ <= 0x6000 + return 0; +#endif +} + +namespace { namespace file_local { +void throw_key_error_if_end(const StringMap& m, StringMap::const_iterator p, std::size_t key) +{ + if (p == m.end()) + { + PyErr_SetObject(PyExc_KeyError, py::converters::to_python(key)); + throw py::ErrorAlreadySet(); + } +} +}} // namespace ::file_local + +const std::string& StringMapPythonClass::get_item(const StringMap& m, std::size_t key) +{ + const StringMap::const_iterator p = m.find(key); + file_local::throw_key_error_if_end(m, p, key); + return p->second; +} + +void StringMapPythonClass::set_item(StringMap& m, std::size_t key, const std::string& value) +{ + m[key] = value; +} + +void StringMapPythonClass::del_item(StringMap& m, std::size_t key) +{ + const StringMap::iterator p = m.find(key); + file_local::throw_key_error_if_end(m, p, key); + m.erase(p); +} + +// +// Show that polymorphism can work. a DerivedFromFoo object will be passed to +// Python in a smart pointer object. +// +class DerivedFromFoo : public Foo +{ +public: + DerivedFromFoo(int x) : Foo(x) {} + +private: + const char* pure() const + { return "this was never pure!"; } + + int add_len(const char*) const + { return 1000; } +}; + +// +// function implementations +// + +IntPair make_pair(int x, int y) +{ + return std::make_pair(x, y); +} + +const char* Foo::mumble() +{ + return "mumble"; +} + +void Foo::set(long x) +{ + m_x = x; +} + +const char* Foo::call_pure() +{ + return this->pure(); +} + +int Foo::call_add_len(const char* s) const +{ + return this->add_len(s); +} + +int Foo::add_len(const char* s) const // sum the held value and the length of s +{ + return PY_CSTD_::strlen(s) + static_cast(m_x); +} + +boost::shared_ptr Baz::create_foo() +{ + return boost::shared_ptr(new DerivedFromFoo(0)); +} + +// We can accept smart pointer parameters +int Baz::get_foo_value(boost::shared_ptr foo) +{ + return foo->call_add_len(""); +} + +// Show what happens in python when we take ownership from an auto_ptr +void Baz::eat_baz(std::auto_ptr baz) +{ + baz->clone(); // just do something to show that it is valid. +} + +Baz Bar::pass_baz(Baz b) +{ + return b; +} + +std::string stringpair_repr(const StringPair& sp) +{ + return "('" + sp.first + "', '" + sp.second + "')"; +} + +int stringpair_compare(const StringPair& sp1, const StringPair& sp2) +{ + return sp1 < sp2 ? -1 : sp2 < sp1 ? 1 : 0; +} + +py::String range_str(const Range& r) +{ + char buf[200]; + sprintf(buf, "(%d, %d)", r.m_start, r.m_finish); + return py::String(buf); +} + +int range_compare(const Range& r1, const Range& r2) +{ + int d = r1.m_start - r2.m_start; + if (d == 0) + d = r1.m_finish - r2.m_finish; + return d; +} + +long range_hash(const Range& r) +{ + return r.m_start * 123 + r.m_finish; +} + +void init_module(py::Module& m) +{ + m.add(new Foo::PythonClass); + m.add(new BarPythonClass); + m.add(new BazPythonClass); + m.add(new StringMapPythonClass); + m.add(new IntPairPythonClass); + m.def(make_pair, "make_pair"); + m.add(new CompareIntPairPythonClass); + + py::ClassWrapper string_pair(m, "StringPair"); + string_pair.def(py::Constructor()); + string_pair.def_readonly(&StringPair::first, "first"); + string_pair.def_read_write(&StringPair::second, "second"); + string_pair.def(&stringpair_repr, "__repr__"); + string_pair.def(&stringpair_compare, "__cmp__"); + m.def(first_string, "first_string"); + m.def(second_string, "second_string"); + + py::ClassWrapper range(m, "Range"); + range.def(py::Constructor()); + range.def(py::Constructor()); + range.def((void (Range::*)(std::size_t))&Range::length, "__len__"); + range.def((std::size_t (Range::*)() const)&Range::length, "__len__"); + range.def(&Range::operator[], "__getitem__"); + range.def(&Range::slice, "__getslice__"); + range.def(&range_str, "__str__"); + range.def(&range_compare, "__cmp__"); + range.def(&range_hash, "__hash__"); + range.def_readonly(&Range::m_start, "start"); + range.def_readonly(&Range::m_finish, "finish"); +} + +void init_module() +{ + py::Module demo("demo"); + init_module(demo); + + // Just for giggles, add a raw metaclass. + demo.add(new py::MetaClass); +} + +extern "C" +#ifdef _WIN32 +__declspec(dllexport) +#endif +void initdemo() +{ + try { + extclass_demo::init_module(); + } + catch(...) { + py::handle_exception(); + } // Need a way to report other errors here +} + +CompareIntPairPythonClass::CompareIntPairPythonClass() + : py::ExtensionClass("CompareIntPair") +{ + def(py::Constructor()); + def(&CompareIntPair::operator(), "__call__"); +} + +} // namespace extclass_demo + + +#if defined(_WIN32) +# include +extern "C" BOOL WINAPI DllMain ( HINSTANCE hInst, DWORD wDataSeg, LPVOID lpvReserved ); + +# ifdef PY_COMPILER_IS_MSVC +extern "C" void structured_exception_translator(unsigned int, EXCEPTION_POINTERS*) +{ + throw; +} +# endif + +BOOL WINAPI DllMain( + HINSTANCE, //hDllInst + DWORD fdwReason, + LPVOID // lpvReserved + ) +{ +# ifdef PY_COMPILER_IS_MSVC + _set_se_translator(structured_exception_translator); +#endif + return 1; + (void)fdwReason; // warning suppression. +} +#endif // _WIN32 diff --git a/extclass_demo.h b/extclass_demo.h new file mode 100644 index 00000000..f4cd6a88 --- /dev/null +++ b/extclass_demo.h @@ -0,0 +1,231 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef EXTCLASS_DEMO_DWA052200_H_ +# define EXTCLASS_DEMO_DWA052200_H_ +// +// Example code demonstrating extension class usage +// + +# include "extclass.h" +# include "callback.h" +# include +# include +# include +# include +# include +# include + +namespace extclass_demo { + +// +// example: Foo, Bar, and Baz are C++ classes we want to wrap. +// + +class Foo // prohibit copying, proving that it doesn't choke + : boost::noncopyable // our generation of to_python(). +{ + public: // constructor/destructor + Foo(int x) : m_x(x) {} + virtual ~Foo() {} + + public: // non-virtual functions + const char* mumble(); // mumble something + void set(long x); // change the held value + + // These two call virtual functions + const char* call_pure(); // call a pure virtual fuction + int call_add_len(const char* s) const; // virtual function with a default implementation + + private: + // by default, sum the held value and the length of s + virtual int add_len(const char* s) const; + + // Derived classes can do whatever they want here, but they must do something! + virtual const char* pure() const = 0; + + public: // friend declarations + // If you have private virtual functions such as add_len which you want to + // override in Python and have default implementations, they must be + // accessible by the thing making the def() call on the ExtensionClass (in + // this case, the nested PythonClass itself), and by the C++ derived class + // which is used to cause the Python callbacks (in this case, + // FooCallback). See the definition of FooCallback::add_len() + struct PythonClass; + friend struct PythonClass; + friend class FooCallback; + + private: + int m_x; // the held value +}; + +// +// Bar and Baz have mutually-recursive type conversion dependencies (see +// pass_xxx functions). I've done this to prove that it doesn't cause a +// problem for Python class definitions, which happen later. +// +// Bar and Baz functions are only virtual to increase the likelihood of a crash +// if I inadvertently use a pointer to garbage memory (a likely thing to test +// for considering the amount of type casting needed to translate to and from +// Python). +struct Baz; +struct Bar +{ + Bar(int x, int y) : m_first(x), m_second(y) {} + virtual int first() const { return m_first; } + virtual int second() const { return m_second; } + virtual Baz pass_baz(Baz x); + + int m_first, m_second; +}; + +struct Baz +{ + virtual Bar pass_bar(const Bar& x) { return x; } + + // We can return smart pointers + virtual std::auto_ptr clone() { return std::auto_ptr(new Baz(*this)); } + + // This illustrates creating a polymorphic derived class of Foo + virtual boost::shared_ptr create_foo(); + + // We can accept smart pointer parameters + virtual int get_foo_value(boost::shared_ptr); + + // Show what happens in python when we take ownership from an auto_ptr + virtual void eat_baz(std::auto_ptr); +}; + +typedef std::map StringMap; +typedef std::pair IntPair; + +IntPair make_pair(int, int); + +typedef std::less CompareIntPair; +typedef std::pair StringPair; + +inline std::string first_string(const StringPair& x) +{ + return x.first; +} + +inline std::string second_string(const StringPair& x) +{ + return x.second; +} + +struct Range +{ + Range(int x) + : m_start(x), m_finish(x) {} + + Range(int start, int finish) + : m_start(start), m_finish(finish) {} + + std::size_t length() const + { return m_finish < m_start ? 0 : m_finish - m_start; } + + void length(std::size_t new_length) + { m_finish = m_start + new_length; } + + int operator[](std::size_t n) + { return m_start + n; } + + Range slice(std::size_t start, std::size_t end) + { + if (start > length()) + start = length(); + if (end > length()) + end = length(); + return Range(m_start + start, m_start + end); + } + + int m_start, m_finish; +}; + +//////////////////////////////////////////////////////////////////////// +// // +// Begin wrapping code. Usually this would live in a separate header. // +// // +//////////////////////////////////////////////////////////////////////// + +// Since Foo has virtual functions which we want overriden in Python, we must +// derive FooCallback. +class FooCallback : public Foo +{ + public: + // Note the additional constructor parameter "self", which is needed to + // allow function overriding from Python. + FooCallback(PyObject* self, int x); + + friend struct PythonClass; // give it access to the functions below + + private: // implementations of Foo virtual functions that are overridable in python. + int add_len(const char* x) const; + + // A function which Python can call in case bar is not overridden from + // Python. In true Python style, we use a free function taking an initial + // self parameter. You can put this function anywhere; it needn't be a + // static member of the wrapping class. + static int default_add_len(const Foo* self, const char* x); + + // Since Foo::pure() is pure virtual, we don't need a corresponding + // default_pure(). A failure to override it in Python will result in an + // exception at runtime when pure() is called. + const char* pure() const; + + private: // Required boilerplate if functions will be overridden + PyObject* m_self; // No, we don't want a py::Ptr here, or we'd get an ownership cycle. +}; + +// Define the Python base class +struct Foo::PythonClass : py::ExtensionClass { PythonClass(); }; + +// No virtual functions on Bar or Baz which are actually supposed to behave +// virtually from C++, so we'll rely on the library to define a wrapper for +// us. Even so, Python Class types for each type we're wrapping should be +// _defined_ here in a header where they can be seen by other extension class +// definitions, since it is the definition of the py::ExtensionClass<> that +// causes to_python/from_python conversion functions to be generated. +struct BarPythonClass : py::ExtensionClass { BarPythonClass(); }; +struct BazPythonClass : py::ExtensionClass { BazPythonClass(); }; + +struct StringMapPythonClass + : py::ExtensionClass +{ + StringMapPythonClass(); + + // These static functions implement the right argument protocols for + // implementing the Python "special member functions" for mapping on + // StringMap. Could just as easily be global functions. + static const std::string& get_item(const StringMap& m, std::size_t key); + static void set_item(StringMap& m, std::size_t key, const std::string& value); + static void del_item(StringMap& m, std::size_t key); +}; + +struct IntPairPythonClass + : py::ExtensionClass +{ + IntPairPythonClass(); + + // The following could just as well be a free function; it implements the + // getattr functionality for IntPair. + static int getattr(const IntPair&, const std::string& s); + static void setattr(IntPair&, const std::string& name, int value); + static void delattr(IntPair&, const char* name); +}; + +struct CompareIntPairPythonClass + : py::ExtensionClass +{ + CompareIntPairPythonClass(); +}; + +} // namespace extclass_demo + +#endif // EXTCLASS_DEMO_DWA052200_H_ diff --git a/extclass_demo.py b/extclass_demo.py new file mode 100644 index 00000000..df882bed --- /dev/null +++ b/extclass_demo.py @@ -0,0 +1,23 @@ +# A stand-in for the real extension module. We're just using this one to +# simulate it while we have all the parts linked together into cpp.dll + +# using "import*-safe" imports +import sys +_sys = sys +del sys + +import os +_os = os +del os + +_savecwd = _os.getcwd() + +if len(_sys.argv) > 1: + _os.chdir(_sys.argv[1]) + +try: + from cpp import * +finally: + if len(_sys.argv) > 1: + _os.chdir(_savecwd) + diff --git a/extclass_demo_d.cpp b/extclass_demo_d.cpp new file mode 100644 index 00000000..d0201cda --- /dev/null +++ b/extclass_demo_d.cpp @@ -0,0 +1,2 @@ +#define DEBUG_PYTHON +#include "extclass_demo.cpp" diff --git a/extclass_pygen.h b/extclass_pygen.h new file mode 100644 index 00000000..a63fe12d --- /dev/null +++ b/extclass_pygen.h @@ -0,0 +1,80 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file automatically generated for 5-argument constructors by +// gen_extclass.py + +#ifndef EXTCLASS_PYGEN_DWA070900_H_ +# define EXTCLASS_PYGEN_DWA070900_H_ + + +// A simple wrapper over a T which allows us to use ExtensionClass with a +// single template parameter only. See ExtensionClass, above. +template +class HeldInstance : public T +{ + // There are no member functions: we want to avoid inadvertently overriding + // any virtual functions in T. +public: + HeldInstance(PyObject* p) : T(), m_self(p) {} + template + HeldInstance(PyObject* p, const A1& a1) : T(a1), m_self(p) {} + template + HeldInstance(PyObject* p, const A1& a1, const A2& a2) : T(a1, a2), m_self(p) {} + template + HeldInstance(PyObject* p, const A1& a1, const A2& a2, const A3& a3) : T(a1, a2, a3), m_self(p) {} + template + HeldInstance(PyObject* p, const A1& a1, const A2& a2, const A3& a3, const A4& a4) : T(a1, a2, a3, a4), m_self(p) {} + template + HeldInstance(PyObject* p, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) : T(a1, a2, a3, a4, a5), m_self(p) {} +protected: + PyObject* m_self; // Not really needed; doesn't really hurt. +}; + +class InstanceHolderBase +{ +public: + virtual ~InstanceHolderBase() {} +}; + +template +class InstanceHolder : public InstanceHolderBase +{ +public: + virtual Held *target() = 0; +}; + +template +class InstanceValueHolder : public InstanceHolder +{ +public: + Held* target() { return &m_held; } + Wrapper* value_target() { return &m_held; } + + InstanceValueHolder(ExtensionInstance* p) : + m_held(p) {} + template + InstanceValueHolder(ExtensionInstance* p, const A1& a1) : + m_held(p, a1) {} + template + InstanceValueHolder(ExtensionInstance* p, const A1& a1, const A2& a2) : + m_held(p, a1, a2) {} + template + InstanceValueHolder(ExtensionInstance* p, const A1& a1, const A2& a2, const A3& a3) : + m_held(p, a1, a2, a3) {} + template + InstanceValueHolder(ExtensionInstance* p, const A1& a1, const A2& a2, const A3& a3, const A4& a4) : + m_held(p, a1, a2, a3, a4) {} + template + InstanceValueHolder(ExtensionInstance* p, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) : + m_held(p, a1, a2, a3, a4, a5) {} +private: + Wrapper m_held; +}; + +#endif diff --git a/functions.cpp b/functions.cpp new file mode 100644 index 00000000..7d1f2c8e --- /dev/null +++ b/functions.cpp @@ -0,0 +1,161 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#include "functions.h" +#include "newtypes.h" +#include "singleton.h" +#include "objects.h" +#include "errors.h" + +namespace py { + +struct Function::TypeObject : + Singleton > > +{ + TypeObject() : SingletonBase(&PyType_Type) {} +}; + + +void Function::add_to_namespace(PyPtr new_function, const char* name, PyObject* dict) +{ + Dict d(Ptr(dict, Ptr::borrowed)); + String key(name); + + Ptr existing_object = d.get_item(key.reference()); + if (existing_object.get() == 0) + { + d[key] = Ptr(new_function.get(), Ptr::borrowed); + } + else + { + if (existing_object->ob_type == TypeObject::singleton()) + { + Function* f = static_cast(existing_object.get()); + while (f->m_overloads.get() != 0) + f = f->m_overloads.get(); + f->m_overloads = new_function; + } + else + { + PyErr_SetObject(PyExc_RuntimeError, + (String("Attempt to overload ") + name + + " failed. The existing attribute has type " + + existing_object->ob_type->tp_name).get()); + throw ErrorAlreadySet(); + } + } +} + +Function::Function() + : PythonObject(TypeObject::singleton()) +{ +} + +PyObject* Function::call(PyObject* args, PyObject* keywords) const +{ + for (const Function* f = this; f != 0; f = f->m_overloads.get()) + { + PyErr_Clear(); + try + { + PyObject* const result = f->do_call(args, keywords); + if (result != 0) + return result; + } + catch(const ArgumentError&) + { + } + } + + if (m_overloads.get() == 0) + return 0; + + PyErr_Clear(); + String message("No overloaded functions match ("); + Tuple arguments(Ptr(args, Ptr::borrowed)); + for (std::size_t i = 0; i < arguments.size(); ++i) + { + if (i != 0) + message += ", "; + message += arguments[i]->ob_type->tp_name; + } + + message += "). Candidates are:\n"; + for (const Function* f1 = this; f1 != 0; f1 = f1->m_overloads.get()) + { + if (f1 != this) + message += "\n"; + message += f1->description(); + } + + PyErr_SetObject(PyExc_TypeError, message.get()); + return 0; +} + +Ptr BoundFunction::create(Ptr target, Ptr fn) +{ + BoundFunction* result = free_list; + if (result != 0) + { + free_list = result->m_free_list_link; + result->m_target = target; + result->m_unbound_function = fn; + } + else + { + result = new BoundFunction(target, fn); + } + return Ptr(result, Ptr::new_ref); +} + +struct BoundFunction::TypeObject : + Singleton > > +{ + TypeObject() : SingletonBase(&PyType_Type) {} + +private: // TypeObject hook override + void dealloc(BoundFunction*) const; +}; + +BoundFunction::BoundFunction(Ptr target, Ptr fn) + : PythonObject(TypeObject::singleton()), + m_target(target), + m_unbound_function(fn), + m_free_list_link(0) +{ +} + +PyObject* +BoundFunction::call(PyObject* args, PyObject* keywords) const +{ + // Build a new tuple which prepends the target to the arguments + Tuple tail_arguments(Ptr(args, Ptr::borrowed)); + Ptr all_arguments(PyTuple_New(tail_arguments.size() + 1)); + + PyTuple_SET_ITEM(all_arguments.get(), 0, m_target.get()); + Py_INCREF(m_target.get()); + for (std::size_t i = 0; i < tail_arguments.size(); ++i) + { + PyTuple_SET_ITEM(all_arguments.get(), i + 1, tail_arguments[i].get()); + Py_INCREF(tail_arguments[i].get()); + } + + return PyEval_CallObjectWithKeywords(m_unbound_function.get(), all_arguments.get(), keywords); +} + +void BoundFunction::TypeObject::dealloc(BoundFunction* instance) const +{ + instance->m_free_list_link = free_list; + free_list = instance; + instance->m_target.reset(); + instance->m_unbound_function.reset(); +} + +BoundFunction* BoundFunction::free_list; + +} diff --git a/functions.h b/functions.h new file mode 100644 index 00000000..7a2e2b66 --- /dev/null +++ b/functions.h @@ -0,0 +1,175 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef FUNCTIONS_DWA051400_H_ +# define FUNCTIONS_DWA051400_H_ + +# include "pyconfig.h" +# include "wrap_python.h" +# include "pyptr.h" +# include "signatures.h" +# include "caller.h" +# include +# include "objects.h" +# include "base_object.h" +# include + +namespace py { + +class Function : public PythonObject +{ + public: + Function(); + // Function objects are reasonably rare, so we guess we can afford a virtual table. + // This cuts down on the number of distinct type objects which need to be defined. + virtual ~Function() {} + + PyObject* call(PyObject* args, PyObject* keywords) const; + static void add_to_namespace(PyPtr f, const char* name, PyObject* dict); + + private: + virtual PyObject* do_call(PyObject* args, PyObject* keywords) const = 0; + virtual const char* description() const = 0; + private: + struct TypeObject; + private: + PyPtr m_overloads; +}; + +template +struct WrappedFunctionPointer : Function +{ + typedef F PtrFun; // pointer-to--function or pointer-to-member-function + + WrappedFunctionPointer(PtrFun pf) + : m_pf(pf) {} + + private: + PyObject* do_call(PyObject* args, PyObject* keywords) const + { return Caller::call(m_pf, args, keywords); } + + const char* description() const + { return typeid(F).name(); } + + private: + const PtrFun m_pf; +}; + +// A helper function for new_member_function(), below. Implements the core +// functionality once the return type has already been deduced. R is expected to +// be Type, where X is the actual return type of pmf. +template +Function* new_wrapped_function_aux(R, F pmf) +{ + // We can't just use "typename R::Type" below because MSVC (incorrectly) pukes. + typedef typename R::Type ReturnType; + return new WrappedFunctionPointer(pmf); +} + +// Create and return a new member function object wrapping the given +// pointer-to-member function +template +inline Function* new_wrapped_function(F pmf) +{ + // Deduce the return type and pass it off to the helper function above + return new_wrapped_function_aux(return_value(pmf), pmf); +} + +// A function with a bundled "bound target" object. This is what is produced by +// the expression a.b where a is an Instance or ExtensionInstance object and b +// is a callable object not found in the instance namespace but on its class or +// a base class. +class BoundFunction : public PythonObject +{ + public: + static Ptr create(Ptr target, Ptr fn); + + BoundFunction(Ptr target, Ptr fn); + PyObject* call(PyObject*args, PyObject* keywords) const; + + private: + struct TypeObject; + friend struct TypeObject; + + Ptr m_target; + Ptr m_unbound_function; + + private: // data members for allocation/deallocation optimization + BoundFunction* m_free_list_link; + + static BoundFunction* free_list; +}; + +// Special functions designed to access data members of a wrapped C++ object. +template +class GetterFunction : public Function +{ + public: + typedef MemberType ClassType::* PointerToMember; + + GetterFunction(PointerToMember pm) + : m_pm(pm) {} + + private: + PyObject* do_call(PyObject* args, PyObject* keywords) const; + + const char* description() const + { return typeid(MemberType (*)(const ClassType&)).name(); } + private: + PointerToMember m_pm; +}; + +template +class SetterFunction : public Function +{ + public: + typedef MemberType ClassType::* PointerToMember; + + SetterFunction(PointerToMember pm) + : m_pm(pm) {} + + private: + PyObject* do_call(PyObject* args, PyObject* keywords) const; + + const char* description() const + { return typeid(void (*)(const ClassType&, const MemberType&)).name(); } + private: + PointerToMember m_pm; +}; + +template +PyObject* GetterFunction::do_call( + PyObject* args, PyObject* /* keywords */) const +{ + PyObject* self; + if (!PyArg_ParseTuple(args, const_cast("O"), &self)) + return 0; + + return to_python( + from_python(self, Type())->*m_pm); +} + +template +PyObject* SetterFunction::do_call( + PyObject* args, PyObject* /* keywords */) const +{ + PyObject* self; + PyObject* value; + if (!PyArg_ParseTuple(args, const_cast("OO"), &self, &value)) + return 0; + + typedef typename boost::call_traits::const_reference ExtractType; + from_python(self, Type())->*m_pm + = from_python(value, Type()); + + return none(); +} + +} + +#endif // FUNCTIONS_DWA051400_H_ diff --git a/functions_d.cpp b/functions_d.cpp new file mode 100644 index 00000000..2522b504 --- /dev/null +++ b/functions_d.cpp @@ -0,0 +1,2 @@ +#define DEBUG_PYTHON +#include "functions.cpp" diff --git a/gcc.mak b/gcc.mak new file mode 100644 index 00000000..00cea9e5 --- /dev/null +++ b/gcc.mak @@ -0,0 +1,40 @@ +LIBSRC = \ + extclass.cpp \ + init_function.cpp \ + py.cpp \ + module.cpp \ + subclass.cpp \ + functions.cpp \ + newtypes.cpp \ + objects.cpp + +LIBOBJ = $(LIBSRC:.cpp=.o) +OBJ = $(LIBOBJ) extclass_demo.o + +INC = -I/home/koethe/include -I/home/koethe/C++/boost -I/home/koethe/python/include/python1.5 + +%.o: %.cpp + g++ -fPIC $(INC) -c $*.cpp + +%.d: %.cpp + @echo creating $@ + @set -e; g++ -M $(INC) -c $*.cpp \ + | sed 's/\($*\)\.o[ :]*/\1.o $@ : /g' > $@; \ + [ -s $@ ] || rm -f $@ + +demo: extclass_demo.o libpycpp.a + g++ -shared -o demomodule.so extclass_demo.o -L. -lpycpp + python test_extclass.py + +clean: + rm -rf *.o *.so *.a *.d *.pyc *.bak a.out + +libpycpp.a: $(LIBOBJ) + rm -f libpycpp.a + ar cq libpycpp.a $(LIBOBJ) + +DEP = $(OBJ:.o=.d) + +ifneq "$(MAKECMDGOALS)" "clean" +include $(DEP) +endif diff --git a/gen_all.py b/gen_all.py new file mode 100644 index 00000000..744e936b --- /dev/null +++ b/gen_all.py @@ -0,0 +1,26 @@ +from gen_callback import * +from gen_caller import * +from gen_init_function import * +from gen_signatures import * +from gen_singleton import * +from gen_extclass import * + +def gen_all(args): + open('callback.h', 'w').write(gen_callback(args)) + open('caller.h', 'w').write(gen_caller(args)) + open('init_function.h', 'w').write(gen_init_function(args)) + open('signatures.h', 'w').write(gen_signatures(args)) + open('singleton.h', 'w').write(gen_singleton(args)) + open('extclass_pygen.h', 'w').write(gen_extclass(args)) + +if __name__ == '__main__': + import sys + + if len(sys.argv) == 1: + args = 5 + else: + args = int(sys.argv[1]) + + print gen_all(args) + + diff --git a/gen_callback.py b/gen_callback.py new file mode 100644 index 00000000..44aa6a60 --- /dev/null +++ b/gen_callback.py @@ -0,0 +1,71 @@ +from gen_function import * +import string + +def gen_callback(args): + # A template for the call_method function which we're going to generate + call_method = '''%{ template <%(class A%n%:, %)> +%} static %1 call_method(PyObject* self, const char* name%(, const A%n& a%n%)) + { %2PyEval_CallMethod(self, const_cast(name), const_cast("(%(N%))")%(, to_python(a%n)%))%3; } + +''' + non_void = ('R', 'return from_python(expect_non_null(', '), Type())') + void = ('void', 'expect_and_absorb_non_null(', ')') + + return ( +"""// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file was generated for %d-argument python callbacks by gen_callback.py + +#ifndef CALLBACK_DWA_052100_H_ +# define CALLBACK_DWA_052100_H_ + +# include "pyconfig.h" +# include "py.h" + +namespace py { + +// Just like the above, except we decrement p's reference count instead of returning it. +void expect_and_absorb_non_null(PyObject* p); + +// Calling Python from C++ +template +struct Callback +{ +""" % args + + gen_functions(call_method, args, 'R', 'return from_python(expect_non_null(', '), Type())') + + +"""}; + +// This specialization wouldn't be needed, but MSVC6 doesn't correctly allow the following: +// void g(); +// void f() { return g(); } +template <> +struct Callback +{ +""" + + gen_functions(call_method, args, 'void', 'expect_and_absorb_non_null(', ')') + + +"""}; + +} // namespace py + +#endif // CALLBACK_DWA_052100_H_ +""") + +if __name__ == '__main__': + import sys + + if len(sys.argv) == 1: + args = 5 + else: + args = int(sys.argv[1]) + + print gen_callback(args) + + diff --git a/gen_caller.py b/gen_caller.py new file mode 100644 index 00000000..1f81b091 --- /dev/null +++ b/gen_caller.py @@ -0,0 +1,138 @@ +# (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +# distribute this software is granted provided this copyright notice appears +# in all copies. This software is provided "as is" without express or implied +# warranty, and with no claim as to its suitability for any purpose. +# +# The author gratefully acknowleges the support of Dragon Systems, Inc., in +# producing this work. + +from gen_function import * +import string + +header = '''// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file generated for %d-argument member functions and %d-argument free +// functions by gen_caller.py +''' + +body_sections = ( +''' +#ifndef CALLER_DWA05090_H_ +# define CALLER_DWA05090_H_ + +# include "pyconfig.h" +# include "wrap_python.h" +# include +# include "signatures.h" +# include "none.h" + +namespace py { + +// Calling C++ from Python +template +struct Caller +{ +''', +''' +''', +''' // Free functions +''', +'''}; + +template <> +struct Caller +{ +''', +''' +''', +''' + // Free functions +''', +'''}; + +} + +#endif +''') + +#' + +member_function = ''' template + static PyObject* call(%1 (T::*pmf)(%(A%n%:, %))%2, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; +%( PyObject* a%n; +%) if (!PyArg_ParseTuple(args, const_cast("O%(O%)"), &self%(, &a%n%))) + return 0; + T& target = from_python(self, Type()); + %3(target.*pmf)(%(from_python(a%n, Type())%:, + %))%4 + } + +''' + +free_function = '''%{ template <%(class A%n%:, %)> +%} static PyObject* call(%1 (*f)(%(A%n%:, %)), PyObject* args, PyObject* /* keywords */ ) { +%( PyObject* a%n; +%) if (!PyArg_ParseTuple(args, const_cast("%(O%)")%(, &a%n%))) + return 0; + %2f(%(from_python(a%n, Type())%:, + %))%3 + } + +''' + +def gen_caller(member_function_args, free_function_args = None): + if free_function_args is None: + free_function_args = member_function_args + + return_none = '''; + return none();''' + + return (header % (member_function_args, free_function_args) + + body_sections[0] + + gen_functions(member_function, member_function_args, + 'R', '', 'return to_python(', ');') + + body_sections[1] + + gen_functions(member_function, member_function_args, + 'R', ' const', 'return to_python(', ');') + + body_sections[2] + + + gen_functions(free_function, free_function_args, + 'R', 'return to_python(', ');') + + body_sections[3] + + # specialized part for void return values begins here + + gen_functions(member_function, member_function_args, + 'void', '', '', return_none) + + body_sections[4] + + gen_functions(member_function, member_function_args, + 'void', ' const', '', return_none) + + body_sections[5] + + + gen_functions(free_function, free_function_args, + 'void', '', return_none) + + body_sections[6] + ) + +if __name__ == '__main__': + import sys + + if len(sys.argv) == 1: + member_function_args = 5 + free_function_args = 6 + else: + member_function_args = int(sys.argv[1]) + if len(sys.argv) > 2: + free_function_args = int(sys.argv[2]) + else: + free_function_args = member_function_args + + print gen_caller(member_function_args, free_function_args) + + diff --git a/gen_extclass.py b/gen_extclass.py new file mode 100644 index 00000000..04c2c555 --- /dev/null +++ b/gen_extclass.py @@ -0,0 +1,81 @@ +from gen_function import * +import string + +def gen_extclass(args): + held_instance = """%{ + template <%(class A%n%:, %)>%} + HeldInstance(PyObject* p%(, const A%n%& a%n%)) : T(%(a%n%:, %)), m_self(p) {}""" + + instance_value_holder = """%{ + template <%(class A%n%:, %)>%} + InstanceValueHolder(ExtensionInstance* p%(, const A%n& a%n%)) : + m_held(p%(, a%n%)) {}""" + + return ( +"""// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file automatically generated for %d-argument constructors by +// gen_extclass.py + +#ifndef EXTCLASS_PYGEN_DWA070900_H_ +# define EXTCLASS_PYGEN_DWA070900_H_ + + +// A simple wrapper over a T which allows us to use ExtensionClass with a +// single template parameter only. See ExtensionClass, above. +template +class HeldInstance : public T +{ + // There are no member functions: we want to avoid inadvertently overriding + // any virtual functions in T. +public:""" % args + + gen_functions(held_instance, args) + + """ +protected: + PyObject* m_self; // Not really needed; doesn't really hurt. +}; + +class InstanceHolderBase +{ +public: + virtual ~InstanceHolderBase() {} +}; + +template +class InstanceHolder : public InstanceHolderBase +{ +public: + virtual Held *target() = 0; +}; + +template +class InstanceValueHolder : public InstanceHolder +{ +public: + Held* target() { return &m_held; } + Wrapper* value_target() { return &m_held; } +""" + + gen_functions(instance_value_holder, args) + + """ +private: + Wrapper m_held; +}; + +#endif +""") + +if __name__ == '__main__': + import sys + + if len(sys.argv) == 1: + args = 5 + else: + args = int(sys.argv[1]) + + print gen_extclass(args) diff --git a/gen_function.py b/gen_function.py new file mode 100644 index 00000000..b2b22ab8 --- /dev/null +++ b/gen_function.py @@ -0,0 +1,184 @@ +import string + +def _find(s, sub, start=0, end=None): + """Just like string.find, except it returns end or len(s) when not found. + """ + if end == None: + end = len(s) + + pos = string.find(s, sub, start, end) + if pos < 0: + return end + else: + return pos + +def _gen_common_key(key, n, args): + if len(key) > 0 and key in '123456789': + return str(args[int(key) - 1]) + elif key == 'x': + return str(n) + else: + return key + +def _gen_arg(template, n, args, delimiter = '%'): + result = '' + i = 0 + while i < len(template): # until the template is consumed + # consume everything up to the first delimiter + delimiter_pos = _find(template, delimiter, i) + result = result + template[i:delimiter_pos] + + # The start position of whatever comes after the delimiter+key + start = delimiter_pos + 2 + key = template[start - 1 : start] # the key character. If there were no + # delimiters left, key will be empty + + if key == 'n': + result = result + `n` + else: + result = result + _gen_common_key(key, n, args) + + i = start + + return result + +def gen_function(template, n, *args, **keywords): + r"""gen_function(template, n, [args...] ) -> string + + Generate a function declaration based on the given template. + + Sections of the template between '%(', '%)' pairs are repeated n times. If '%:' + appears in the middle, it denotes the beginning of a delimiter. + + Sections of the template between '%{', '%}' pairs are ommitted if n == 0. + + %n is transformed into the string representation of 1..n for each repetition + of n. + + %x, where x is a digit, is transformed into the corresponding additional + argument. + + for example, + + >>> gen_function('%1 abc(%(int a%n%:, %));%{ // all args are ints%}', 2, 'void') + 'void abc(int a1, int a2); // all args are ints' + >>> gen_function('%1 abc(%(int a%n%:, %));%{ // all args are ints%}', 0, 'x') + 'x abc();' + + + >>> template = ''' template + ... static PyObject* call( %1(T::*pmf)(%(A%n%:, %))%2, PyObject* args, PyObject* /* keywords */ ) { + ... PyObject* self; + ... %( PyObject* a%n; + ... %) if (!PyArg_ParseTuple(args, const_cast("O%(O%)"), &self%(, &a%n%))) + ... return 0; + ... T& target = from_python(self, Type()); + ... %3to_python((target.*pmf)(%( + ... from_python(a%n, Type())%:,%) + ... ));%4 + ... }''' + + >>> print gen_function(template, 0, 'R ', '', 'return ', '') + template + static PyObject* call( R (T::*pmf)(), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + if (!PyArg_ParseTuple(args, const_cast("O"), &self)) + return 0; + T& target = from_python(self, Type()); + return to_python((target.*pmf)( + )); + } + + >>> print gen_function(template, 2, 'R ', '', 'return ', '') + template + static PyObject* call( R (T::*pmf)(A1, A2), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + if (!PyArg_ParseTuple(args, const_cast("OOO"), &self, &a1, &a2)) + return 0; + T& target = from_python(self, Type()); + return to_python((target.*pmf)( + from_python(a1, Type()), + from_python(a2, Type()) + )); + } + + >>> print gen_function(template, 3, 'void ', ' const', '', '\n'+8*' ' + 'return none();') + template + static PyObject* call( void (T::*pmf)(A1, A2, A3) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + if (!PyArg_ParseTuple(args, const_cast("OOOO"), &self, &a1, &a2, &a3)) + return 0; + T& target = from_python(self, Type()); + to_python((target.*pmf)( + from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()) + )); + return none(); + } +""" + delimiter = keywords.get('delimiter', '%') + result = '' + i = 0 + while i < len(template): # until the template is consumed + # consume everything up to the first delimiter + delimiter_pos = _find(template, delimiter, i) + result = result + template[i:delimiter_pos] + + # The start position of whatever comes after the delimiter+key + start = delimiter_pos + 2 + key = template[start - 1 : start] # the key character. If there were no + # delimiters left, key will be empty + + pairs = { '(':')', '{':'}' } + + if key in pairs.keys(): + end = string.find(template, delimiter + pairs[key], start) + assert end >= 0, "Matching '" + delimiter + pairs[key] +"' not found!" + delimiter_pos = end + + if key == '{': + if n > 0: + result = result + gen_function(template[start:end], n, args, delimiter) + else: + separator_pos = _find(template, delimiter + ':', start, end) + separator = template[separator_pos+2 : end] + + for x in range(1, n + 1): + result = result + _gen_arg(template[start:separator_pos], x, args, + delimiter) + if x != n: + result = result + separator + + else: + result = result + _gen_common_key(key, n, args) + + i = delimiter_pos + 2 + + return result + +def gen_functions(template, n, *args): + r"""gen_functions(template, n, [args...]) -> string + + Call gen_function repeatedly with from 0..n and the given optional + arguments. + + >>> print gen_functions('%1 abc(%(int a%n%:, %));%{ // all args are ints%}\n', 2, 'void'), + void abc(); + void abc(int a1); // all args are ints + void abc(int a1, int a2); // all args are ints + + """ + result = '' + for x in range(n + 1): + result = result + apply(gen_function, (template, x) + args) + return result + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/gen_init_function.py b/gen_init_function.py new file mode 100644 index 00000000..3dd66aa9 --- /dev/null +++ b/gen_init_function.py @@ -0,0 +1,82 @@ +from gen_function import * +import string + +def gen_init_function(args): + + return ( +"""// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file was generated for %d-argument constructors by gen_init_function.py + +#ifndef INIT_FUNCTION_DWA052000_H_ +# define INIT_FUNCTION_DWA052000_H_ + +# include "pyconfig.h" +# include "functions.h" +# include "signatures.h" +# include + +namespace py { + +class ExtensionInstance; +class InstanceHolderBase; + +class Init; +""" + + gen_functions('template struct Init%x;\n', args) + + """ +template +struct InitFunction +{ +""" + gen_functions("""%{ + template <%(class A%n%:, %)> +%} static Init* create(Signature%x%{<%(A%n%:, %)>%}) + { return new Init%x; } +""", args)+"""}; + +class Init : public Function +{ +private: // override Function hook + PyObject* do_call(PyObject* args, PyObject* keywords) const; +private: + virtual InstanceHolderBase* create_holder(ExtensionInstance* self, PyObject* tail_args, PyObject* keywords) const = 0; +}; +""" + gen_functions(""" + +template +struct Init%x : Init +{ + virtual InstanceHolderBase* create_holder(ExtensionInstance* self, PyObject* args, PyObject* /*keywords*/) const + { + %(PyObject* a%n; + %)if (!PyArg_ParseTuple(args, const_cast("%(O%)")%(, &a%n%))) + throw ArgumentError(); + return new T(self%(, + from_python(a%n, Type())%) + ); + } + const char* description() const + { return typeid(void (*)(%(A%n%:, %))).name(); } +};""", args) + """ + +} + +#endif // INIT_FUNCTION_DWA052000_H_ +""") + +if __name__ == '__main__': + import sys + + if len(sys.argv) == 1: + args = 5 + else: + args = int(sys.argv[1]) + + print gen_init_function(args) + diff --git a/gen_signatures.py b/gen_signatures.py new file mode 100644 index 00000000..d9395171 --- /dev/null +++ b/gen_signatures.py @@ -0,0 +1,152 @@ +from gen_function import * +import string + +def gen_struct_signatures(args): + result = '' + for n in range(args, -1, -1): + result = ( + result + gen_function("""%{template <%(class T%n%:, %)> +%}struct Signature%x {}; + +""", n) +# + ((n == args) and [""] or +# [gen_function(""" +# template +# static inline Signature%1 prepend(Type) +# { return Signature%1(); }""", +# n, (str(n+1),)) +# ] +# )[0] +# +# + ((n != 0) and [""] or +# [""" +# // This one terminates the chain. Prepending Void to the head of a Void +# // signature results in a Void signature again. +# static inline Signature0 prepend(Void) { return Signature0(); }"""] +# )[0] +# + """ +#}; +# +#""" + + ((n == args) and [""] or + [gen_function( +"""template <%(class T%n%, %)class X> +inline Signature%1 prepend(Type, Signature%x%{<%(T%n%:, %)>%}) + { return Signature%1(); } + +""", n, str(n+1)) + ] + )[0] + ) + return result + +def gen_signatures(args): + return ( +"""// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file automatically generated by gen_signatures.py for %d arguments. +#ifndef SIGNATURES_DWA050900_H_ +# define SIGNATURES_DWA050900_H_ + +# include "pyconfig.h" + +namespace py { + +// A stand-in for the built-in void. This one can be passed to functions and +// (under MSVC, which has a bug, be used as a default template type parameter). +struct Void {}; + +// An envelope in which type information can be delivered for the purposes +// of selecting an overloaded from_python() function. This is needed to work +// around MSVC's lack of partial specialiation/ordering. Where normally we'd +// want to form a function call like void f(), We instead pass +// Type as one of the function parameters to select a particular +// overload. +// +// The Id typedef helps us deal with the lack of partial ordering by generating +// unique types for constructor signatures. In general, Type::Id is Type, +// but Type::Id is just Void. +template +struct Type +{ + typedef Type Id; +}; + +template <> +struct Type +{ + typedef Void Id; +}; + +// These basically encapsulate a chain of types, , used to make the syntax of +// add(Constructor()) work. We need to produce a unique type for each number +// of non-default parameters to Constructor<>. Q: why not use a recursive +// formulation for infinite extensibility? A: MSVC6 seems to choke on constructs +// that involve recursive template nesting. +// +// Signature chaining +""" % args + + gen_struct_signatures(args) + + """ +// This one terminates the chain. Prepending Void to the head of a Void +// signature results in a Void signature again. +inline Signature0 prepend(Void, Signature0) { return Signature0(); } +""" + + gen_function(""" +template <%(class A%n% = Void%:, %)> +struct Constructor +{ +}; +""", args) + + """ +// Return value extraction: + +// This is just another little envelope for carrying a typedef (see Type, +// above). I could have re-used Type, but that has a very specific purpose. I +// thought this would be clearer. +template +struct ReturnValue { typedef T Type; }; + +// free functions""" + + gen_functions(""" +template +ReturnValue return_value(R (*)(%(A%n%:, %))) { return ReturnValue(); } +""", args) + + + +""" +// TODO(?): handle 'const void' + +// member functions""" + + gen_functions(""" +template +ReturnValue return_value(R (T::*)(%(A%n%:, %))) { return ReturnValue(); } +""", args) + + + gen_functions(""" +template +ReturnValue return_value(R (T::*)(%(A%n%:, %)) const) { return ReturnValue(); } +""", args) + + + """ +} + +#endif +""") + +if __name__ == '__main__': + import sys + + if len(sys.argv) == 1: + args = 5 + else: + args = int(sys.argv[1]) + + print gen_signatures(args) + diff --git a/gen_singleton.py b/gen_singleton.py new file mode 100644 index 00000000..ed2e97d6 --- /dev/null +++ b/gen_singleton.py @@ -0,0 +1,58 @@ +from gen_function import * +import string + +def gen_singleton(args): + return ( +"""// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef SINGLETON_DWA051900_H_ +# define SINGLETON_DWA051900_H_ + +# include "pyconfig.h" + +namespace py { + +struct Empty {}; +template +struct Singleton : Base +{ + typedef Singleton SingletonBase; // Convenience type for derived class constructors + + static Derived* singleton(); + + // Pass-through constructors +""" + + gen_functions("""%{ + template <%(class A%n%:, %)> +%} Singleton(%(const A%n& a%n%:, %)) : Base(%(a%n%:, %)) {} +""", args) + + """ +}; + +template +Derived* Singleton::singleton() +{ + static Derived x; + return &x; +} + +} + +#endif +""") + +if __name__ == '__main__': + import sys + + if len(sys.argv) == 1: + args = 5 + else: + args = int(sys.argv[1]) + + print gen_singleton(args) diff --git a/init_function.cpp b/init_function.cpp new file mode 100644 index 00000000..33add6ce --- /dev/null +++ b/init_function.cpp @@ -0,0 +1,36 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#include "init_function.h" +#include "objects.h" +#include "extclass.h" +#include + +namespace py { + +PyObject* Init::do_call(PyObject* args_, PyObject* keywords) const +{ + Tuple args(Ptr(args_, Ptr::borrowed)); + if (args[0]->ob_type->ob_type != extension_meta_class()) + { + PyErr_SetString(PyExc_TypeError, "argument 1 to __init__ must be an ExtensionInstance"); + return 0; + } + + ExtensionInstance *self = static_cast(args[0].get()); + + Tuple ctor_args = args.slice(1, args.size()); + + std::auto_ptr result( + create_holder(self, ctor_args.get(), keywords)); + + self->add_implementation(result); + return none(); +} + +} diff --git a/init_function.h b/init_function.h new file mode 100644 index 00000000..c22c627f --- /dev/null +++ b/init_function.h @@ -0,0 +1,184 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file was generated for %d-argument constructors by gen_init_function.py + +#ifndef INIT_FUNCTION_DWA052000_H_ +# define INIT_FUNCTION_DWA052000_H_ + +# include "pyconfig.h" +# include "functions.h" +# include "signatures.h" +# include + +namespace py { + +class ExtensionInstance; +class InstanceHolderBase; + +class Init; +template struct Init0; +template struct Init1; +template struct Init2; +template struct Init3; +template struct Init4; +template struct Init5; + +template +struct InitFunction +{ + static Init* create(Signature0) + { return new Init0; } + + template + static Init* create(Signature1) + { return new Init1; } + + template + static Init* create(Signature2) + { return new Init2; } + + template + static Init* create(Signature3) + { return new Init3; } + + template + static Init* create(Signature4) + { return new Init4; } + + template + static Init* create(Signature5) + { return new Init5; } +}; + +class Init : public Function +{ +private: // override Function hook + PyObject* do_call(PyObject* args, PyObject* keywords) const; +private: + virtual InstanceHolderBase* create_holder(ExtensionInstance* self, PyObject* tail_args, PyObject* keywords) const = 0; +}; + + +template +struct Init0 : Init +{ + virtual InstanceHolderBase* create_holder(ExtensionInstance* self, PyObject* args, PyObject* /*keywords*/) const + { + if (!PyArg_ParseTuple(args, const_cast(""))) + throw ArgumentError(); + return new T(self + ); + } + const char* description() const + { return typeid(void (*)()).name(); } +}; + +template +struct Init1 : Init +{ + virtual InstanceHolderBase* create_holder(ExtensionInstance* self, PyObject* args, PyObject* /*keywords*/) const + { + PyObject* a1; + if (!PyArg_ParseTuple(args, const_cast("O"), &a1)) + throw ArgumentError(); + return new T(self, + from_python(a1, Type()) + ); + } + const char* description() const + { return typeid(void (*)(A1)).name(); } +}; + +template +struct Init2 : Init +{ + virtual InstanceHolderBase* create_holder(ExtensionInstance* self, PyObject* args, PyObject* /*keywords*/) const + { + PyObject* a1; + PyObject* a2; + if (!PyArg_ParseTuple(args, const_cast("OO"), &a1, &a2)) + throw ArgumentError(); + return new T(self, + from_python(a1, Type()), + from_python(a2, Type()) + ); + } + const char* description() const + { return typeid(void (*)(A1, A2)).name(); } +}; + +template +struct Init3 : Init +{ + virtual InstanceHolderBase* create_holder(ExtensionInstance* self, PyObject* args, PyObject* /*keywords*/) const + { + PyObject* a1; + PyObject* a2; + PyObject* a3; + if (!PyArg_ParseTuple(args, const_cast("OOO"), &a1, &a2, &a3)) + throw ArgumentError(); + return new T(self, + from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()) + ); + } + const char* description() const + { return typeid(void (*)(A1, A2, A3)).name(); } +}; + +template +struct Init4 : Init +{ + virtual InstanceHolderBase* create_holder(ExtensionInstance* self, PyObject* args, PyObject* /*keywords*/) const + { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + if (!PyArg_ParseTuple(args, const_cast("OOOO"), &a1, &a2, &a3, &a4)) + throw ArgumentError(); + return new T(self, + from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()), + from_python(a4, Type()) + ); + } + const char* description() const + { return typeid(void (*)(A1, A2, A3, A4)).name(); } +}; + +template +struct Init5 : Init +{ + virtual InstanceHolderBase* create_holder(ExtensionInstance* self, PyObject* args, PyObject* /*keywords*/) const + { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + if (!PyArg_ParseTuple(args, const_cast("OOOOO"), &a1, &a2, &a3, &a4, &a5)) + throw ArgumentError(); + return new T(self, + from_python(a1, Type()), + from_python(a2, Type()), + from_python(a3, Type()), + from_python(a4, Type()), + from_python(a5, Type()) + ); + } + const char* description() const + { return typeid(void (*)(A1, A2, A3, A4, A5)).name(); } +}; + +} + +#endif // INIT_FUNCTION_DWA052000_H_ diff --git a/init_function_d.cpp b/init_function_d.cpp new file mode 100644 index 00000000..fc40d2d2 --- /dev/null +++ b/init_function_d.cpp @@ -0,0 +1,2 @@ +#define DEBUG_PYTHON +#include "init_function.cpp" diff --git a/makefile b/makefile new file mode 100644 index 00000000..f86b11cf --- /dev/null +++ b/makefile @@ -0,0 +1,48 @@ +######################################################### +# +# makefile - Manhattan py_cpp makefile +# +# Author: David Abrahams +# Date: 04-May-2000 +# Copyright (c) 2000 Dragon Systems, Inc. +# +# Revision history at bottom. +# +######################################################### + +# Set up $(ROOT_DIR) +include ../make/userdirs.mak +include $(ROOT_DIR)/make/common.mak +include $(ROOT_DIR)/python/pythonhelp.mak +include $(ROOT_DIR)/utils/utilhelp.mak +include $(ROOT_DIR)/py_cpp/py_cpphelp.mak + +MAKEFILE_INCLUDES = $(PYTHON_INCLUDES) + +# Look in this directory, then the output subdirectory (BIN_DIR) for modules to load without qualification. +export PYTHONPATH := $(THISDIR)$(PYTHONPATH_SEP)$(THISDIR)/$(BIN_DIR)$(PYTHONPATH_SEP)$(PYTHONPATH) + +# In order to get the automatic dependency generation working correctly, it +# is necessary to list the source files here. +SRC_FILES = extclass.cpp init_function.cpp subclass.cpp functions.cpp module.cpp newtypes.cpp py.cpp objects.cpp +SRC_FILES += extclass_demo.cpp example1.cpp + +SRC_FILES += extclass_d.cpp init_function_d.cpp subclass_d.cpp functions_d.cpp module_d.cpp newtypes_d.cpp py_d.cpp objects_d.cpp +SRC_FILES += extclass_demo_d.cpp +LIBS=$(PYTHON_LIB) $(PYTHON_D_LIB) + +test : demo + $(PYTHON_EXE) test_extclass.py $(ARGS) + +test_d : demo_d + $(DEBUGGER) $(PYTHON_D_EXE) test_extclass.py $(ARGS) + +ifndef PYTHON_D_LIB +PYTHON_D_LIB = $(SPACE_CHAR) +endif + +-include py_cpp.mk1 +-include py_cpp_d.mk1 +-include demo.mk1 +-include demo_d.mk1 +-include example1.mk1 \ No newline at end of file diff --git a/module.cpp b/module.cpp new file mode 100644 index 00000000..d63c9cf6 --- /dev/null +++ b/module.cpp @@ -0,0 +1,39 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#include "module.h" + +namespace py { + +Module::Module(const char* name) + : m_module(Py_InitModule(const_cast(name), initial_methods)) +{ +} + +void +Module::add(Function* x, const char* name) +{ + PyPtr f(x); // First take possession of the object. + Function::add_to_namespace(f, name, PyModule_GetDict(m_module)); +} + +void Module::add(Ptr x, const char* name) +{ + PyObject* dictionary = PyModule_GetDict( m_module ); + PyDict_SetItemString(dictionary, const_cast(name), x.get()); +}; + +void Module::add(PyTypeObject* x, const char* name /*= 0*/) +{ + this->add(Ptr(as_object(x), Ptr::new_ref), + name ? name : x->tp_name); +} + +PyMethodDef Module::initial_methods[] = { { 0, 0, 0, 0 } }; + +} diff --git a/module.h b/module.h new file mode 100644 index 00000000..29b98fdf --- /dev/null +++ b/module.h @@ -0,0 +1,43 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef MODULE_DWA051000_H_ +# define MODULE_DWA051000_H_ + +# include "pyconfig.h" +# include "pyptr.h" +# include "functions.h" + +namespace py { + +class ExtensionType; + +class Module +{ +public: + Module(const char* name); + + void add(Function* x, const char* name); + + void add(PyTypeObject* x, const char* name = 0); + + void add(Ptr x, const char*name); + + template + void def(Fn fn, const char* name) + { + add(new_wrapped_function(fn), name); + } + private: + PyObject* m_module; + static PyMethodDef initial_methods[1]; +}; + +} + +#endif diff --git a/module_d.cpp b/module_d.cpp new file mode 100644 index 00000000..a57ebca0 --- /dev/null +++ b/module_d.cpp @@ -0,0 +1,2 @@ +#define DEBUG_PYTHON +#include "module.cpp" diff --git a/newtypes.cpp b/newtypes.cpp new file mode 100644 index 00000000..3c169e9c --- /dev/null +++ b/newtypes.cpp @@ -0,0 +1,628 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#include "newtypes.h" +#include "pyptr.h" // for handle_exception() +#include "module.h" +#include "none.h" +#include +#include +#include +#include +#include "objects.h" + +namespace py { + +namespace detail { +UniquePodSet& UniquePodSet::instance() +{ + static UniquePodSet me; + return me; +} + +struct UniquePodSet::Compare +{ + bool operator()(const std::pair& x1, + const std::pair& x2) const + { + const std::ptrdiff_t n1 = x1.second - x1.first; + const std::ptrdiff_t n2 = x2.second - x2.first; + return n1 < n2 || n1 == n2 && PY_CSTD_::memcmp(x1.first, x2.first, n1) < 0; + } +}; + +const void* UniquePodSet::get_element(const void* buffer, std::size_t size) +{ + const Holder element(static_cast(buffer), + static_cast(buffer) + size); + + const Storage::iterator found + = std::lower_bound(m_storage.begin(), m_storage.end(), element, Compare()); + + if (found != m_storage.end() && !Compare()(element, *found)) + return found->first; + + std::size_t length = element.second - element.first; + char* base_address = new char[length]; + try { + PY_CSTD_::memcpy(base_address, element.first, length); + Holder new_element(base_address, base_address + length); + m_storage.insert(found, new_element); + } + catch(...) { + delete[] base_address; + throw; + } + return base_address; +} + +UniquePodSet::~UniquePodSet() +{ + for (Storage::const_iterator p = m_storage.begin(), finish = m_storage.end(); + p != finish; ++p) + { + delete[] const_cast(p->first); + } +} +} // namespace detail + +template +static MethodStruct* enable_method(const MethodStruct* base, MemberPtr p, Fn f) +{ + MethodStruct new_value; + + if (base != 0) + new_value = *base; + else + PY_CSTD_::memset(&new_value, 0, sizeof(PyMappingMethods)); + + new_value.*p = f; + + return const_cast(detail::UniquePodSet::instance().get(new_value)); +} + +// TODO: is there a problem with calling convention here, or can I really pass a +// pointer to a C++ linkage function as a C-linkage function pointer? The +// compilers seem to swallow it, but is it legal? Symantec C++ for Mac didn't +// behave this way, FWIW. +// Using C++ linkage allows us to keep the virtual function members of +// TypeObjectBase private and use friendship to get them called. + +extern "C" { + +static PyObject* do_instance_repr(PyObject* instance) +{ + try + { + return static_cast(instance->ob_type) + ->instance_repr(instance); + } + catch(...) + { + handle_exception(); + return 0; + } +} + +static int do_instance_compare(PyObject* instance, PyObject* other) +{ + try + { + return static_cast(instance->ob_type) + ->instance_compare(instance, other); + } + catch(...) + { + handle_exception(); + return -1; + } +} + +static PyObject* do_instance_str(PyObject* instance) +{ + try + { + return static_cast(instance->ob_type) + ->instance_str(instance); + } + catch(...) + { + handle_exception(); + return 0; + } +} + +static long do_instance_hash(PyObject* instance) +{ + try + { + return static_cast(instance->ob_type) + ->instance_hash(instance); + } + catch(...) + { + handle_exception(); + return -1; + } +} + +static PyObject* do_instance_call(PyObject* instance, PyObject* args, PyObject* keywords) +{ + try + { + return static_cast(instance->ob_type) + ->instance_call(instance, args, keywords); + } + catch(...) + { + handle_exception(); + return 0; + } +} + +static void do_instance_dealloc(PyObject* instance) +{ + try + { + static_cast(instance->ob_type) + ->instance_dealloc(instance); + } + catch(...) + { + assert(!"exception during destruction!"); + handle_exception(); + } +} + +static PyObject* do_instance_getattr(PyObject* instance, char* name) +{ + try + { + return static_cast(instance->ob_type) + ->instance_getattr(instance, name); + } + catch(...) + { + handle_exception(); + return 0; + } +} + +static int do_instance_setattr(PyObject* instance, char* name, PyObject* value) +{ + try + { + return static_cast(instance->ob_type) + ->instance_setattr(instance, name, value); + } + catch(...) + { + handle_exception(); + return -1; + } +} + +static int do_instance_mp_length(PyObject* instance) +{ + try + { + const int outcome = + static_cast(instance->ob_type) + ->instance_mapping_length(instance); + + if (outcome < 0) + { + PyErr_SetString(PyExc_ValueError, "__len__() should return >= 0"); + return -1; + } + return outcome; + } + catch(...) + { + handle_exception(); + return -1; + } +} + +static int do_instance_sq_length(PyObject* instance) +{ + try + { + const int outcome = + static_cast(instance->ob_type) + ->instance_sequence_length(instance); + + if (outcome < 0) + { + PyErr_SetString(PyExc_ValueError, "__len__() should return >= 0"); + return -1; + } + return outcome; + } + catch(...) + { + handle_exception(); + return -1; + } +} + +static PyObject* do_instance_mp_subscript(PyObject* instance, PyObject* index) +{ + try + { + return static_cast(instance->ob_type) + ->instance_mapping_subscript(instance, index); + } + catch(...) + { + handle_exception(); + return 0; + } +} + +static PyObject* do_instance_sq_item(PyObject* instance, int index) +{ + try + { + const PyTypeObject* const type = instance->ob_type; + + // This is an extension to standard class behavior. If sequence_length + // is implemented and n >= sequence_length(), raise an IndexError. That + // keeps users from having to worry about raising it themselves + if (type->tp_as_sequence != 0 && type->tp_as_sequence->sq_length != 0 + && index >= type->tp_as_sequence->sq_length(instance)) + { + PyErr_SetString(PyExc_IndexError, type->tp_name); + return 0; + } + + return static_cast(instance->ob_type) + ->instance_sequence_item(instance, index); + } + catch(...) + { + handle_exception(); + return 0; + } +} + +static int do_instance_mp_ass_subscript(PyObject* instance, PyObject* index, PyObject* value) +{ + try + { + return static_cast(instance->ob_type) + ->instance_mapping_ass_subscript(instance, index, value); + } + catch(...) + { + handle_exception(); + return -1; + } +} + +static int do_instance_sq_ass_item(PyObject* instance, int index, PyObject* value) +{ + try + { + return static_cast(instance->ob_type) + ->instance_sequence_ass_item(instance, index, value); + } + catch(...) + { + handle_exception(); + return -1; + } +} + +static PyObject* do_instance_sq_concat(PyObject* instance, PyObject* other) +{ + try + { + return static_cast(instance->ob_type) + ->instance_sequence_concat(instance, other); + } + catch(...) + { + handle_exception(); + return 0; + } +} + +static PyObject* do_instance_sq_repeat(PyObject* instance, int n) +{ + try + { + return static_cast(instance->ob_type) + ->instance_sequence_repeat(instance, n); + } + catch(...) + { + handle_exception(); + return 0; + } +} + +static PyObject* do_instance_sq_slice( + PyObject* instance, int start, int finish) +{ + try + { + return static_cast(instance->ob_type) + ->instance_sequence_slice(instance, start, finish); + } + catch(...) + { + handle_exception(); + return 0; + } +} + +static int do_instance_sq_ass_slice( + PyObject* instance, int start, int finish, PyObject* value) +{ + try + { + return static_cast(instance->ob_type) + ->instance_sequence_ass_slice(instance, start, finish, value); + } + catch(...) + { + handle_exception(); + return -1; + } +} + +} + +namespace detail { +template struct category_type; + +#define DECLARE_CAPABILITY_TYPE(field, sub_structure) \ + template <> \ + struct category_type<(offsetof(PyTypeObject, tp_as_##field))> \ + { \ + typedef sub_structure type; \ + } + +#define CAPABILITY(field) \ + { offsetof(PyTypeObject, tp_##field), 0, Dispatch(do_instance_##field), 0, -1 } + +#define CAPABILITY2(category, field) \ + { offsetof(PyTypeObject, tp_as_##category), \ + offsetof(category_type::type, field), \ + Dispatch(do_instance_##field), \ + sizeof(category_type::type), \ + offsetof(AllMethods, category) \ + } + +DECLARE_CAPABILITY_TYPE(mapping, PyMappingMethods); +DECLARE_CAPABILITY_TYPE(sequence, PySequenceMethods); + +const CapabilityEntry capabilities[] = { + CAPABILITY(hash), + CAPABILITY(call), + CAPABILITY(str), + CAPABILITY(getattr), + CAPABILITY(setattr), + CAPABILITY(compare), + CAPABILITY(repr), + + CAPABILITY2(mapping, mp_length), + CAPABILITY2(mapping, mp_subscript), + CAPABILITY2(mapping, mp_ass_subscript), + + CAPABILITY2(sequence, sq_length), + CAPABILITY2(sequence, sq_item), + CAPABILITY2(sequence, sq_ass_item), + CAPABILITY2(sequence, sq_concat), + CAPABILITY2(sequence, sq_repeat), + CAPABILITY2(sequence, sq_slice), + CAPABILITY2(sequence, sq_ass_slice) +}; + + const std::size_t num_capabilities = PY_ARRAY_LENGTH(capabilities); + + void add_capability( + std::size_t n, + PyTypeObject* dest_, + AllMethods& all_methods, + const PyTypeObject* src_) + { + assert(n < PY_ARRAY_LENGTH(capabilities)); + const CapabilityEntry& c = capabilities[n]; + + const char* const* src = src_ ? reinterpret_cast( + reinterpret_cast(src_) + c.offset1) : 0; + + char** const dest = reinterpret_cast( + reinterpret_cast(dest_) + c.offset1); + + if (c.substructure_size == 0) + { + if (src == 0 || +#if defined(__MWERKS__) && __MWERKS__ <= 0x4000 + ((const Dispatch*)src) +#else + reinterpret_cast(src) +#endif + != 0) { + *reinterpret_cast(dest) = c.dispatch; + } + } + else + { + if (src == 0 || + *src != 0 && *reinterpret_cast(*src + c.offset2) != 0) + { + *dest = reinterpret_cast(&all_methods) + c.allmethods_offset; + *reinterpret_cast(*dest + c.offset2) = c.dispatch; + } + } + + } +} // namespace detail + +namespace { + union SubStructures { + PyMappingMethods mapping; + PySequenceMethods sequence; + PyNumberMethods number; + PyBufferProcs buffer; + }; +} + +void TypeObjectBase::enable(TypeObjectBase::Capability capability) +{ + using detail::capabilities; + using detail::CapabilityEntry; + using detail::Dispatch; + + assert((std::size_t)capability < PY_ARRAY_LENGTH(capabilities)); + const CapabilityEntry& c = capabilities[capability]; + + PyTypeObject* const me = this; + char* const base_address = reinterpret_cast(me); + + if (c.substructure_size == 0) + { + // Stuff the dispatch function directly into the PyTypeObject + *reinterpret_cast(base_address + c.offset1) = c.dispatch; + return; + } + + const char*& sub_structure = *reinterpret_cast(base_address + c.offset1); + + // Initialize this POD union with the current state-of-the-world + SubStructures sub; + if (sub_structure == 0) + PY_CSTD_::memset(&sub, 0, c.substructure_size); + else + PY_CSTD_::memcpy(&sub, sub_structure, c.substructure_size); + + // Stuff the dispatch function into the sub-structure + *reinterpret_cast(reinterpret_cast(&sub) + c.offset2) = c.dispatch; + + // Retrieve the unique dynamically-allocated substructure and stuff it into + // the PyTypeObject. + sub_structure = static_cast( + detail::UniquePodSet::instance().get_element(&sub, c.substructure_size)); +} + + +TypeObjectBase::TypeObjectBase(PyTypeObject* t) + : PythonType(t) +{ + this->tp_dealloc = do_instance_dealloc; +} + +namespace { + struct ErrorType { + operator PyObject*() const { return 0; } + operator int() const { return -1; } + }; + + ErrorType unimplemented(const char* name) + { + assert(!"Control should never reach here"); + String s("Unimplemented "); + s += String(name); + PyErr_SetObject(PyExc_RuntimeError, s.get()); + return ErrorType(); + } +} + +PyObject* TypeObjectBase::instance_repr(PyObject*) const +{ + return unimplemented("instance_repr"); +} + +int TypeObjectBase::instance_compare(PyObject*, PyObject*) const +{ + return unimplemented("instance_compare"); +} + +PyObject* TypeObjectBase::instance_str(PyObject*) const +{ + return unimplemented("instance_str"); +} + +long TypeObjectBase::instance_hash(PyObject* /* instance */) const +{ + return unimplemented("instance_hash"); +} + +PyObject* TypeObjectBase::instance_call(PyObject* /*instance*/, PyObject* /*args*/, PyObject* /*kw*/) const +{ + return unimplemented("instance_call"); +} + +PyObject* TypeObjectBase::instance_getattr(PyObject* /*instance*/, const char* /*name*/) const +{ + return unimplemented("instance_getattr"); +} + +int TypeObjectBase::instance_setattr(PyObject* /*instance*/, const char* /*name*/, PyObject* /*value*/) const +{ + return unimplemented("instance_setattr"); +} + +int TypeObjectBase::instance_mapping_length(PyObject*) const +{ + return unimplemented("instance_mapping_length"); +} + +int TypeObjectBase::instance_sequence_length(PyObject*) const +{ + return unimplemented("instance_sequence_length"); +} + +PyObject* TypeObjectBase::instance_mapping_subscript(PyObject*, PyObject*) const +{ + return unimplemented("instance_mapping_subscript"); +} + +PyObject* TypeObjectBase::instance_sequence_item(PyObject*, int) const +{ + return unimplemented("instance_sequence_item"); +} + +int TypeObjectBase::instance_mapping_ass_subscript(PyObject*, PyObject*, PyObject*) const +{ + return unimplemented("instance_mapping_ass_subscript"); +} + +int TypeObjectBase::instance_sequence_ass_item(PyObject*, int, PyObject*) const +{ + return unimplemented("instance_sequence_ass_item"); +} + +PyObject* TypeObjectBase::instance_sequence_concat(PyObject*, PyObject*) const +{ + return unimplemented("instance_sequence_concat"); +} + +PyObject* TypeObjectBase::instance_sequence_repeat(PyObject*, int) const +{ + return unimplemented("instance_sequence_repeat"); +} + +PyObject* TypeObjectBase::instance_sequence_slice(PyObject*, int, int) const +{ + return unimplemented("instance_sequence_slice"); +} + +int TypeObjectBase::instance_sequence_ass_slice(PyObject*, int, int, PyObject*) const +{ + return unimplemented("instance_sequence_ass_slice"); +} + +TypeObjectBase::~TypeObjectBase() +{ +} + +} diff --git a/newtypes.h b/newtypes.h new file mode 100644 index 00000000..94382232 --- /dev/null +++ b/newtypes.h @@ -0,0 +1,264 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef TYPES_DWA051800_H_ +# define TYPES_DWA051800_H_ + +// Usage: +// class X : public +// py::Callable< +// py::Getattrable < +// py::Setattrable > > +// { +// public: +// Ptr call(args, kw); +// Ptr getattr(args, kw); +// Ptr setattr(args, kw); +// }; + +# include "pyconfig.h" +# include "signatures.h" // really just for Type<> +# include "cast.h" +# include "base_object.h" +# include +# include + +namespace py { + +class TypeObjectBase : public PythonType +{ + public: + explicit TypeObjectBase(PyTypeObject* type_type); + virtual ~TypeObjectBase(); + + public: + enum Capability + { + hash, call, str, getattr, setattr, compare, repr, + mapping_length, mapping_subscript, mapping_ass_subscript, + sequence_length, sequence_item, sequence_ass_item, + sequence_concat, sequence_repeat, sequence_slice, sequence_ass_slice + }; + + void enable(Capability); + + // + // Type behaviors + // + public: // Callbacks for basic type functionality. + virtual PyObject* instance_repr(PyObject*) const; + virtual int instance_compare(PyObject*, PyObject* other) const; + virtual PyObject* instance_str(PyObject*) const; + virtual long instance_hash(PyObject*) const; + virtual PyObject* instance_call(PyObject* instance, PyObject* args, PyObject* kw) const; + virtual PyObject* instance_getattr(PyObject* instance, const char* name) const; + virtual int instance_setattr(PyObject* instance, const char* name, PyObject* value) const; + + // Dealloc is a special case, since every type needs a nonzero tp_dealloc slot. + virtual void instance_dealloc(PyObject*) const = 0; + + public: // Callbacks for mapping methods + virtual int instance_mapping_length(PyObject*) const; + virtual PyObject* instance_mapping_subscript(PyObject*, PyObject*) const ; + virtual int instance_mapping_ass_subscript(PyObject*, PyObject*, PyObject*) const; + + public: // Callbacks for sequence methods + virtual int instance_sequence_length(PyObject* instance) const; + virtual PyObject* instance_sequence_concat(PyObject* instance, PyObject* other) const; + virtual PyObject* instance_sequence_repeat(PyObject* instance, int n) const; + virtual PyObject* instance_sequence_item(PyObject* instance, int n) const; + virtual PyObject* instance_sequence_slice(PyObject* instance, int start, int finish) const; + virtual int instance_sequence_ass_item(PyObject* instance, int n, PyObject* value) const; + virtual int instance_sequence_ass_slice(PyObject* instance, int start, int finish, PyObject* value) const; + +}; + +template +class TypeObject : public TypeObjectBase +{ + public: + typedef T Instance; + + TypeObject(PyTypeObject* type_type, const char* name = 0) + : TypeObjectBase(type_type) + { + this->tp_name = const_cast(name ? name : typeid(Instance).name()); + } + + private: // Overridable behaviors. + // Called when the reference count goes to zero. The default implementation + // is "delete p". If you have not allocated your object with operator new or + // you have other constraints, you'll need to override this + virtual void dealloc(T* p) const; + + private: // Implementation of TypeObjectBase hooks. Do not reimplement in derived classes. + void instance_dealloc(PyObject*) const; +}; + +// +// Type objects +// +template +class Callable : public Base +{ + public: + typedef Callable Properties; // Convenience for derived class construction + typedef typename Base::Instance Instance; + Callable(PyTypeObject* type_type, const char* name = 0); + private: + PyObject* instance_call(PyObject* instance, PyObject* args, PyObject* kw) const; +}; + +template +class Getattrable : public Base +{ + public: + typedef Getattrable Properties; // Convenience for derived class construction + typedef typename Base::Instance Instance; + Getattrable(PyTypeObject* type_type, const char* name = 0); + private: + PyObject* instance_getattr(PyObject* instance, const char* name) const; +}; + +template +class Setattrable : public Base +{ + public: + typedef Setattrable Properties; // Convenience for derived class construction + typedef typename Base::Instance Instance; + Setattrable(PyTypeObject* type_type, const char* name = 0); + private: + int instance_setattr(PyObject* instance, const char* name, PyObject* value) const; +}; + +// +// Member function definitions +// + +// TypeObject<> +template +void TypeObject::instance_dealloc(PyObject* instance) const +{ + this->dealloc(Downcast(instance).get()); +} + +template +void TypeObject::dealloc(T* instance) const +{ + delete instance; +} + +// Callable +template +Callable::Callable(PyTypeObject* type_type, const char* name) + : Base(type_type, name) +{ + this->enable(call); +} + +template +PyObject* Callable::instance_call(PyObject* instance, PyObject* args, PyObject* kw) const +{ + return Downcast(instance)->call(args, kw); +} + +// Getattrable +template +Getattrable::Getattrable(PyTypeObject* type_type, const char* name) + : Base(type_type, name) +{ + this->enable(getattr); +} + +template +PyObject* Getattrable::instance_getattr(PyObject* instance, const char* name) const +{ + return Downcast(instance)->getattr(name); +} + +// Setattrable +template +Setattrable::Setattrable(PyTypeObject* type_type, const char* name) + : Base(type_type, name) +{ + this->enable(setattr); +} + +template +int Setattrable::instance_setattr(PyObject* instance, const char* name, PyObject* value) const +{ + return Downcast(instance)->setattr(name, value); +} + +namespace detail { + + struct AllMethods { + PyMappingMethods mapping; + PySequenceMethods sequence; + PyNumberMethods number; + PyBufferProcs buffer; + }; + + typedef void (*Dispatch)(); + struct CapabilityEntry + { + std::size_t offset1; + std::size_t offset2; + Dispatch dispatch; + std::size_t substructure_size; + int allmethods_offset; + }; + + extern const CapabilityEntry capabilities[]; + extern const std::size_t num_capabilities; + + void add_capability(std::size_t index, PyTypeObject* dest, AllMethods&, const PyTypeObject* src = 0); + + class UniquePodSet + { + typedef std::pair Holder; + typedef std::vector Storage; + public: + static UniquePodSet& instance(); + ~UniquePodSet(); + + template + T* get(const T& x) + { + char* base = const_cast( + reinterpret_cast(&x)); + return const_cast( + static_cast( + get_element(base, sizeof(T)))); + } + + const void* get_element(const void* buffer, std::size_t size); + + private: + struct Compare; + + private: + UniquePodSet() {} // singleton + + private: + Storage m_storage; + }; + +# define PY_ARRAY_LENGTH(a) \ + (sizeof(::py::detail::countof_validate(a, &(a))) ? sizeof(a) / sizeof((a)[0]) : 0) + + template + inline void countof_validate(T* const, T* const*); + + template + inline int countof_validate(const void*, T); +} + +} + +#endif // TYPES_DWA051800_H_ diff --git a/newtypes_d.cpp b/newtypes_d.cpp new file mode 100644 index 00000000..12318b83 --- /dev/null +++ b/newtypes_d.cpp @@ -0,0 +1,2 @@ +#define DEBUG_PYTHON +#include "newtypes.cpp" diff --git a/none.h b/none.h new file mode 100644 index 00000000..10232958 --- /dev/null +++ b/none.h @@ -0,0 +1,21 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef NONE_DWA_052000_H_ +# define NONE_DWA_052000_H_ + +# include "pyconfig.h" +# include "wrap_python.h" + +namespace py { + +inline PyObject* none() { Py_INCREF(Py_None); return Py_None; } + +} + +#endif // NONE_DWA_052000_H_ diff --git a/objects.cpp b/objects.cpp new file mode 100644 index 00000000..63c7a52e --- /dev/null +++ b/objects.cpp @@ -0,0 +1,454 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +// TODO: Move inline implementations from objects.cpp here + +#include "objects.h" +#include "none.h" + +namespace py { + +template +T object_from_python(PyObject* p, Type) +{ + Ptr x(p); + if (!T::accepts(x)) + { + PyErr_SetString(PyExc_TypeError, p->ob_type->tp_name); + throw ErrorAlreadySet(); + } + return T(x); +} + +inline PyObject* object_to_python(const Object& x) +{ + return x.reference().release(); +} + +Object::Object(Ptr p) + : m_p(p) {} + +// Return a reference to the held object +Ptr Object::reference() const +{ + return m_p; +} + +// Return a raw pointer to the held object +PyObject* Object::get() const +{ + return m_p.get(); +} + +#ifdef PY_NO_INLINE_FRIENDS_IN_NAMESPACE +} // Back to the global namespace for this GCC bug +#endif + +PyObject* to_python(const py::Tuple& x) +{ + return object_to_python(x); +} + +py::Tuple from_python(PyObject* p, py::Type type) +{ + return py::object_from_python(p, type); +} + +PyObject* to_python(const py::List& x) +{ + return object_to_python(x); +} + +py::List from_python(PyObject* p, py::Type type) +{ + return py::object_from_python(p, type); +} + +PyObject* to_python(const py::Dict& x) +{ + return object_to_python(x); +} + +py::Dict from_python(PyObject* p, py::Type type) +{ + return py::object_from_python(p, type); +} + +PyObject* to_python(const py::String& x) +{ + return object_to_python(x); +} + +py::String from_python(PyObject* p, py::Type type) +{ + return py::object_from_python(p, type); +} + +#ifdef PY_NO_INLINE_FRIENDS_IN_NAMESPACE +namespace py { +#endif + +Tuple::Tuple(std::size_t n) + : Object(Ptr(PyTuple_New(n))) +{ + for (std::size_t i = 0; i < n; ++i) + PyTuple_SET_ITEM(get(), i, none()); +} + +Tuple::Tuple(Ptr p) + : Object(p) +{ + assert(accepts(p)); +} + +PyTypeObject* Tuple::type_object() +{ + return &PyTuple_Type; +} + +bool Tuple::accepts(Ptr p) +{ + return PyTuple_Check(p.get()); +} + +std::size_t Tuple::size() const +{ + return PyTuple_Size(get()); +} + +Ptr Tuple::operator[](std::size_t pos) const +{ + return Ptr(PyTuple_GetItem(get(), static_cast(pos)), + Ptr::new_ref); +} + +void Tuple::set_item(std::size_t pos, const Ptr& rhs) +{ + int failed = PyTuple_SetItem( + get(), static_cast(pos), Ptr(rhs).release()); // A reference is stolen here. + (void)failed; + assert(failed == 0); +} + +Tuple Tuple::slice(int low, int high) const +{ + return Tuple(Ptr(PyTuple_GetSlice(get(), low, high))); +} + +Tuple& Tuple::operator+=(const Tuple& rhs) +{ + return *this = *this + rhs; +} + + +// Construct from an owned PyObject*. +// Precondition: p must point to a python string. +String::String(Ptr p) + : Object(p) { assert(accepts(p)); } + +String::String(const char* s) + : Object(Ptr(PyString_FromString(s))) {} + +String::String(const char* s, std::size_t length) + : Object(Ptr(PyString_FromStringAndSize(s, length))) {} + +String::String(const char* s, Interned) + : Object(Ptr(PyString_InternFromString(s))) {} + +#if 0 +String::String(const char* s, std::size_t length, Interned) + : Object(Ptr(PyString_InternFromStringAndSize(s, length))) {} +#endif + +String::String(const String& rhs) + : Object(rhs.reference()) {} + +// Get the type object for Strings +PyTypeObject* String::type_object() +{ return &PyString_Type; } + +// Return true if the given object is a python String +bool String::accepts(Ptr o) +{ return PyString_Check(o.get()); } + +// Return the length of the string. +std::size_t String::size() const +{ + int size = PyString_GET_SIZE(get()); + assert(size >= 0); + return static_cast(size); +} + +// Returns a null-terminated representation of the contents of string. +// The pointer refers to the internal buffer of string, not a copy. +// The data must not be modified in any way. It must not be de-allocated. +const char* String::c_str() const +{ return PyString_AS_STRING(get()); } + +void String::intern() +{ // UNTESTED!! + *this = String(Ptr(PyString_InternFromString(c_str()), Ptr::borrowed)); +} + +Dict::Dict(Ptr p) + : Object(p) { assert(accepts(p)); } + +Dict::Dict() + : Object(Ptr(PyDict_New())) {} + +PyTypeObject* Dict::type_object() +{ return &PyDict_Type; } + +bool Dict::accepts(Ptr p) +{ return PyDict_Check(p.get()); } + +void Dict::clear() +{ PyDict_Clear(get()); } + +const Ptr& Dict::Proxy::operator=(const Ptr& rhs) { + if (PyDict_SetItem(m_dict.get(), m_key.get(), rhs.get()) == -1) + throw ErrorAlreadySet(); + return rhs; +} + +Dict::Proxy::operator Ptr() const +{ + return Ptr(m_dict->ob_type->tp_as_mapping->mp_subscript(m_dict.get(), m_key.get()), + Ptr::borrowed); +} + +Dict::Proxy::Proxy(const Ptr& dict, const Ptr& key) + : m_dict(dict), m_key(key) {} + +Dict::Proxy Dict::operator[](Ptr key) +{ return Proxy(reference(), key); } + +Ptr Dict::operator[](Ptr key) const { + // An odd MSVC bug causes the ".operator Ptr()" to be needed + return Proxy(reference(), key).operator Ptr(); +} + + +Ptr Dict::get_item(const Ptr& key, const Ptr& _default /* = Ptr() */) +{ + PyObject* value_or_null = PyDict_GetItem(get(), key.get()); + if (value_or_null == 0 && !PyErr_Occurred()) + return _default; + else + return Ptr(value_or_null, Ptr::borrowed); // Will throw if there was another error +} + +void Dict::erase(Ptr key) { + if (PyDict_DelItem(get(), key.get()) == -1) + throw ErrorAlreadySet(); +} + +Ptr Dict::items() const { return Ptr(PyDict_Items(get())); } +Ptr Dict::keys() const { return Ptr(PyDict_Keys(get())); } +Ptr Dict::values() const { return Ptr(PyDict_Values(get())); } + +std::size_t Dict::size() const { return static_cast(PyDict_Size(get())); } + +Dict::Proxy Dict::operator[](const Object& key) +{ + return this->operator[](key.reference()); +} + +Ptr Dict::operator[](const Object& key) const +{ + return this->operator[](key.reference()); +} + +Ptr Dict::get_item(const Object& key, Ptr default_) +{ + return this->get_item(key.reference(), default_); +} + +void Dict::erase(const Object& key) +{ + this->erase(key.reference()); +} + + +// TODO: iterator support + +String operator+(String x, String y) +{ + PyObject* io_string = x.reference().release(); + PyString_Concat(&io_string, y.get()); + return String(Ptr(io_string)); +} + +String& String::operator+=(const String& rhs) +{ + return *this = *this + rhs; +} + +String& String::operator+=(const char* y) +{ + return *this += String(y); +} + +String operator%(const String& format, const Tuple& args) +{ + return String(Ptr(PyString_Format(format.get(), args.reference().get()))); +} + +String operator+(String x, const char* y) +{ + return x + String(y); +} + +String operator+(const char* x, String y) +{ + return String(x) + y; +} + +Tuple operator+(const Tuple& x, const Tuple& y) +{ + Tuple result(x.size() + y.size()); + for (std::size_t xi = 0; xi < x.size(); ++xi) + result.set_item(xi, x[xi]); + for (std::size_t yi = 0; yi < y.size(); ++yi) + result.set_item(yi + x.size(), y[yi]); + return result; +} + + +List::List(Ptr p) + : Object(p) +{ + assert(accepts(p)); +} + +List::List(std::size_t sz) + : Object(Ptr(PyList_New(sz))) +{ +} + +PyTypeObject* List::type_object() +{ + return &PyList_Type; +} + +bool List::accepts(Ptr p) +{ + return PyList_Check(p.get()); +} + +std::size_t List::size() +{ + return PyList_Size(get()); +} + +Ptr List::operator[](std::size_t pos) const +{ + return Ptr(PyList_GetItem(get(), pos), Ptr::borrowed); +} + +List::Proxy List::operator[](std::size_t pos) +{ + return Proxy(reference(), pos); +} + +void List::insert(std::size_t index, Ptr item) +{ + if (PyList_Insert(get(), index, item.get()) == -1) + throw ErrorAlreadySet(); +} + +void List::push_back(Ptr item) +{ + if (PyList_Append(get(), item.get()) == -1) + throw ErrorAlreadySet(); +} + +void List::append(Ptr item) +{ + this->push_back(item); +} + +List List::slice(int low, int high) const +{ + return List(Ptr(PyList_GetSlice(get(), low, high))); +} + +List::SliceProxy List::slice(int low, int high) +{ + return SliceProxy(reference(), low, high); +} + +void List::sort() +{ + if (PyList_Sort(get()) == -1) + throw ErrorAlreadySet(); +} + +void List::reverse() +{ + if (PyList_Reverse(get()) == -1) + throw ErrorAlreadySet(); +} + +Tuple List::as_tuple() const +{ + return Tuple(Ptr(PyList_AsTuple(get()))); +} + +const Ptr& List::Proxy::operator=(const Ptr& rhs) +{ + int result = PyList_SetItem(m_list.get(), m_index, rhs.get()); + if (result == -1) + throw ErrorAlreadySet(); + Py_INCREF(rhs.get()); + return rhs; +} + +List::Proxy::operator Ptr() const +{ + return Ptr(PyList_GetItem(m_list.get(), m_index), Ptr::borrowed); +} + +List::Proxy::Proxy(const Ptr& list, std::size_t index) + : m_list(list), m_index(index) +{ +} + +const List& List::SliceProxy::operator=(const List& rhs) +{ + if (PyList_SetSlice(m_list.get(), m_low, m_high, rhs.get()) == -1) + throw ErrorAlreadySet(); + return rhs; +} + +List::SliceProxy::operator Ptr() const +{ + return Ptr(PyList_GetSlice(m_list.get(), m_low, m_high)); +} + +List::SliceProxy::operator List() const +{ + return List(this->operator Ptr()); +} + +std::size_t List::SliceProxy::size() +{ + return this->operator List().size(); +} + +Ptr List::SliceProxy::operator[](std::size_t pos) const +{ + return this->operator List()[pos].operator Ptr(); +} + +List::SliceProxy::SliceProxy(const Ptr& list, int low, int high) + : m_list(list), m_low(low), m_high(high) +{ +} + +} diff --git a/objects.h b/objects.h new file mode 100644 index 00000000..c2d1eabe --- /dev/null +++ b/objects.h @@ -0,0 +1,233 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef OBJECTS_DWA051100_H_ +# define OBJECTS_DWA051100_H_ + +# include "wrap_python.h" +# include "pyconfig.h" +# include "pyptr.h" +# include "boost/operators.hpp" + +namespace py { + +class Object +{ + public: + explicit Object(Ptr p); + + // Return a reference to the held object + Ptr reference() const; + + // Return a raw pointer to the held object + PyObject* get() const; + + private: + Ptr m_p; +}; + +class Tuple : public Object +{ + public: + Tuple(std::size_t n = 0); + explicit Tuple(Ptr p); + + Tuple(const Ptr* start, const Ptr* finish); // not yet implemented. + + static PyTypeObject* type_object(); + static bool accepts(Ptr p); + std::size_t size() const; + Ptr operator[](std::size_t pos) const; + + void set_item(std::size_t pos, const Ptr& rhs); + Tuple slice(int low, int high) const; + + friend Tuple operator+(const Tuple&, const Tuple&); + Tuple& operator+=(const Tuple& rhs); +}; + +class List : public Object +{ + struct Proxy; + struct SliceProxy; + public: + explicit List(Ptr p); + explicit List(std::size_t sz = 0); + static PyTypeObject* type_object(); + static bool accepts(Ptr p); + std::size_t size(); + Ptr operator[](std::size_t pos) const; + Proxy operator[](std::size_t pos); + void insert(std::size_t index, Ptr item); + void push_back(Ptr item); + void append(Ptr item); + List slice(int low, int high) const; + SliceProxy slice(int low, int high); + void sort(); + void reverse(); + Tuple as_tuple() const; +}; + +class String : public Object +{ + public: + // Construct from an owned PyObject*. + // Precondition: p must point to a python string. + explicit String(Ptr p); + explicit String(const char* s); + String(const char* s, std::size_t length); + String(const String& rhs); + + enum Interned { interned }; + String(const char* s, Interned); +#if 0 + String(const char* s, std::size_t length, Interned); +#endif + + // Get the type object for Strings + static PyTypeObject* type_object(); + + // Return true if the given object is a python String + static bool accepts(Ptr o); + + // Return the length of the string. + std::size_t size() const; + + // Returns a null-terminated representation of the contents of string. + // The pointer refers to the internal buffer of string, not a copy. + // The data must not be modified in any way. It must not be de-allocated. + const char* c_str() const; + + String& operator+=(const String& rhs); + friend String operator+(String x, String y); + String& operator+=(const char* rhs); + friend String operator+(String x, const char* y); + friend String operator+(const char* x, String y); + + void intern(); // UNTESTED!! + + friend String operator%(const String& format, const Tuple& args); +}; + +class Dict : public Object +{ + private: + struct Proxy; + + public: + explicit Dict(Ptr p); + Dict(); + void clear(); + + static PyTypeObject* type_object(); + static bool accepts(Ptr p); + + public: + Proxy operator[](Ptr key); + Ptr operator[](Ptr key) const; + + Ptr get_item(const Ptr& key, const Ptr& _default = Ptr()); + + void erase(Ptr key); + + Proxy operator[](const Object& key); + Ptr operator[](const Object& key) const; + + Ptr get_item(const Object& key, Ptr default_ = Ptr()); + + void erase(const Object& key); + + Ptr items() const; + Ptr keys() const; + Ptr values() const; + + std::size_t size() const; + // TODO: iterator support +}; + +struct Dict::Proxy +{ + const Ptr& operator=(const Ptr& rhs); + operator Ptr() const; + private: + friend class Dict; + Proxy(const Ptr& dict, const Ptr& key); + private: + Ptr m_dict; + Ptr m_key; +}; + +struct List::Proxy +{ + const Ptr& operator=(const Ptr& rhs); + operator Ptr() const; + private: + friend class List; + Proxy(const Ptr& list, std::size_t index); + private: + Ptr m_list; + std::size_t m_index; +}; + +struct List::SliceProxy +{ + const List& operator=(const List& rhs); + operator Ptr() const; + operator List() const; + std::size_t size(); + Ptr operator[](std::size_t pos) const; + private: + friend class List; + SliceProxy(const Ptr& list, int low, int high); + private: + Ptr m_list; + int m_low, m_high; +}; + +#ifdef PY_NO_INLINE_FRIENDS_IN_NAMESPACE +} // Back to the global namespace for this GCC bug +#endif + +PyObject* to_python(const py::Tuple&); +py::Tuple from_python(PyObject* p, py::Type); + +inline py::Tuple from_python(PyObject* p, py::Type) +{ + return from_python(p, py::Type()); +} + +PyObject* to_python(const py::List&); +py::List from_python(PyObject* p, py::Type); + +inline py::List from_python(PyObject* p, py::Type) +{ + return from_python(p, py::Type()); +} + +PyObject* to_python(const py::String&); +py::String from_python(PyObject* p, py::Type); + +inline py::String from_python(PyObject* p, py::Type) +{ + return from_python(p, py::Type()); +} + +PyObject* to_python(const py::Dict&); +py::Dict from_python(PyObject* p, py::Type); + +inline py::Dict from_python(PyObject* p, py::Type) +{ + return from_python(p, py::Type()); +} + +#ifdef PY_NO_INLINE_FRIENDS_IN_NAMESPACE +namespace py { +#endif + +} +#endif diff --git a/objects_d.cpp b/objects_d.cpp new file mode 100644 index 00000000..95e19316 --- /dev/null +++ b/objects_d.cpp @@ -0,0 +1,2 @@ +#define DEBUG_PYTHON +#include "objects.cpp" diff --git a/py.cpp b/py.cpp new file mode 100644 index 00000000..a64e27c5 --- /dev/null +++ b/py.cpp @@ -0,0 +1,204 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#include "py.h" +#include + +namespace py { + +void expect_and_absorb_non_null(PyObject* p) +{ + Py_XDECREF(expect_non_null(p)); +} + +// IMPORTANT: this function may only be called from within a catch block! +void handle_exception() +{ + try { + // re-toss the current exception so we can find out what type it is. + // NOTE: a heinous bug in MSVC6 causes exception objects re-thrown in + // this way to be double-destroyed. Thus, you must only use objects that + // can tolerate double-destruction with that compiler. Metrowerks + // Codewarrior doesn't suffer from this problem. + throw; + } + catch(const py::ErrorAlreadySet&) + { + // The python error reporting has already been handled. + } + catch(const std::bad_alloc&) + { + PyErr_NoMemory(); + } + catch(const std::exception& x) + { + PyErr_SetString(PyExc_RuntimeError, x.what()); + } + catch(...) + { + PyErr_SetString(PyExc_RuntimeError, "unidentifiable C++ exception"); + } +} + +#ifdef PY_NO_INLINE_FRIENDS_IN_NAMESPACE +} +#endif + +long from_python(PyObject* p, py::Type) +{ + // Why am I clearing the error here before trying to convert? I know there's a reason... + long result; + { +// py::SuspendError suspended_error(py::SuspendError::discard_old_error); + result = PyInt_AsLong(p); + if (PyErr_Occurred()) + throw py::ArgumentError(); +// suspended_error.throw_if_error(); + } + return result; +} + +double from_python(PyObject* p, py::Type) +{ + // Why am I clearing the error here before trying to convert? I know there's a reason... + double result; + { +// py::SuspendError suspended_error(py::SuspendError::discard_old_error); + result = PyFloat_AsDouble(p); + if (PyErr_Occurred()) + throw py::ArgumentError(); +// suspended_error.throw_if_error(); + } + return result; +} + +template +T integer_from_python(PyObject* p, py::Type) +{ + const long long_result = from_python(p, py::Type()); + + try + { + return boost::numeric_cast(long_result); + } + catch(const boost::bad_numeric_cast&) + { + char buffer[256]; + const char message[] = "%ld out of range for %s"; + sprintf(buffer, message, long_result, typeid(T).name()); + PyErr_SetString(PyExc_ValueError, buffer); + throw py::ArgumentError(); + } +#if defined(__MWERKS__) && __MWERKS__ < 0x6000 + return 0; // Not smart enough to know that the catch clause always rethrows +#endif +} + +template +PyObject* integer_to_python(T value) +{ + long value_as_long; + + try + { + value_as_long = boost::numeric_cast(value); + } + catch(const boost::bad_numeric_cast&) + { + const char message[] = "value out of range for Python int"; + PyErr_SetString(PyExc_ValueError, message); + throw py::ErrorAlreadySet(); + } + + return to_python(value_as_long); +} + +int from_python(PyObject* p, py::Type type) +{ + return integer_from_python(p, type); +} + +PyObject* to_python(unsigned int i) +{ + return integer_to_python(i); +} + +unsigned int from_python(PyObject* p, py::Type type) +{ + return integer_from_python(p, type); +} + +short from_python(PyObject* p, py::Type type) +{ + return integer_from_python(p, type); +} + +float from_python(PyObject* p, py::Type) +{ + return static_cast(from_python(p, py::Type())); +} + +PyObject* to_python(unsigned short i) +{ + return integer_to_python(i); +} + +unsigned short from_python(PyObject* p, py::Type type) +{ + return integer_from_python(p, type); +} + +PyObject* to_python(unsigned long x) +{ + return integer_to_python(x); +} + +unsigned long from_python(PyObject* p, py::Type type) +{ + return integer_from_python(p, type); +} + +void from_python(PyObject* p, py::Type) +{ + if (p != Py_None) { + PyErr_SetString(PyExc_TypeError, "expected argument of type None"); + throw py::ArgumentError(); + } +} + +const char* from_python(PyObject* p, py::Type) +{ + const char* s = PyString_AsString(p); + if (!s) throw py::ArgumentError(); + return s; +} + +PyObject* to_python(const std::string& s) +{ + return PyString_FromString(s.c_str()); +} + +std::string from_python(PyObject* p, py::Type) +{ + return std::string(from_python(p, py::Type())); +} + +bool from_python(PyObject* p, py::Type) +{ + int value = from_python(p, py::Type()); + if (value == 0) + return false; + return true; +} + +#ifdef PY_NO_INLINE_FRIENDS_IN_NAMESPACE +namespace py { +#endif + +} + diff --git a/py.h b/py.h new file mode 100644 index 00000000..2cf89807 --- /dev/null +++ b/py.h @@ -0,0 +1,266 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef METHOD_DWA122899_H_ +# define METHOD_DWA122899_H_ + +# include "wrap_python.h" +# include "none.h" +# include "signatures.h" +# include +# include "errors.h" +# include + +namespace py { + +template class WrappedPointer; + +//#pragma warn_possunwant off +inline void decref_impl(PyObject* p) { Py_DECREF(p); } +inline void xdecref_impl(PyObject* p) { Py_XDECREF(p); } +//#pragma warn_possunwant reset + +template +inline void decref(T* p) +{ + char* const raw_p = reinterpret_cast(p); + char* const p_base = raw_p - offsetof(PyObject, ob_refcnt); + decref_impl(reinterpret_cast(p_base)); +} + +template +inline void xdecref(T* p) +{ + char* const raw_p = reinterpret_cast(p); + char* const p_base = raw_p - offsetof(PyObject, ob_refcnt); + xdecref_impl(reinterpret_cast(p_base)); +} + +#ifdef PY_NO_INLINE_FRIENDS_IN_NAMESPACE +} +#endif + +// +// Converters +// +PyObject* to_python(long); +long from_python(PyObject* p, py::Type); +long from_python(PyObject* p, py::Type); + +PyObject* to_python(unsigned long); +unsigned long from_python(PyObject* p, py::Type); +unsigned long from_python(PyObject* p, py::Type); + +PyObject* to_python(int); +int from_python(PyObject*, py::Type); +int from_python(PyObject*, py::Type); + +PyObject* to_python(unsigned int); +unsigned int from_python(PyObject*, py::Type); +unsigned int from_python(PyObject*, py::Type); + +PyObject* to_python(short); +short from_python(PyObject*, py::Type); +short from_python(PyObject*, py::Type); + +PyObject* to_python(unsigned short); +unsigned short from_python(PyObject*, py::Type); +unsigned short from_python(PyObject*, py::Type); + +PyObject* to_python(float); +float from_python(PyObject*, py::Type); +float from_python(PyObject*, py::Type); + +PyObject* to_python(double); +double from_python(PyObject*, py::Type); +double from_python(PyObject*, py::Type); + +PyObject* to_python(bool); +bool from_python(PyObject*, py::Type); +bool from_python(PyObject*, py::Type); + +PyObject* to_python(void); +void from_python(PyObject*, py::Type); + +PyObject* to_python(const char* s); +const char* from_python(PyObject*, py::Type); + +PyObject* to_python(const std::string& s); +std::string from_python(PyObject*, py::Type); +std::string from_python(PyObject*, py::Type); + +// For when your C++ function really wants to pass/return a PyObject* +PyObject* to_python(PyObject*); +PyObject* from_python(PyObject*, py::Type); + +// Some standard conversions to/from smart pointer types. You can add your own +// from these examples. These are not generated using the friend technique from +// WrappedPointer because: +// +// 1. We want to be able to extend conversion to/from WrappedPointers using +// arbitrary smart pointer types. +// +// 2. It helps with compilation independence. This way, code which creates +// wrappers for functions accepting and returning smart_ptr does not +// have to have already seen the invocation of WrappedType. +// + +// Unfortunately, MSVC6 is so incredibly lame that we have to rely on the friend +// technique to auto_generate standard pointer conversions for wrapped +// types. This means that you need to write a non-templated function for each +// specific smart_ptr which you want to convert from_python. For example, +// +// namespace py { +// #ifdef MUST_SUPPORT_MSVC +// +// MyPtr from_python(PyObject*p, Type >) +// { return smart_ptr_from_python(p, Type >(), Type());} +// } +// +// MyPtr from_python(PyObject*p, Type >) +// { return smart_ptr_from_python(p, Type >(), Type());} +// +// ... // definitions for MyPtr, MyPtr, etc. +// +// #else +// +// // Just once for all MyPtr +// template +// MyPtr from_python(PyObject*p, Type >) +// { +// return smart_ptr_from_python(p, Type >(), Type()); +// } +// +// #endif +// } + +#if !defined(PY_MSVC6_OR_EARLIER) +template +boost::shared_ptr from_python(PyObject*p, py::Type >) +{ + return smart_ptr_from_python(p, py::Type >(), py::Type()); +} +#endif + +#if 0 +template +PyObject* to_python(std::auto_ptr p) +{ + return new py::WrappedPointer, T>(p); +} + +template +PyObject* to_python(boost::shared_ptr p) +{ + return new py::WrappedPointer, T>(p); +} +#endif + +// +// inline implementations +// + +inline PyObject* to_python(long l) +{ + return PyInt_FromLong(l); +} + +inline PyObject* to_python(int x) +{ + return PyInt_FromLong(x); +} + +inline int from_python(PyObject* p, py::Type) +{ + return from_python(p, py::Type()); +} + +inline PyObject* to_python(short x) +{ + return PyInt_FromLong(x); +} + +inline short from_python(PyObject* p, py::Type) +{ + return from_python(p, py::Type()); +} + +inline PyObject* to_python(bool b) +{ + return PyInt_FromLong(b); +} + +inline bool from_python(PyObject* p, py::Type) +{ + return from_python(p, py::Type()); +} + +inline PyObject* to_python(void) +{ + return py::none(); +} + +// const char* +inline PyObject* to_python(const char* s) +{ + return PyString_FromString(s); +} + +inline std::string from_python(PyObject* p, py::Type) +{ + return from_python(p, py::Type()); +} + +inline PyObject* to_python(PyObject* p) +{ + Py_INCREF(p); + return p; +} + +inline PyObject* from_python(PyObject* p, py::Type) +{ + return p; +} + +inline unsigned int from_python(PyObject* p, py::Type) +{ + return from_python(p, py::Type()); +} + +inline unsigned short from_python(PyObject* p, py::Type) +{ + return from_python(p, py::Type()); +} + +inline unsigned long from_python(PyObject* p, py::Type) +{ + return from_python(p, py::Type()); +} + +inline long from_python(PyObject* p, py::Type) +{ + return from_python(p, py::Type()); +} + + +#ifdef PY_NO_INLINE_FRIENDS_IN_NAMESPACE +namespace py { +namespace converters { // bringing these into namespace py tended to confuse gcc; +using ::to_python; // they are in namespace py::converters for use by clients +using ::from_python; +} +#else +namespace converters { +using ::py::to_python; +using ::py::from_python; +} +#endif + +} // namespace py + +#endif diff --git a/py_cpp.bdf b/py_cpp.bdf new file mode 100644 index 00000000..32be8441 --- /dev/null +++ b/py_cpp.bdf @@ -0,0 +1,4 @@ +TargetName=py_cpp +TargetType=lib +SourceFiles=extclass.cpp init_function.cpp subclass.cpp functions.cpp objects.cpp +SourceFiles+=module.cpp newtypes.cpp py.cpp diff --git a/py_cpp_20001013.zip b/py_cpp_20001013.zip new file mode 100644 index 00000000..420d2bc4 Binary files /dev/null and b/py_cpp_20001013.zip differ diff --git a/py_cpp_d.bdf b/py_cpp_d.bdf new file mode 100644 index 00000000..e8598e51 --- /dev/null +++ b/py_cpp_d.bdf @@ -0,0 +1,4 @@ +TargetName=py_cpp_d +TargetType=lib +SourceFiles=extclass_d.cpp init_function_d.cpp subclass_d.cpp functions_d.cpp objects_d.cpp +SourceFiles+=module_d.cpp newtypes_d.cpp py_d.cpp diff --git a/py_cpphelp.mak b/py_cpphelp.mak new file mode 100644 index 00000000..f0367ac0 --- /dev/null +++ b/py_cpphelp.mak @@ -0,0 +1,21 @@ +ifndef PY_CPPHELP_MAK_INCLUDED +PY_CPPHELP_MAK_INCLUDED=1 + +ifndef DONT_BUILD_IMPORTS + + +PY_CPP_DIR=$(ROOT_DIR)/py_cpp +PY_CPP_LIB=$(PY_CPP_DIR)/$(OBJDIR)/py_cpp.lib +PY_CPP_D_LIB=$(PY_CPP_DIR)/$(OBJDIR)/py_cpp_d.lib + +$(PY_CPP_LIB) : FORCE + $(RECURSIVE_MAKE) -C $(PY_CPP_DIR) py_cpp + +$(PY_CPP_D_LIB) : FORCE + $(RECURSIVE_MAKE) -C $(PY_CPP_DIR) py_cpp_d + +# end ifndef DONT_BUILD_IMPORTS +endif + +# end ifndef PY_CPPHELP_MAK_INCLUDED +endif diff --git a/py_d.cpp b/py_d.cpp new file mode 100644 index 00000000..59a6c210 --- /dev/null +++ b/py_d.cpp @@ -0,0 +1,2 @@ +#define DEBUG_PYTHON +#include "py.cpp" diff --git a/pyconfig.h b/pyconfig.h new file mode 100644 index 00000000..35a6bf1e --- /dev/null +++ b/pyconfig.h @@ -0,0 +1,40 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef CONFIG_DWA052200_H_ +# define CONFIG_DWA052200_H_ + +# include +# include + +# ifdef BOOST_NO_OPERATORS_IN_NAMESPACE +# define PY_NO_INLINE_FRIENDS_IN_NAMESPACE 1 // A more accurate name +# define PY_INLINE_FRIEND +# else +# define PY_INLINE_FRIEND ::py +# endif + +# if !defined(__GNUC__) && !defined(__MWERKS__) && !defined(__BORLANDC__) && defined(_MSC_VER) +# define PY_COMPILER_IS_MSVC 1 +# if _MSC_VER <= 1200 +# define PY_MSVC6_OR_EARLIER 1 +# endif + +# pragma warning (disable : 4786) + +# endif + +// The STLport puts all of the standard 'C' library names in std (as far as the +// user is concerned), but without it you need a fix if you're using MSVC. +# if defined(PY_MSVC6_OR_EARLIER) && !defined(__STLPORT) +# define PY_CSTD_ +# else +# define PY_CSTD_ std +# endif + +#endif // CONFIG_DWA052200_H_ diff --git a/pyptr.h b/pyptr.h new file mode 100644 index 00000000..6c62a03f --- /dev/null +++ b/pyptr.h @@ -0,0 +1,143 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef PYPTR_DWA050400_H_ +# define PYPTR_DWA050400_H_ + +# include "pyconfig.h" +# include +# include "wrap_python.h" +# include "cast.h" +# include +# include "signatures.h" +# include "errors.h" + +namespace py { + +template +class PyPtr + : public boost::dereferenceable, T*> // supplies op-> +{ +public: + PyPtr(const PyPtr& rhs) + : m_p(rhs.m_p) + { + Py_XINCREF(object()); + } + +#if !defined(PY_MSVC6_OR_EARLIER) + template + PyPtr(const PyPtr& rhs) + : m_p(rhs.object()) + { + Py_XINCREF(object()); + } +#endif + + PyPtr() : m_p(0) {} + + // These are two ways of spelling the same thing, that we need to increment + // the reference count on the pointer when we're initialized. + enum NewRef { new_ref, borrowed = new_ref }; + + enum AllowNull { null_ok }; + + template + explicit PyPtr(T2* x) + : m_p(expect_non_null(x)) {} + + template + PyPtr(T2* x, NewRef) + : m_p(expect_non_null(x)) { Py_INCREF(object()); } + + template + PyPtr(T2* x, AllowNull) + : m_p(x) {} + + template + PyPtr(T2* x, AllowNull, NewRef) + : m_p(x) { Py_XINCREF(object()); } + + template + PyPtr(T2* x, NewRef, AllowNull) + : m_p(x) { Py_XINCREF(object()); } + +#if !defined(PY_MSVC6_OR_EARLIER) + template + PyPtr& operator=(const PyPtr& rhs) + { + Py_XDECREF(object()); + m_p = rhs.m_p; + Py_XINCREF(object()); + return *this; + } +#endif + + PyPtr& operator=(const PyPtr& rhs) + { + Py_XINCREF(static_cast(rhs.m_p)); + Py_XDECREF(object()); + m_p = rhs.m_p; + return *this; + } + + ~PyPtr() + { + Py_XDECREF(m_p); + } + + T& operator*() const { return *m_p; } + + T* get() const { return m_p; } + + T* release() + { + T* p = m_p; + m_p = 0; + return p; + } + + void reset() + { Py_XDECREF(m_p); m_p = 0; } + + template + void reset(T2* x) + { Py_XDECREF(m_p); m_p = expect_non_null(x);} + + template + void reset(T2* x, NewRef) + { Py_XDECREF(m_p); m_p = expect_non_null(x); Py_INCREF(object()); } + + template + void reset(T2* x, AllowNull) + { Py_XDECREF(m_p); m_p = x;} + + template + void reset(T2* x, AllowNull, NewRef) + { Py_XDECREF(m_p); m_p = x; Py_XINCREF(object()); } + + template + void reset(T2* x, NewRef, AllowNull) + { Py_XDECREF(m_p); m_p = x; Py_XINCREF(object()); } + +#if !defined(BOOST_NO_MEMBER_TEMPLATE_FRIENDS) +private: + template friend class shared_ptr; +#endif + + inline PyObject* object() const + { return as_object(m_p); } + + T* m_p; +}; + +typedef PyPtr Ptr; + +} + +#endif // PYPTR_DWA050400_H_ diff --git a/signatures.h b/signatures.h new file mode 100644 index 00000000..08913b77 --- /dev/null +++ b/signatures.h @@ -0,0 +1,165 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file automatically generated by gen_signatures.py for 5 arguments. +#ifndef SIGNATURES_DWA050900_H_ +# define SIGNATURES_DWA050900_H_ + +# include "pyconfig.h" + +namespace py { + +// A stand-in for the built-in void. This one can be passed to functions and +// (under MSVC, which has a bug, be used as a default template type parameter). +struct Void {}; + +// An envelope in which type information can be delivered for the purposes +// of selecting an overloaded from_python() function. This is needed to work +// around MSVC's lack of partial specialiation/ordering. Where normally we'd +// want to form a function call like void f(), We instead pass +// Type as one of the function parameters to select a particular +// overload. +// +// The Id typedef helps us deal with the lack of partial ordering by generating +// unique types for constructor signatures. In general, Type::Id is Type, +// but Type::Id is just Void. +template +struct Type +{ + typedef Type Id; +}; + +template <> +struct Type +{ + typedef Void Id; +}; + +// These basically encapsulate a chain of types, , used to make the syntax of +// add(Constructor()) work. We need to produce a unique type for each number +// of non-default parameters to Constructor<>. Q: why not use a recursive +// formulation for infinite extensibility? A: MSVC6 seems to choke on constructs +// that involve recursive template nesting. +// +// Signature chaining +template +struct Signature5 {}; + +template +struct Signature4 {}; + +template +inline Signature5 prepend(Type, Signature4) + { return Signature5(); } + +template +struct Signature3 {}; + +template +inline Signature4 prepend(Type, Signature3) + { return Signature4(); } + +template +struct Signature2 {}; + +template +inline Signature3 prepend(Type, Signature2) + { return Signature3(); } + +template +struct Signature1 {}; + +template +inline Signature2 prepend(Type, Signature1) + { return Signature2(); } + +struct Signature0 {}; + +template +inline Signature1 prepend(Type, Signature0) + { return Signature1(); } + + +// This one terminates the chain. Prepending Void to the head of a Void +// signature results in a Void signature again. +inline Signature0 prepend(Void, Signature0) { return Signature0(); } + +template +struct Constructor +{ +}; + +// Return value extraction: + +// This is just another little envelope for carrying a typedef (see Type, +// above). I could have re-used Type, but that has a very specific purpose. I +// thought this would be clearer. +template +struct ReturnValue { typedef T Type; }; + +// free functions +template +ReturnValue return_value(R (*)()) { return ReturnValue(); } + +template +ReturnValue return_value(R (*)(A1)) { return ReturnValue(); } + +template +ReturnValue return_value(R (*)(A1, A2)) { return ReturnValue(); } + +template +ReturnValue return_value(R (*)(A1, A2, A3)) { return ReturnValue(); } + +template +ReturnValue return_value(R (*)(A1, A2, A3, A4)) { return ReturnValue(); } + +template +ReturnValue return_value(R (*)(A1, A2, A3, A4, A5)) { return ReturnValue(); } + +// TODO(?): handle 'const void' + +// member functions +template +ReturnValue return_value(R (T::*)()) { return ReturnValue(); } + +template +ReturnValue return_value(R (T::*)(A1)) { return ReturnValue(); } + +template +ReturnValue return_value(R (T::*)(A1, A2)) { return ReturnValue(); } + +template +ReturnValue return_value(R (T::*)(A1, A2, A3)) { return ReturnValue(); } + +template +ReturnValue return_value(R (T::*)(A1, A2, A3, A4)) { return ReturnValue(); } + +template +ReturnValue return_value(R (T::*)(A1, A2, A3, A4, A5)) { return ReturnValue(); } + +template +ReturnValue return_value(R (T::*)() const) { return ReturnValue(); } + +template +ReturnValue return_value(R (T::*)(A1) const) { return ReturnValue(); } + +template +ReturnValue return_value(R (T::*)(A1, A2) const) { return ReturnValue(); } + +template +ReturnValue return_value(R (T::*)(A1, A2, A3) const) { return ReturnValue(); } + +template +ReturnValue return_value(R (T::*)(A1, A2, A3, A4) const) { return ReturnValue(); } + +template +ReturnValue return_value(R (T::*)(A1, A2, A3, A4, A5) const) { return ReturnValue(); } + +} + +#endif diff --git a/singleton.h b/singleton.h new file mode 100644 index 00000000..5e510df7 --- /dev/null +++ b/singleton.h @@ -0,0 +1,53 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef SINGLETON_DWA051900_H_ +# define SINGLETON_DWA051900_H_ + +# include "pyconfig.h" + +namespace py { + +struct Empty {}; +template +struct Singleton : Base +{ + typedef Singleton SingletonBase; // Convenience type for derived class constructors + + static Derived* singleton(); + + // Pass-through constructors + Singleton() : Base() {} + + template + Singleton(const A1& a1) : Base(a1) {} + + template + Singleton(const A1& a1, const A2& a2) : Base(a1, a2) {} + + template + Singleton(const A1& a1, const A2& a2, const A3& a3) : Base(a1, a2, a3) {} + + template + Singleton(const A1& a1, const A2& a2, const A3& a3, const A4& a4) : Base(a1, a2, a3, a4) {} + + template + Singleton(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) : Base(a1, a2, a3, a4, a5) {} + +}; + +template +Derived* Singleton::singleton() +{ + static Derived x; + return &x; +} + +} + +#endif diff --git a/subclass.cpp b/subclass.cpp new file mode 100644 index 00000000..3f1d25ec --- /dev/null +++ b/subclass.cpp @@ -0,0 +1,398 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#include "subclass.h" +#include "functions.h" +#include "singleton.h" +#include +#include "callback.h" + +namespace py { + +Instance::Instance(PyTypeObject* class_) + : PythonObject(class_) +{ +} + +PyObject* Instance::getattr(const char* name, bool use_special_function) +{ + Ptr local_attribute = m_name_space.get_item(String(name).reference()); + + if (local_attribute.get()) + return local_attribute.release(); + + // Check its class. + PyObject* function = + PyObject_GetAttrString(as_object(this->ob_type), const_cast(name)); + + Ptr class_attribute; + if (!use_special_function || function != 0) + { + // This will throw if the attribute wasn't found + class_attribute = Ptr(function); + } + else + { + // Suspend the error while we try special methods method (if any). +#if 0 + SuspendError suspended_error(SuspendError::discard_new_error); +#else + PyErr_Clear(); +#endif + + // First we try the special method that comes from concatenating + // "__getattr__" and and 2 trailing underscores. This is an + // extension to regular Python class functionality. + const String specific_getattr_name(detail::getattr_string() + name + "__"); + PyObject* getattr_method = PyObject_GetAttr( + as_object(this->ob_type), specific_getattr_name.get()); + + // Use just the first arg to PyEval_CallFunction if found + char* arg_format = const_cast("(O)"); + + // Try for the regular __getattr__ method if not found + if (getattr_method == 0) + { + PyErr_Clear(); + getattr_method = PyObject_GetAttrString( + as_object(this->ob_type), const_cast("__getattr__")); + + // Use both args to PyEval_CallFunction + arg_format = const_cast("(Os)"); + } + + // If there is no such method, throw now. +#if 0 + suspended_error.throw_if_error(); +#else + if (PyErr_Occurred()) + { + PyErr_SetString(PyExc_AttributeError, name); + throw ErrorAlreadySet(); + } +#endif + + // Take ownership of the method + Ptr owner(getattr_method); + + // Call it to get the attribute. + return PyEval_CallFunction(getattr_method, arg_format, this, name); + } + + if (!PyCallable_Check(class_attribute.get())) + { + PyErr_Clear(); + return class_attribute.release(); + } + else + { + return new BoundFunction(Ptr(this, Ptr::borrowed), class_attribute); + } +} + +// Instance::setattr - +// +// Implements the setattr() and delattr() functionality for our own Instance +// objects, using the standard Python interface: if value == 0, we are deleting +// the attribute, and returns 0 unless an error occurred. +int Instance::setattr(const char* name, PyObject* value) +{ + // Try to find an appropriate "specific" setter or getter method, either + // __setattr____(value) or __delattr____(). This is an extension + // to regular Python class functionality. + const String& base_name = value ? detail::setattr_string() : detail::delattr_string(); + const String specific_method_name(base_name + name + "__"); + + Ptr special_method( + PyObject_GetAttr(as_object(this->ob_type), specific_method_name.get()), + Ptr::null_ok); + + PyObject* result_object = 0; + if (special_method.get() != 0) + { + // The specific function was found; call it now. Note that if value is + // not included in the format string, it is ignored. + char* format_string = const_cast(value ? "(OO)" : "(O)"); + result_object = PyEval_CallFunction(special_method.get(), format_string, this, value); + } + else + { + // If not found, try the usual __setattr__(name, value) or + // __delattr__(name) functions. + PyErr_Clear(); + special_method.reset( + PyObject_GetAttr(as_object(this->ob_type), base_name.get()), + Ptr::null_ok); + + if (special_method.get() != 0) + { + // The special function was found; call it now. Note that if value + // is not included in the format string, it is ignored. + char* format_string = const_cast(value ? "(OsO)" : "(Os)"); + result_object = PyEval_CallFunction( + special_method.get(), format_string, this, name, value); + } + } + + // If we found an appropriate special method, handle the return value. + if (special_method.get() != 0) + { + expect_and_absorb_non_null(result_object); + return 0; + } + + PyErr_Clear(); // Nothing was found; clear the python error state + + if (value == 0) // Try to remove the attribute from our name space + { + const int result = PyDict_DelItemString(m_name_space.reference().get(), + const_cast(name)); + if (result < 0) + { + PyErr_Clear(); + PyErr_SetString(PyExc_AttributeError, "delete non-existing instance attribute"); + } + return result; + } + else // Change the specified item in our name space + { + return PyDict_SetItemString(m_name_space.reference().get(), + const_cast(name), value); + } +} + +PyObject* Instance::call(PyObject* args, PyObject* keywords) +{ + return PyEval_CallObjectWithKeywords( + Ptr(getattr("__call__")).get(), // take possession of the result from getattr() + args, keywords); +} + +PyObject* Instance::repr() +{ + return Callback::call_method(this, "__repr__"); +} + +int Instance::compare(PyObject* other) +{ + return Callback::call_method(this, "__cmp__", other); +} + +PyObject* Instance::str() +{ + return Callback::call_method(this, "__str__"); +} + +long Instance::hash() +{ + return Callback::call_method(this, "__hash__"); +} + +int Instance::length() +{ + return Callback::call_method(this, "__len__"); +} + +PyObject* Instance::get_subscript(PyObject* key) +{ + return Callback::call_method(this, "__getitem__", key); +} + +void Instance::set_subscript(PyObject* key, PyObject* value) +{ + if (value == 0) + Callback::call_method(this, "__delitem__", key); + else + Callback::call_method(this, "__setitem__", key, value); +} + +PyObject* Instance::get_slice(int start, int finish) +{ + return Callback::call_method(this, "__getslice__", start, finish); +} + +void Instance::set_slice(int start, int finish, PyObject* value) +{ + if (value == 0) + Callback::call_method(this, "__delslice__", start, finish); + else + Callback::call_method(this, "__setslice__", start, finish, value); +} + +namespace { + struct NamedCapability + { + const char* name; + TypeObjectBase::Capability capability; + }; + + const NamedCapability enablers[] = + { + { "__hash__", TypeObjectBase::hash }, + { "__cmp__", TypeObjectBase::compare }, + { "__repr__", TypeObjectBase::repr }, + { "__str__", TypeObjectBase::str }, + { "__call__", TypeObjectBase::call }, + { "__getattr__", TypeObjectBase::getattr }, + { "__setattr__", TypeObjectBase::setattr }, + { "__len__", TypeObjectBase::mapping_length }, + { "__len__", TypeObjectBase::sequence_length }, + { "__getitem__", TypeObjectBase::mapping_subscript }, + { "__getitem__", TypeObjectBase::sequence_item }, + { "__setitem__", TypeObjectBase::mapping_ass_subscript }, + { "__setitem__", TypeObjectBase::sequence_ass_item }, + { "__delitem__", TypeObjectBase::mapping_ass_subscript }, + { "__delitem__", TypeObjectBase::sequence_ass_item }, + { "__getslice__", TypeObjectBase::sequence_slice }, + { "__setslice__", TypeObjectBase::sequence_ass_slice }, + { "__delslice__", TypeObjectBase::sequence_ass_slice } + }; + + bool is_prefix(const char* s1, const char* s2) + { + while (*s1 != 0 && *s2 != 0 && *s1 == *s2) + ++s1, ++s2; + return *s1 == 0; + } + + bool is_special_name(const char* name) + { + if (name[0] != '_' || name[1] != '_' || name[2] == 0 || name[3] == 0) + return false; + + std::size_t name_length = PY_CSTD_::strlen(name); + return name[name_length - 1] == '_' && name[name_length - 2] == '_'; + } +} + +// Enable any special methods which are enabled in the base class. +void enable_special_methods(TypeObjectBase* derived, const Tuple& bases, const Dict& name_space) +{ + detail::AllMethods all_methods; + PY_CSTD_::memset(&all_methods, 0, sizeof(all_methods)); + + for (std::size_t i = 0; i < bases.size(); ++i) + { + PyTypeObject* base = Downcast(bases[i].get()); + + for (std::size_t n = 0; n < detail::num_capabilities; ++n) + { + detail::add_capability(n, derived, all_methods, base); + } + } + + Ptr keys = name_space.keys(); + for (std::size_t j = 0, len = PyList_GET_SIZE(keys.get()); j < len; ++j) + { + const char* name = PyString_AsString(PyList_GetItem(keys.get(), j)); + + if (!is_special_name(name)) + continue; + + for (std::size_t i = 0; i < PY_ARRAY_LENGTH(enablers); ++i) + { + if (is_prefix(enablers[i].name + 2, name + 2)) + { + detail::add_capability(enablers[i].capability, derived, all_methods, 0); + } + } + } + + // Now replace those pointers with a persistent copy + using detail::UniquePodSet; + if (derived->tp_as_buffer) + derived->tp_as_buffer = UniquePodSet::instance().get(*derived->tp_as_buffer); + + if (derived->tp_as_number) + derived->tp_as_number = UniquePodSet::instance().get(*derived->tp_as_number); + + if (derived->tp_as_sequence) + derived->tp_as_sequence = UniquePodSet::instance().get(*derived->tp_as_sequence); + + if (derived->tp_as_mapping) + derived->tp_as_mapping = UniquePodSet::instance().get(*derived->tp_as_mapping); +} + +// Enable the special handler for methods of the given name, if any. +void enable_named_method(TypeObjectBase* type_object, const char* name) +{ + const std::size_t num_enablers = sizeof(enablers) / sizeof(enablers[0]); + + // Make sure this ends with "__" since we'll only compare the head of the + // string. This is done to make the __getattr____/__setattr____ + // extension work. + if (!is_special_name(name)) + return; + + for (std::size_t i = 0; i < num_enablers; ++i) + { + if (is_prefix(enablers[i].name + 2, name + 2)) + { + type_object->enable(enablers[i].capability); + } + } +} + +void add_current_module_name(Dict& name_space) +{ + static String module_key("__module__", String::interned); + static String name_key("__name__", String::interned); + + Ptr existing_value = name_space.get_item(module_key); + if (existing_value.get() == 0) + { + PyObject* globals = PyEval_GetGlobals(); + if (globals != 0) // Why don't we throw in this case? Who knows? This is + { // what Python does for class objects! + PyObject* module_name = PyDict_GetItem(globals, name_key.get()); + if (module_name != 0) + { + name_space[module_key] = Ptr(module_name, Ptr::borrowed); + } + } + } +} + +void adjust_slice_indices(PyObject* instance, int& start, int& finish) +{ + int length = Callback::call_method(instance, "__len__"); + + // This is standard Python class behavior. + if (start < 0) + start += length; + if (finish < 0) + finish += length; + + // This is not + if (start < 0) + start = 0; + if (finish < 0) + finish = 0; +} + +namespace detail { +const String& setattr_string() +{ + static String x("__setattr__", String::interned); + return x; +} + +const String& getattr_string() +{ + static String x("__getattr__", String::interned); + return x; +} + +const String& delattr_string() +{ + static String x("__delattr__", String::interned); + return x; +} +} + +} // namespace py diff --git a/subclass.h b/subclass.h new file mode 100644 index 00000000..4d745bc0 --- /dev/null +++ b/subclass.h @@ -0,0 +1,371 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef SUBCLASS_DWA051500_H_ +# define SUBCLASS_DWA051500_H_ + +# include "pyconfig.h" +# include "newtypes.h" +# include "objects.h" +# include "singleton.h" +# include +# include "py.h" +# include "callback.h" + +namespace py { + +// A simple type which acts something like a built-in Python class instance. +// TODO: implement all the special methods, like __call__, __getattr__, etc., +// and the other special attributes, like __dict__. +class Instance : public PythonObject +{ + public: + Instance(PyTypeObject* class_); + + // Standard Python functions. + PyObject* repr(); + int compare(PyObject*); + PyObject* str(); + long hash(); + PyObject* call(PyObject* args, PyObject* keywords); + PyObject* getattr(const char* name, bool use_special_function = true); + int setattr(const char* name, PyObject* value); + + // Mapping methods + int length(); + PyObject* get_subscript(PyObject* key); + void set_subscript(PyObject* key, PyObject* value); + + // Sequence methods + PyObject* get_slice(int start, int finish); + void set_slice(int start, int finish, PyObject* value); + + private: // noncopyable, without the size bloat + Instance(const Instance&); + void operator=(const Instance&); + + private: + Dict m_name_space; +}; + +template class MetaClass; + +// A type which acts a lot like a built-in Python class. T is the instance type, +// so Class is a very simple "class-alike". +template +class Class + : public Getattrable > > +{ + public: + Class(MetaClass* meta_class, String name, Tuple bases, const Dict& name_space); + + Tuple bases() const; + String name() const; + Dict& dict(); + + // Standard Python functions. + PyObject* getattr(const char* name); + int setattr(const char* name, PyObject* value); + PyObject* call(PyObject* args, PyObject* keywords); + + protected: + void add_base(Ptr base); + + private: // Implement mapping methods on instances + PyObject* instance_repr(PyObject*) const; + int instance_compare(PyObject*, PyObject* other) const; + PyObject* instance_str(PyObject*) const; + long instance_hash(PyObject*) const; + int instance_mapping_length(PyObject*) const; + PyObject* instance_mapping_subscript(PyObject*, PyObject*) const; + int instance_mapping_ass_subscript(PyObject*, PyObject*, PyObject*) const; + + private: // Implement sequence methods on instances + int instance_sequence_length(PyObject*) const; + PyObject* instance_sequence_item(PyObject* instance, int n) const; + int instance_sequence_ass_item(PyObject* instance, int n, PyObject* value) const; + PyObject* instance_sequence_slice(PyObject*, int start, int finish) const; + int instance_sequence_ass_slice(PyObject*, int start, int finish, PyObject* value) const; + + private: // Miscellaneous "special" methods + PyObject* instance_call(PyObject* instance, PyObject* args, PyObject* keywords) const; + + private: // noncopyable, without the size bloat + Class(const Class&); + void operator=(const Class&); + + private: + String m_name; + Tuple m_bases; + Dict m_name_space; +}; + +// Don't really need to be friends, but are essentially part of the Class interface. +// These operate on TypeObjectBase just to save on code space. +void enable_special_methods(TypeObjectBase*, const Tuple& bases, const Dict& name_space); +void enable_named_method(TypeObjectBase*, const char*); + +// The type of a Class object. +template +class MetaClass + : public Callable > > > >, + boost::noncopyable +{ + public: + MetaClass(); + + // Standard Python functions. + PyObject* call(PyObject* args, PyObject* keywords); + private: + + struct TypeObject + : Singleton > > + { + TypeObject() : SingletonBase(&PyType_Type) {} + }; +}; + +// Add the name of the module currently being loaded to the name_space with the +// key "__module__". If no module is being loaded, or if name_space already has +// a key "__module", has no effect. This is not really a useful public +// interface; it's just used for Class<>::Class() below. +void add_current_module_name(Dict&); + +// +// Member function implementations. +// +template +MetaClass::MetaClass() + : Properties(TypeObject::singleton()) +{ +} + +template +Class::Class(MetaClass* meta_class, String name, Tuple bases, const Dict& name_space) + : Properties(meta_class, name.c_str()), + m_name(name), + m_bases(bases), + m_name_space(name_space) +{ + add_current_module_name(m_name_space); + enable_special_methods(this, bases, name_space); +} + +template +String Class::name() const +{ + return m_name; +} + +template +PyObject* Class::getattr(const char* name) +{ + Ptr local_attribute = m_name_space.get_item(String(name).reference()); + + if (local_attribute.get()) + return local_attribute.release(); + + // In case there are no bases... + PyErr_SetString(PyExc_AttributeError, name); + + // Check bases + for (std::size_t i = 0; i < m_bases.size(); ++i) + { + if (PyErr_ExceptionMatches(PyExc_AttributeError)) + PyErr_Clear(); // we're going to try a base class + else if (PyErr_Occurred()) + break; // Other errors count, though! + + PyObject* base_attribute = PyObject_GetAttrString(m_bases[i].get(), const_cast(name)); + if (base_attribute != 0) + return base_attribute; + } + return 0; +} + +template +int Class::setattr(const char* name, PyObject* value) +{ + if (PyCallable_Check(value)) + enable_named_method(this, name); + + return PyDict_SetItemString( + m_name_space.reference().get(), const_cast(name), value); +} + +template +PyObject* Class::call(PyObject* args, PyObject* keywords) +{ + PyPtr result(new T(this)); + + // Getting the init function off the result instance should result in a + // bound method. + PyObject* const init_function = result->getattr("__init__", false); + + if (init_function == 0) + { + if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); // no __init__? That's legal. + } + else { + return 0; // Something else? Keep the error + } + } + else + { + // Manage the reference to the bound function + Ptr init_function_holder(init_function); + + // Declare a Ptr to manage the result of calling __init__ (which should be None). + Ptr init_result( + PyEval_CallObjectWithKeywords(init_function, args, keywords)); + } + + return result.release(); +} + +template +PyObject* Class::instance_repr(PyObject* instance) const +{ + return Downcast(instance)->repr(); +} + +template +int Class::instance_compare(PyObject* instance, PyObject* other) const +{ + return Downcast(instance)->compare(other); +} + +template +PyObject* Class::instance_str(PyObject* instance) const +{ + return Downcast(instance)->str(); +} + +template +long Class::instance_hash(PyObject* instance) const +{ + return Downcast(instance)->hash(); +} + +template +int Class::instance_mapping_length(PyObject* instance) const +{ + return Downcast(instance)->length(); +} + +template +int Class::instance_sequence_length(PyObject* instance) const +{ + return Downcast(instance)->length(); +} + +template +PyObject* Class::instance_mapping_subscript(PyObject* instance, PyObject* key) const +{ + return Downcast(instance)->get_subscript(key); +} + +template +PyObject* Class::instance_sequence_item(PyObject* instance, int n) const +{ + Ptr key(to_python(n)); + return Downcast(instance)->get_subscript(key.get()); +} + +template +int Class::instance_sequence_ass_item(PyObject* instance, int n, PyObject* value) const +{ + Ptr key(to_python(n)); + Downcast(instance)->set_subscript(key.get(), value); + return 0; +} + +template +int Class::instance_mapping_ass_subscript(PyObject* instance, PyObject* key, PyObject* value) const +{ + Downcast(instance)->set_subscript(key, value); + return 0; +} + +void adjust_slice_indices(PyObject* instance, int& start, int& finish); + +template +PyObject* Class::instance_sequence_slice(PyObject* instance, int start, int finish) const +{ + adjust_slice_indices(instance, start, finish); + return Downcast(instance)->get_slice(start, finish); +} + +template +int Class::instance_sequence_ass_slice(PyObject* instance, int start, int finish, PyObject* value) const +{ + adjust_slice_indices(instance, start, finish); + Downcast(instance)->set_slice(start, finish, value); + return 0; +} + +template +PyObject* Class::instance_call(PyObject* instance, PyObject* args, PyObject* keywords) const +{ + return Downcast(instance)->call(args, keywords); +} + +template +Dict& Class::dict() +{ + return m_name_space; +} + +template +Tuple Class::bases() const +{ + return m_bases; +} + +template +void Class::add_base(Ptr base) +{ + Tuple new_bases(m_bases.size() + 1); + for (std::size_t i = 0; i < m_bases.size(); ++i) + new_bases.set_item(i, m_bases[i]); + new_bases.set_item(m_bases.size(), base); + m_bases = new_bases; +} + +template +PyObject* MetaClass::call(PyObject* args, PyObject* /*keywords*/) +{ + PyObject* name; + PyObject* bases; + PyObject* name_space; + + if (!PyArg_ParseTuple(args, const_cast("O!O!O!"), + &PyString_Type, &name, + &PyTuple_Type, &bases, + &PyDict_Type, &name_space)) + { + return 0; + } + + return as_object( + new Class(this, String(Ptr(name, Ptr::borrowed)), + Tuple(Ptr(bases, Ptr::borrowed)), + Dict(Ptr(name_space, Ptr::borrowed))) + ); +} + +namespace detail { +const String& setattr_string(); +const String& getattr_string(); +const String& delattr_string(); +} + + +} // namespace py +#endif diff --git a/subclass_d.cpp b/subclass_d.cpp new file mode 100644 index 00000000..cfd27dc2 --- /dev/null +++ b/subclass_d.cpp @@ -0,0 +1,2 @@ +#define DEBUG_PYTHON +#include "subclass.cpp" diff --git a/test_example1.py b/test_example1.py new file mode 100644 index 00000000..0e3a9a18 --- /dev/null +++ b/test_example1.py @@ -0,0 +1,50 @@ +r''' +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +That's it! If we build this shared library and put it on our PYTHONPATH we can +now access our C++ class and function from Python. + + >>> import hello + >>> hi_world = hello.world(3) + >>> hi_world.get() + 'hi, world' + >>> hello.length(hi_world) + 9 + +We can even make a subclass of hello.world: + + + >>> class my_subclass(hello.world): + ... def get(self): + ... return 'hello, world' + ... + >>> y = my_subclass(2) + >>> y.get() + 'hello, world' + +Pretty cool! You can't do that with an ordinary Python extension type! + + >>> hello.length(y) + 9 + +Of course, you may now have a slightly empty feeling in the pit of your little +pythonic stomach. Perhaps you feel your subclass deserves to have a length() of +12? If so, read on... +''' +from hello import * + +def run(args = None): + if args is not None: + import sys + sys.argv = args + import doctest, test_example1 + doctest.testmod(test_example1) + +if __name__ == '__main__': + run() diff --git a/test_extclass.py b/test_extclass.py new file mode 100644 index 00000000..1912b237 --- /dev/null +++ b/test_extclass.py @@ -0,0 +1,423 @@ +r''' +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +Automatic checking of the number and type of arguments. Foo's constructor takes +a single long parameter. + + >>> ext = Foo() + Traceback (innermost last): + File "", line 1, in ? + TypeError: function requires exactly 1 argument; 0 given + + >>> ext = Foo('foo') + Traceback (innermost last): + File "", line 1, in ? + TypeError: illegal argument type for built-in operation + + >>> ext = Foo(1) + +Call a virtual function. This call takes a trip into C++ where +FooCallback::add_len() looks up the Python "add_len" attribute and finds the +wrapper for FooCallback::default_add_len(), which in turn calls Foo::add_len(). + + >>> ext.add_len('hello') + 6 + >>> ext.set(3) + >>> ext.add_len('hello') + 8 + +Call a pure virtual function which should have been overridden, but was not. + + >>> ext.call_pure() + Traceback (innermost last): + File "", line 1, in ? + AttributeError: pure + +We can subclass Foo. + + >>> class Subclass(Foo): + ... def __init__(self, seq): + ... Foo.__init__(self, len(seq)) + ... + ... def pure(self): + ... return 'not pure anymore!' + ... + ... def get(self): + ... return Foo.add_len(self, '') + ... + ... def add_len(self, s): + ... print 'called add_len()' + ... return self.get() + len(s) + ... + >>> b = Subclass('yippee') + >>> b.get() + 6 + >>> b.mumble() + 'mumble' + >>> b.call_pure() + 'not pure anymore!' + +If no __init__ function is defined, the one from the base class takes effect, just +like in a Python class. + + >>> class DemonstrateInitPassthru(Foo): pass + ... + >>> q = DemonstrateInitPassthru(1) + >>> q.add_len("x") + 2 + +If we don't initialize the base class, we'll get a RuntimeError when we try to +use its methods. The test illustrates the kind of error to expect. + + >>> class BadSubclass(Foo): + ... def __init__(self): pass + ... + >>> barf = BadSubclass() + >>> barf.set(4) + Traceback (innermost last): + ... + RuntimeError: __init__ function for extension class 'Foo' was never called. + +Here we are tesing that the simple definition procedure used in the C++ demo +file for classes without any virtual functions actually worked. + + >>> bar = Bar(3, 4) + >>> bar.first() + 3 + >>> bar.second() + 4 + >>> baz = Baz() + +We can actually return the wrapped classes by value + + >>> baz.pass_bar(bar).first() + 3 + >>> bar.pass_baz(baz) is baz # A copy of the return value is made. + 0 + >>> type(bar.pass_baz(baz)) is type(baz) + 1 + +And, yes, we can multiply inherit from these classes. + + >>> class MISubclass(Subclass, Bar): + ... def __init__(self, s): + ... Subclass.__init__(self, s) + ... Bar.__init__(self, 0, len(s)) + ... + >>> mi = MISubclass('xx') + >>> mi.first() + 0 + >>> mi.second() + 2 + >>> mi.mumble() + 'mumble' + +Any object whose class is derived from Bar can be passed to a function expecting +a Bar parameter: + + >>> baz.pass_bar(mi).first() + 0 + +But objects not derived from Bar cannot: + + >>> baz.pass_bar(baz) + Traceback (innermost last): + ... + TypeError: extension class 'Baz' is not derived from 'Bar'. + +The clone function on Baz returns a smart pointer; we wrap it into an +ExtensionInstance and make it look just like any other Baz instance. + + >>> baz_clone = baz.clone() + >>> baz_clone.pass_bar(mi).first() + 0 + +Functions expecting an std::auto_ptr parameter will not accept a raw Baz + + >>> try: baz.eat_baz(Baz()) + ... except RuntimeError, err: + ... assert re.match("Object of extension class 'Baz' does not wrap <.*>.", + ... str(err)) + +We can pass std::auto_ptr where it is expected + + >>> baz.eat_baz(baz_clone) + +And if the auto_ptr has given up ownership? + + # MSVC6 ships with an outdated auto_ptr that doesn't get zeroed out when it + # gives up ownership. If you are using MSVC6 without the new Dinkumware + # library, SGI STL or the STLport, expect this test to crash unless you put + # --broken-auto-ptr on the command line. + >>> if not '--broken-auto-ptr' in sys.argv: + ... try: baz_clone.clone() + ... except RuntimeError, err: + ... assert re.match('Converting from python, pointer or smart pointer to <.*> is NULL.', str(err)) + +Polymorphism also works: + + >>> polymorphic_foo = baz.create_foo() + >>> polymorphic_foo.call_pure() + 'this was never pure!' + >>> baz.get_foo_value(polymorphic_foo) + 1000 + +Special member functions in action + + >>> m = StringMap() + +__getitem__() + >>> m[1] + Traceback (innermost last): + File "", line 1, in ? + KeyError: 1 + +__setitem__() + + >>> m[1] = 'hello' + +__getitem__() + >>> m[1] + 'hello' + +__delitem__() + >>> del m[1] + >>> m[1] # prove that it's gone + Traceback (innermost last): + File "", line 1, in ? + KeyError: 1 + +__delitem__() + >>> del m[2] + Traceback (innermost last): + File "", line 1, in ? + KeyError: 2 + +__length__() + >>> len(m) + 0 + >>> m[3] = 'farther' + >>> len(m) + 1 + +Check for sequence/mapping confusion: + >>> for x in m: + ... print x + ... + Traceback (innermost last): + File "", line 1, in ? + KeyError: 0 + +Overloading tests: + >>> r = Range(3) + >>> print str(r) + (3, 3) + >>> r.start + 3 + >>> r.finish + 3 + >>> r.__len__() + 0 + >>> r.__len__(4) + >>> r.finish + 7 + >>> try: r = Range('yikes') + ... except TypeError, e: + ... assert re.match( + ... 'No overloaded functions match [(]Range, string[)]\. Candidates are:\n.*\n.*', + ... str(e)) + +Sequence tests: + >>> len(Range(3, 10)) + 7 + + >>> map(lambda x:x, Range(3, 10)) + [3, 4, 5, 6, 7, 8, 9] + + >>> map(lambda x:x, Range(3, 10)[-2:]) + [8, 9] + + >>> map(lambda x:x, Range(3, 10)[:-4]) + [3, 4, 5] + + >>> map(lambda x:x, Range(3, 10)[4:]) + [7, 8, 9] + + >>> map(lambda x:x, Range(3, 10)[4:100]) + [7, 8, 9] + + >>> map(lambda x:x, Range(3, 10)[20:]) + [] + + >>> map(lambda x:x, Range(3, 10)[0:4]) + [3, 4, 5, 6] + +delete non-existent attribute: + del m.foobar + Traceback (innermost last): + File "", line 1, in ? + AttributeError: delete non-existing instance attribute + +Testing __getattr__ and __getattr__: + + >>> n = IntPair(1, 2) + >>> n.first + 1 + >>> n.second + 2 + >>> n.third + Traceback (innermost last): + File "", line 1, in ? + AttributeError: third + +Testing __setattr__ and __setattr__: + >>> n.first = 33 # N.B __setattr__first sets first to + >>> n.first # the negative of its argument. + -33 + >>> n.second = 66 + >>> n.second + 66 + +Testing __delattr__ and __delattr__: + >>> del n.first + Traceback (innermost last): + File "", line 1, in ? + AttributeError: first can't be deleted! + >>> del n.second + Traceback (innermost last): + File "", line 1, in ? + AttributeError: Attributes can't be deleted! + >>> del n.third + Traceback (innermost last): + File "", line 1, in ? + AttributeError: Attributes can't be deleted! + + # Now show that we can override it. + + >>> class IntTriple(IntPair): + ... def __getattr__(self, s): + ... if s in ['first', 'second']: + ... return IntPair.__getattr__(self, s) + ... elif s == 'third': + ... return 3 + ... else: + ... raise AttributeError(s) + ... + ... # Also show that __setattr__ is supported + ... def __setattr__(self, name, value): + ... raise AttributeError('no writable attributes') + ... + >>> p = IntTriple(0, 1) + >>> p.first + 0 + >>> p.second + 1 + >>> p.third + 3 + >>> p.bax + Traceback (innermost last): + File "", line 1, in ? + AttributeError: bax + >>> p.third = 'yes' + Traceback (innermost last): + File "", line 1, in ? + AttributeError: no writable attributes + >>> del p.third + Traceback (innermost last): + File "", line 1, in ? + AttributeError: Attributes can't be deleted! + +demonstrate def_readonly, def_read_write: + >>> sp = StringPair("hello", "world") + >>> sp.first # first is read-only + 'hello' + >>> first_string(sp) # prove that we're not just looking in sp's __dict__ + 'hello' + >>> sp.first = 'hi' # we're not allowed to change it + Traceback (innermost last): + File "", line 1, in ? + AttributeError: 'first' attribute is read-only + >>> first_string(sp) # prove that it hasn't changed + 'hello' + + >>> sp.second # second is read/write + 'world' + >>> second_string(sp) + 'world' + >>> sp.second = 'universe' # set the second attribute + >>> sp.second + 'universe' + >>> second_string(sp) # this proves we didn't just set it in sp's __dict__ + 'universe' + +some __str__ and __repr__ tests: + >>> sp + ('hello', 'universe') + >>> repr(sp) + "('hello', 'universe')" + >>> str(sp) + "('hello', 'universe')" + + Range has a __str__ function but not a __repr__ function + >>> range = Range(5, 20) + >>> str(range) + '(5, 20)' + >>> assert re.match('', repr(range)) + + +__hash__ and __cmp__ tests: + # Range has both __hash__ and __cmp__, thus is hashable + >>> colors = { Range(3,4): 'blue', Range(7,9): 'red' } + >>> colors[Range(3,4)] + 'blue' + + # StringPair has only __cmp__ + >>> { StringPair('yo', 'eddy'): 1 } + Traceback (innermost last): + File "", line 1, in ? + TypeError: unhashable type + + # But it can be sorted + >>> stringpairs = [ StringPair('yo', 'eddy'), StringPair('yo', 'betty'), sp ] + >>> stringpairs.sort() + >>> stringpairs + [('hello', 'universe'), ('yo', 'betty'), ('yo', 'eddy')] + +make_pair is a global function in the module. + + >>> couple = make_pair(3,12) + >>> couple.first + 3 + >>> couple.second + 12 + +Testing __call__: + >>> couple2 = make_pair(3, 7) + >>> comparator = CompareIntPair() + >>> comparator(couple, couple) + 0 + >>> comparator(couple, couple2) + 0 + >>> comparator(couple2, couple) + 1 +''' + +from demo import * +import string +import re +import sys + +def run(args = None): + if args is not None: + sys.argv = args + import doctest, test_extclass + doctest.testmod(test_extclass) + +if __name__ == '__main__': + run() diff --git a/todo.txt b/todo.txt new file mode 100644 index 00000000..d8250858 --- /dev/null +++ b/todo.txt @@ -0,0 +1,63 @@ +Better python and C++ exception handling/error reporting. +long long support +use Python generic numeric coercion in from_python() for C++ numeric types +Document error-handling +Consider renaming PyPtr to Reference. +Report Cygwin linker memory issues +handle more arguments +Remove one level of indirection on type objects (no vtbl?). +Much more testing, especially of things in objects.h +Make multiple inheritance from real Python classes work. +Handle polymorphism (passing a Wrapped as a Base*). +Specializations of Caller<> for commmon combinations of argument types (?) + +Documentation: + special member functions + adding conversions for fundamental types + generic conversions for template types (with partial spec). + extending multiple-argument support. + differences between Python classes and ExtensionClasses + additional capabilities of ExtensionClasses + exception handling + building + dealing with non-const reference/pointer parameters + +Boost remarks: + + > > One of us is completely nuts ;->. How can I move the test + > > (is_prefix(enablers[i].name + 2, name + 2)) outside the loop if it + depends + > > on the loop index, i? + > > + > name += 2; + > for() + > { + > if (is_prefix(enablers[i].name + 2, name)) + > } + + I see now. I guess I should stop pussyfooting and either go for optimization + or clarity here, eh? + + ------ + + > Re: Dict + > Why abbreviate this? Code is read 5 or 6 times for every time its + > written. The few extra characters don't affect compile time or program + > speed. It's part of my personal goal of write what you mean, name them + what + > they are. + + I completely agree. Abbrevs rub me the wrong way, 2 ;-> + + ------- + + + + +Later: + keyword and varargs? + Put explicit Type<> arguments at the beginnings of overloads, to make them look more like template instance specifications. + +Known bugs + can't handle 'const void' return values + Who returns 'const void'? I did it once, by mistake ;) diff --git a/vc6_prj/ReadMe.txt b/vc6_prj/ReadMe.txt new file mode 100644 index 00000000..a6b8aea4 --- /dev/null +++ b/vc6_prj/ReadMe.txt @@ -0,0 +1,41 @@ +======================================================================== + DYNAMIC LINK LIBRARY : vc6_prj +======================================================================== + + +AppWizard has created this vc6_prj DLL for you. + +This file contains a summary of what you will find in each of the files that +make up your vc6_prj application. + +vc6_prj.dsp + This file (the project file) contains information at the project level and + is used to build a single project or subproject. Other users can share the + project (.dsp) file, but they should export the makefiles locally. + +vc6_prj.cpp + This is the main DLL source file. + + When created, this DLL does not export any symbols. As a result, it + will not produce a .lib file when it is built. If you wish this project + to be a project dependency of some other project, you will either need to + add code to export some symbols from the DLL so that an export library + will be produced, or you can check the "doesn't produce lib" checkbox in + the Linker settings page for this project. + +///////////////////////////////////////////////////////////////////////////// +Other standard files: + +StdAfx.h, StdAfx.cpp + These files are used to build a precompiled header (PCH) file + named vc6_prj.pch and a precompiled types file named StdAfx.obj. + + +///////////////////////////////////////////////////////////////////////////// +Other notes: + +AppWizard uses "TODO:" to indicate parts of the source code you +should add to or customize. + + +///////////////////////////////////////////////////////////////////////////// diff --git a/vc6_prj/StdAfx.cpp b/vc6_prj/StdAfx.cpp new file mode 100644 index 00000000..5a711fb5 --- /dev/null +++ b/vc6_prj/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// vc6_prj.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/vc6_prj/StdAfx.h b/vc6_prj/StdAfx.h new file mode 100644 index 00000000..9bd983af --- /dev/null +++ b/vc6_prj/StdAfx.h @@ -0,0 +1,24 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__A41B37EF_17F7_406A_95AC_FE67BDB05B1E__INCLUDED_) +#define AFX_STDAFX_H__A41B37EF_17F7_406A_95AC_FE67BDB05B1E__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + + +// Insert your headers here +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include + +// TODO: reference additional headers your program requires here + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__A41B37EF_17F7_406A_95AC_FE67BDB05B1E__INCLUDED_) diff --git a/vc6_prj/test_demo.py b/vc6_prj/test_demo.py new file mode 100644 index 00000000..eb68e4a2 --- /dev/null +++ b/vc6_prj/test_demo.py @@ -0,0 +1,6 @@ +import demo # Get demo imported now so test_extclass won't look in its own directory +import os +os.chdir('..') +import test_extclass +test_extclass.run(['-v', '--broken-auto-ptr']) + diff --git a/vc6_prj/test_hello.py b/vc6_prj/test_hello.py new file mode 100644 index 00000000..6da3c4bc --- /dev/null +++ b/vc6_prj/test_hello.py @@ -0,0 +1,6 @@ +import hello # Get demo imported now so test_extclass won't look in its own directory +import os +os.chdir('..') +import test_example1 +test_example1.run(["-v"]) + diff --git a/vc6_prj/vc6_prj.cpp b/vc6_prj/vc6_prj.cpp new file mode 100644 index 00000000..7d098f47 --- /dev/null +++ b/vc6_prj/vc6_prj.cpp @@ -0,0 +1,13 @@ +// vc6_prj.cpp : Defines the entry point for the DLL application. +// + +#include "stdafx.h" + +BOOL APIENTRY DllMain( HANDLE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + return TRUE; +} + diff --git a/vc6_prj/vc6_prj.dsp b/vc6_prj/vc6_prj.dsp new file mode 100644 index 00000000..1348d8da --- /dev/null +++ b/vc6_prj/vc6_prj.dsp @@ -0,0 +1,157 @@ +# Microsoft Developer Studio Project File - Name="vc6_prj" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=vc6_prj - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "vc6_prj.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "vc6_prj.mak" CFG="vc6_prj - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "vc6_prj - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "vc6_prj - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "vc6_prj - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "VC6_PRJ_EXPORTS" /Yu"stdafx.h" /FD /c +# ADD CPP /nologo /MT /W3 /GR /GX /Ox /Ot /Og /Oi /Os /Gy /I "c:\boost" /I "c:\tools\python\include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "VC6_PRJ_EXPORTS" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /out:"demo.dll" /libpath:"c:/tools/python/libs" +# SUBTRACT LINK32 /pdb:none /debug + +!ELSEIF "$(CFG)" == "vc6_prj - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "VC6_PRJ_EXPORTS" /Yu"stdafx.h" /FD /GZ /c +# ADD CPP /nologo /MTd /Gm /GR /GX /ZI /Od /I "c:\boost" /I "c:\tools\python\include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "VC6_PRJ_EXPORTS" /FR /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /out:"demo_d.dll" /pdbtype:sept /libpath:"c:/tools/python/src/pcbuild" + +!ENDIF + +# Begin Target + +# Name "vc6_prj - Win32 Release" +# Name "vc6_prj - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\extclass.cpp + +!IF "$(CFG)" == "vc6_prj - Win32 Release" + +!ELSEIF "$(CFG)" == "vc6_prj - Win32 Debug" + +# ADD CPP /I "c:\tools\python\src\include" /D "DEBUG_PYTHON" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\extclass_demo.cpp +# End Source File +# Begin Source File + +SOURCE=..\functions.cpp +# End Source File +# Begin Source File + +SOURCE=..\init_function.cpp +# End Source File +# Begin Source File + +SOURCE=..\module.cpp +# End Source File +# Begin Source File + +SOURCE=..\newtypes.cpp +# End Source File +# Begin Source File + +SOURCE=..\objects.cpp +# End Source File +# Begin Source File + +SOURCE=..\py.cpp +# End Source File +# Begin Source File + +SOURCE=..\subclass.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\StdAfx.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# Begin Source File + +SOURCE=.\ReadMe.txt +# End Source File +# End Target +# End Project diff --git a/vc6_prj/vc6_prj.dsw b/vc6_prj/vc6_prj.dsw new file mode 100644 index 00000000..0e48beed --- /dev/null +++ b/vc6_prj/vc6_prj.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "vc6_prj"=.\vc6_prj.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/vc6_prj/vc6_prj.opt b/vc6_prj/vc6_prj.opt new file mode 100644 index 00000000..fa7645c3 Binary files /dev/null and b/vc6_prj/vc6_prj.opt differ diff --git a/wrap_python.h b/wrap_python.h new file mode 100644 index 00000000..7c6dd5be --- /dev/null +++ b/wrap_python.h @@ -0,0 +1,61 @@ +#ifdef _DEBUG +# ifndef DEBUG_PYTHON +# undef _DEBUG // Don't let Python force the debug library just because we're debugging. +# define DEBUG_UNDEFINED_FROM_WRAP_PYTHON_H +# endif +#endif + +// +// Some things we need in order to get Python.h to work with compilers other +// than MSVC on Win32 +// +#if defined(_WIN32) +# ifdef __GNUC__ + +typedef int pid_t; +# define WORD_BIT 32 +# define hypot _hypot +# include +# define HAVE_CLOCK +# define HAVE_STRFTIME +# define HAVE_STRERROR +# define NT_THREADS +# define WITH_THREAD +# ifndef NETSCAPE_PI +# define USE_SOCKET +# endif + +# ifdef USE_DL_IMPORT +# define DL_IMPORT(RTYPE) __declspec(dllimport) RTYPE +# endif + +# ifdef USE_DL_EXPORT +# define DL_IMPORT(RTYPE) __declspec(dllexport) RTYPE +# define DL_EXPORT(RTYPE) __declspec(dllexport) RTYPE +# endif + +# define HAVE_LONG_LONG 1 +# define LONG_LONG long long + +# elif defined(__MWERKS__) + +# ifndef _MSC_VER +# define PY_MSC_VER_DEFINED_FROM_WRAP_PYTHON_H 1 +# define _MSC_VER 900 +# endif + +# endif + +#endif // _WIN32 + +#include + +#ifdef PY_MSC_VER_DEFINED_FROM_WRAP_PYTHON_H +# undef _MSC_VER +#endif + +#ifdef DEBUG_UNDEFINED_FROM_WRAP_PYTHON_H +# undef DEBUG_UNDEFINED_FROM_WRAP_PYTHON_H +# define _DEBUG +#endif +