Author: Wim Lavrijsen <wlavrij...@lbl.gov> Branch: reflex-support Changeset: r44632:c241b111c812 Date: 2011-06-01 22:14 -0700 http://bitbucket.org/pypy/pypy/changeset/c241b111c812/
Log: first steps towards STL support diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -16,6 +16,11 @@ ^pypy/module/cpyext/test/.+\.obj$ ^pypy/module/cpyext/test/.+\.manifest$ ^pypy/module/test_lib_pypy/ctypes_tests/.+\.o$ +^pypy/module/cppyy/src/.+\.o$ +^pypy/module/cppyy/src/.+\.errors$ +^pypy/module/cppyy/test/.+_rflx\.cpp$ +^pypy/module/cppyy/test/.+\.so$ +^pypy/module/cppyy/test/.+\.exe$ ^pypy/doc/.+\.html$ ^pypy/doc/config/.+\.rst$ ^pypy/doc/basicblock\.asc$ diff --git a/pypy/module/cppyy/converter.py b/pypy/module/cppyy/converter.py --- a/pypy/module/cppyy/converter.py +++ b/pypy/module/cppyy/converter.py @@ -393,6 +393,13 @@ except KeyError, k: pass + # 3) accept const ref as by value + if compound and compound[len(compound)-1] == "&": + try: + return _converters[clean_name](space, -1) + except KeyError: + pass + # 5) generalized cases (covers basically all user classes) cpptype = interp_cppyy.type_byname(space, clean_name) if compound == "*": diff --git a/pypy/module/cppyy/executor.py b/pypy/module/cppyy/executor.py --- a/pypy/module/cppyy/executor.py +++ b/pypy/module/cppyy/executor.py @@ -27,7 +27,7 @@ class PtrTypeExecutor(FunctionExecutor): _immutable_ = True - typecode = '' + typecode = 'P' def execute(self, space, func, cppthis, num_args, args): lresult = capi.c_call_l(func.cpptype.handle, func.method_index, cppthis, num_args, args) @@ -140,8 +140,17 @@ def get_executor(space, name): + # Matching of 'name' to an executor factory goes through up to four levels: + # 1) full, qualified match + # 2) drop '&': by-ref is pretty much the same as by-value, python-wise + # 3) types/classes, either by ref/ptr or by value + # 4) additional special cases + # + # If all fails, a default is used, which can be ignored at least until use. + from pypy.module.cppyy import interp_cppyy + # 1) full, qualified match try: return _executors[name](space, "", None) except KeyError: @@ -149,9 +158,32 @@ compound = helper.compound(name) clean_name = helper.clean_type(name) - cpptype = interp_cppyy.type_byname(space, clean_name) - if compound == "*": - return InstancePtrExecutor(space, cpptype.name, cpptype) + + # 1a) clean lookup + try: + return _executors[clean_name+compound](space, "", None) + except KeyError: + pass + + # 2) drop '&': by-ref is pretty much the same as by-value, python-wise + if compound and compound[len(compound)-1] == "&": + try: + return _executors[clean_name](space, "", None) + except KeyError: + pass + + # 3) types/classes, either by ref/ptr or by value + try: + cpptype = interp_cppyy.type_byname(space, clean_name) + if compound == "*" or compound == "&": + return InstancePtrExecutor(space, clean_name, cpptype) + except OperationError, e: + if not e.match(space, space.w_TypeError): + raise + pass + + # 4) additional special cases + # ... none for now # currently used until proper lazy instantiation available in interp_cppyy return FunctionExecutor(space, "", None) @@ -159,8 +191,10 @@ # raise TypeError("no clue what %s is" % name) _executors["void"] = VoidExecutor +_executors["void*"] = PtrTypeExecutor _executors["bool"] = BoolExecutor _executors["char"] = CharExecutor +_executors["char*"] = CStringExecutor _executors["unsigned char"] = CharExecutor _executors["short int"] = ShortExecutor _executors["short int*"] = ShortPtrExecutor @@ -178,4 +212,3 @@ _executors["float*"] = FloatPtrExecutor _executors["double"] = DoubleExecutor _executors["double*"] = DoublePtrExecutor -_executors["char*"] = CStringExecutor diff --git a/pypy/module/cppyy/helper.py b/pypy/module/cppyy/helper.py --- a/pypy/module/cppyy/helper.py +++ b/pypy/module/cppyy/helper.py @@ -1,5 +1,7 @@ from pypy.rlib import rstring + +#- type name manipulations -------------------------------------------------- def compound(name): name = "".join(rstring.split(name, "const")) # poor man's replace if name.endswith("]"): # array type? @@ -27,11 +29,120 @@ return i + 1 def clean_type(name): - assert name.find("const") == -1 + # can't strip const early b/c name could be a template ... i = _find_qualifier_index(name) name = name[:i].strip(' ') + + idx = -1 if name.endswith("]"): # array type? idx = name.rfind("[") if 0 < idx: - return name[:idx] - return name + name = name[:idx] + elif name.endswith(">"): # template type? + idx = name.find("<") + n1 = "".join(rstring.split(name[:idx], "const")) # poor man's replace + name = "".join((n1, name[idx:])) + else: + name = "".join(rstring.split(name, "const")) # poor man's replace + name = name[:_find_qualifier_index(name)] + return name.strip(' ') + + +#- operator mappings -------------------------------------------------------- +_operator_mappings = {} + +def map_operator_name(cppname, nargs): + from pypy.module.cppyy import capi + + if cppname[0:8] == "operator": + op = cppname[8:].strip(' ') + + # operator could be a conversion using a typedef + handle = capi.c_get_typehandle(op) + if handle: + op = capi.charp2str_free(capi.c_final_name(handle)) + + # look for known mapping + try: + return _operator_mappings[op] + except KeyError: + pass + + # a couple more cases that depend on whether args were given + + if op == "*": # dereference (not python) vs. multiplication + return nargs and "__mul__" or "__deref__" + + if op == "+": # unary positive vs. binary addition + return nargs and "__add__" or "__pos__" + + if op == "-": # unary negative vs. binary subtraction + return nargs and "__sub__" or "__neg__" + + if op == "++": # prefix v.s. postfix increment (not python) + return nargs and "__postinc__" or "__preinc__"; + + if op == "--": # prefix v.s. postfix decrement (not python) + return nargs and "__postdec__" or "__predec__"; + + # might get here, as not all operator methods handled (new, delete,etc.) + # TODO: perhaps absorb or "pythonify" these operators? + return cppname + +# _operator_mappings["[]"] = "__setitem__" # depends on return type +# _operator_mappings["+"] = "__add__" # depends on # of args (see __pos__) +# _operator_mappings["-"] = "__sub__" # id. (eq. __neg__) +# _operator_mappings["*"] = "__mul__" # double meaning in C++ + +_operator_mappings["[]"] = "__getitem__" +_operator_mappings["()"] = "__call__" +_operator_mappings["/"] = "__div__" # __truediv__ in p3 +_operator_mappings["%"] = "__mod__" +_operator_mappings["**"] = "__pow__" # not C++ +_operator_mappings["<<"] = "__lshift__" +_operator_mappings[">>"] = "__rshift__" +_operator_mappings["&"] = "__and__" +_operator_mappings["|"] = "__or__" +_operator_mappings["^"] = "__xor__" +_operator_mappings["~"] = "__inv__" +_operator_mappings["+="] = "__iadd__" +_operator_mappings["-="] = "__isub__" +_operator_mappings["*="] = "__imul__" +_operator_mappings["/="] = "__idiv__" # __itruediv__ in p3 +_operator_mappings["%="] = "__imod__" +_operator_mappings["**="] = "__ipow__" +_operator_mappings["<<="] = "__ilshift__" +_operator_mappings[">>="] = "__irshift__" +_operator_mappings["&="] = "__iand__" +_operator_mappings["|="] = "__ior__" +_operator_mappings["^="] = "__ixor__" +_operator_mappings["=="] = "__eq__" +_operator_mappings["!="] = "__ne__" +_operator_mappings[">"] = "__gt__" +_operator_mappings["<"] = "__lt__" +_operator_mappings[">="] = "__ge__" +_operator_mappings["<="] = "__le__" + +# the following type mappings are "exact" +_operator_mappings["const char*"] = "__str__" +_operator_mappings["int"] = "__int__" +_operator_mappings["long"] = "__long__" # __int__ in p3 +_operator_mappings["double"] = "__float__" + +# the following type mappings are "okay"; the assumption is that they +# are not mixed up with the ones above or between themselves (and if +# they are, that it is done consistently) +_operator_mappings["char*"] = "__str__" +_operator_mappings["short"] = "__int__" +_operator_mappings["unsigned short"] = "__int__" +_operator_mappings["unsigned int"] = "__long__" # __int__ in p3 +_operator_mappings["unsigned long"] = "__long__" # id. +_operator_mappings["long long"] = "__long__" # id. +_operator_mappings["unsigned long long"] = "__long__" # id. +_operator_mappings["float"] = "__float__" + +_operator_mappings["bool"] = "__nonzero__" # __bool__ in p3 + +# the following are not python, but useful to expose +_operator_mappings["->"] = "__follow__" +_operator_mappings["="] = "__assign__" diff --git a/pypy/module/cppyy/interp_cppyy.py b/pypy/module/cppyy/interp_cppyy.py --- a/pypy/module/cppyy/interp_cppyy.py +++ b/pypy/module/cppyy/interp_cppyy.py @@ -10,7 +10,7 @@ from pypy.rlib import libffi from pypy.rlib import jit, debug -from pypy.module.cppyy import converter, executor +from pypy.module.cppyy import converter, executor, helper class FastCallNotPossible(Exception): pass @@ -320,8 +320,10 @@ args_temp = {} for i in range(num_methods): method_name = capi.charp2str_free(capi.c_method_name(self.handle, i)) + pymethod_name = helper.map_operator_name( + method_name, capi.c_method_num_args(self.handle, i)) cppfunction = self._make_cppfunction(i) - overload = args_temp.setdefault(method_name, []) + overload = args_temp.setdefault(pymethod_name, []) overload.append(cppfunction) for name, functions in args_temp.iteritems(): overload = W_CPPOverload(self.space, name, functions[:]) diff --git a/pypy/module/cppyy/pythonify.py b/pypy/module/cppyy/pythonify.py --- a/pypy/module/cppyy/pythonify.py +++ b/pypy/module/cppyy/pythonify.py @@ -59,9 +59,7 @@ cppclass = get_cppitem(attr, self.__name__) self.__dict__[attr] = cppclass return cppclass - except TypeError, e: - import traceback - traceback.print_exc() + except TypeError: raise AttributeError("%s object has no attribute '%s'" % (self,attr)) @@ -92,15 +90,6 @@ return pycppns def make_cppclass(class_name, cpptype): - d = {"_cppyyclass" : cpptype} - - # insert (static) methods into the class dictionary - for meth_name in cpptype.get_method_names(): - cppol = cpptype.get_overload(meth_name) - if cppol.is_static(): - d[meth_name] = make_static_function(cpptype, meth_name, cppol) - else: - d[meth_name] = make_method(meth_name, cppol) # get a list of base classes for class creation bases = tuple([get_cppclass(base) for base in cpptype.get_base_names()]) @@ -112,20 +101,30 @@ metacpp = type(CppyyClass)(class_name+'_meta', metabases, {"__getattr__" : __innercpp_getattr__}) + # create the python-side C++ class representation + d = {"_cppyyclass" : cpptype} + pycpptype = metacpp(class_name, bases, d) + + # cache result early so that the class methods can find the class itself + _existing_cppitems[class_name] = pycpptype + + # insert (static) methods into the class dictionary + for meth_name in cpptype.get_method_names(): + cppol = cpptype.get_overload(meth_name) + if cppol.is_static(): + setattr(pycpptype, meth_name, make_static_function(cpptype, meth_name, cppol)) + else: + setattr(pycpptype, meth_name, make_method(meth_name, cppol)) + # add all data members to the dictionary of the class to be created, and # static ones also to the meta class (needed for property setters) for dm_name in cpptype.get_data_member_names(): cppdm = cpptype.get_data_member(dm_name) - d[dm_name] = cppdm + setattr(pycpptype, dm_name, cppdm) if cppdm.is_static(): setattr(metacpp, dm_name, cppdm) - # create the python-side C++ class representation - pycpptype = metacpp(class_name, bases, d) - - # cache result and return - _existing_cppitems[class_name] = pycpptype return pycpptype @@ -136,14 +135,13 @@ else: fullname = name - # lookup class + # lookup class ... try: return _existing_cppitems[fullname] except KeyError: pass - # if failed, create - + # ... if lookup failed, create cppitem = cppyy._type_byname(fullname) if cppitem.is_namespace(): return make_cppnamespace(fullname, cppitem) @@ -160,9 +158,7 @@ cppitem = get_cppitem(attr) self.__dict__[attr] = cppitem return cppitem - except TypeError, e: - import traceback - traceback.print_exc() + except TypeError: raise AttributeError("'gbl' object has no attribute '%s'" % attr) diff --git a/pypy/module/cppyy/src/reflexcwrapper.cxx b/pypy/module/cppyy/src/reflexcwrapper.cxx --- a/pypy/module/cppyy/src/reflexcwrapper.cxx +++ b/pypy/module/cppyy/src/reflexcwrapper.cxx @@ -167,7 +167,11 @@ char* cppyy_method_name(cppyy_typehandle_t handle, int method_index) { Reflex::Scope s = scope_from_handle(handle); Reflex::Member m = s.FunctionMemberAt(method_index); - std::string name = m.Name(); + std::string name; + if (m.IsConstructor()) + name = s.Name(Reflex::FINAL); // to get proper name for templates + else + name = m.Name(); return cppstring_to_cstring(name); } diff --git a/pypy/module/cppyy/test/Makefile b/pypy/module/cppyy/test/Makefile --- a/pypy/module/cppyy/test/Makefile +++ b/pypy/module/cppyy/test/Makefile @@ -1,4 +1,4 @@ -all: example01Dict.so datatypesDict.so +all: example01Dict.so datatypesDict.so advancedcppDict.so stltypesDict.so ROOTSYS := ${ROOTSYS} @@ -12,10 +12,10 @@ ifeq ($(shell $(genreflex) --help | grep -- --with-methptrgetter),) genreflexflags= - cppflags2= + cppflags2=-O3 else genreflexflags=--with-methptrgetter - cppflags2=-Wno-pmf-conversions + cppflags2=-Wno-pmf-conversions -O3 endif example01Dict.so: example01.cxx example01.h @@ -29,3 +29,7 @@ advancedcppDict.so: advancedcpp.cxx advancedcpp.h $(genreflex) advancedcpp.h $(genreflexflags) g++ -o $@ advancedcpp_rflx.cpp advancedcpp.cxx -shared -lReflex $(cppflags) $(cppflags2) + +stltypesDict.so: stltypes.cxx stltypes.h stltypes.xml + $(genreflex) stltypes.h --selection=stltypes.xml + g++ -o $@ stltypes_rflx.cpp stltypes.cxx -shared -lReflex $(cppflags) $(cppflags2) diff --git a/pypy/module/cppyy/test/stltypes.cxx b/pypy/module/cppyy/test/stltypes.cxx new file mode 100644 --- /dev/null +++ b/pypy/module/cppyy/test/stltypes.cxx @@ -0,0 +1,1 @@ +#include "stltypes.h" diff --git a/pypy/module/cppyy/test/stltypes.h b/pypy/module/cppyy/test/stltypes.h new file mode 100644 --- /dev/null +++ b/pypy/module/cppyy/test/stltypes.h @@ -0,0 +1,11 @@ +#include <list> +#include <map> +#include <string> +#include <vector> + +#define STLTYPES_EXPLICIT_INSTANTIATION(STLTYPE, TTYPE) \ +template class std::STLTYPE< TTYPE >; \ +template class __gnu_cxx::__normal_iterator<TTYPE*, std::STLTYPE< TTYPE > >; \ +template class __gnu_cxx::__normal_iterator<const TTYPE*, std::STLTYPE< TTYPE > >; + +STLTYPES_EXPLICIT_INSTANTIATION(vector, int) diff --git a/pypy/module/cppyy/test/stltypes.xml b/pypy/module/cppyy/test/stltypes.xml new file mode 100644 --- /dev/null +++ b/pypy/module/cppyy/test/stltypes.xml @@ -0,0 +1,10 @@ +<lcgdict> + + <class pattern="std::vector<*>" /> + <class pattern="__gnu_cxx::__normal_iterator<*>" /> + <class pattern="__gnu_cxx::new_allocator<*>" /> + <class pattern="std::_Vector_base<*>" /> + <class pattern="std::_Vector_base<*>::_Vector_impl" /> + <class pattern="std::allocator<*>" /> + +</lcgdict> diff --git a/pypy/module/cppyy/test/test_helper.py b/pypy/module/cppyy/test/test_helper.py --- a/pypy/module/cppyy/test/test_helper.py +++ b/pypy/module/cppyy/test/test_helper.py @@ -8,7 +8,32 @@ assert helper.array_size("unsigned long int[5]") == 5 +def test_array_size(): + assert helper.array_size("int[5]") == 5 + + def test_clean_type(): assert helper.clean_type(" int***") == "int" + assert helper.clean_type("int* const *&") == "int" assert helper.clean_type("std::vector<int>&") == "std::vector<int>" + assert helper.clean_type("const std::vector<int>&") == "std::vector<int>" + assert helper.clean_type("std::vector<std::vector<int> >" ) == "std::vector<std::vector<int> >" assert helper.clean_type("unsigned short int[3]") == "unsigned short int" + + +def test_operator_mapping(): + assert helper.map_operator_name("operator[]", 1) == "__getitem__" + assert helper.map_operator_name("operator()", 1) == "__call__" + assert helper.map_operator_name("operator%", 1) == "__mod__" + assert helper.map_operator_name("operator**", 1) == "__pow__" + assert helper.map_operator_name("operator<<", 1) == "__lshift__" + assert helper.map_operator_name("operator|", 1) == "__or__" + + assert helper.map_operator_name("operator*", 1) == "__mul__" + assert helper.map_operator_name("operator*", 0) == "__deref__" + + assert helper.map_operator_name("operator+", 1) == "__add__" + assert helper.map_operator_name("operator+", 0) == "__pos__" + + assert helper.map_operator_name("func", 0) == "func" + assert helper.map_operator_name("some_method", 0) == "some_method" diff --git a/pypy/module/cppyy/test/test_stltypes.py b/pypy/module/cppyy/test/test_stltypes.py new file mode 100644 --- /dev/null +++ b/pypy/module/cppyy/test/test_stltypes.py @@ -0,0 +1,44 @@ +import py, os, sys +from pypy.conftest import gettestobjspace + + +currpath = py.path.local(__file__).dirpath() +shared_lib = str(currpath.join("stltypesDict.so")) + +space = gettestobjspace(usemodules=['cppyy']) + +def setup_module(mod): + if sys.platform == 'win32': + py.test.skip("win32 not supported so far") + err = os.system("cd '%s' && make stltypesDict.so" % currpath) + if err: + raise OSError("'make' failed (see stderr)") + +class AppTestSTL: + def setup_class(cls): + cls.space = space + env = os.environ + cls.w_N = space.wrap(13) + cls.w_shared_lib = space.wrap(shared_lib) + cls.w_datatypes = cls.space.appexec([], """(): + import cppyy + return cppyy.load_lib(%r)""" % (shared_lib, )) + + def test1BuiltinTypeVectorType( self ): + """Test access to a vector<int>""" + + import cppyy + + assert cppyy.gbl.std is cppyy.gbl.std +# assert cppyy.gbl.std.vector is cppyy.gbl.std.vector + + tv = getattr(cppyy.gbl.std,'vector<int>') + + v = tv() + for i in range(self.N): + v.push_back(i) + assert v.size() == i+1 +# assert v[i] == i + +# assert len(v) == self.N + v.destruct() _______________________________________________ pypy-commit mailing list pypy-commit@python.org http://mail.python.org/mailman/listinfo/pypy-commit