Commits

wlav committed c241b11

first steps towards STL support

Comments (0)

Files changed (13)

 ^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$

pypy/module/cppyy/converter.py

     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 == "*":

pypy/module/cppyy/executor.py

 
 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)
 
 
 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:
 
     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)
  #  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
 _executors["float*"]              = FloatPtrExecutor
 _executors["double"]              = DoubleExecutor
 _executors["double*"]             = DoublePtrExecutor
-_executors["char*"]               = CStringExecutor

pypy/module/cppyy/helper.py

 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?
     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__"

pypy/module/cppyy/interp_cppyy.py

 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
         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[:])

pypy/module/cppyy/pythonify.py

         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))
 
 
     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()])
     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
 
 
     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)
             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)
 
 

pypy/module/cppyy/src/reflexcwrapper.cxx

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

pypy/module/cppyy/test/Makefile

-all: example01Dict.so datatypesDict.so
+all: example01Dict.so datatypesDict.so advancedcppDict.so stltypesDict.so
 
 ROOTSYS := ${ROOTSYS}
 
 
 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
 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)

pypy/module/cppyy/test/stltypes.cxx

+#include "stltypes.h"

pypy/module/cppyy/test/stltypes.h

+#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)

pypy/module/cppyy/test/stltypes.xml

+<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>

pypy/module/cppyy/test/test_helper.py

     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"

pypy/module/cppyy/test/test_stltypes.py

+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()