Commits

wlav committed a10072d

improve pythonizations and use of std::vector and std::list

  • Participants
  • Parent commits d988ba7
  • Branches reflex-support

Comments (0)

Files changed (11)

File pypy/module/cppyy/capi/__init__.py

     return _c_get_method(cppscope.handle, index)
 _c_get_global_operator = rffi.llexternal(
     "cppyy_get_global_operator",
-    [C_SCOPE, C_SCOPE, rffi.CCHARP], WLAVC_INDEX,
+    [C_SCOPE, C_SCOPE, C_SCOPE, rffi.CCHARP], WLAVC_INDEX,
     threadsafe=ts_reflect,
     compilation_info=backend.eci)
-def c_get_global_operator(lc, rc, op):
-    return _c_get_global_operator(lc.handle, rc.handle, op)
+def c_get_global_operator(nss, lc, rc, op):
+    if nss is not None:
+        return _c_get_global_operator(nss.handle, lc.handle, rc.handle, op)
+    return rffi.cast(WLAVC_INDEX, -1)
 
 # method properties ----------------------------------------------------------
 _c_is_constructor = rffi.llexternal(

File pypy/module/cppyy/include/capi.h

     char* cppyy_method_signature(cppyy_scope_t scope, cppyy_index_t idx);
 
     cppyy_method_t cppyy_get_method(cppyy_scope_t scope, cppyy_index_t idx);
-    cppyy_index_t cppyy_get_global_operator(cppyy_scope_t lc, cppyy_scope_t rc, const char* op);
+    cppyy_index_t cppyy_get_global_operator(
+        cppyy_scope_t scope, cppyy_scope_t lc, cppyy_scope_t rc, const char* op);
 
     /* method properties -----------------------------------------------------  */
     int cppyy_is_constructor(cppyy_type_t type, cppyy_index_t idx);

File pypy/module/cppyy/interp_cppyy.py

 
     def instance__eq__(self, w_other):
         other = self.space.interp_w(W_CPPInstance, w_other, can_be_None=False)
-        # get here if no class-specific overloaded operator is available
-        meth_idx = capi.c_get_global_operator(self.cppclass, other.cppclass, "==")
-        if meth_idx != -1:
-            gbl = scope_byname(self.space, "")
-            f = gbl._make_cppfunction("operator==", meth_idx)
-            ol = W_CPPOverload(self.space, scope_byname(self.space, ""), [f])
-            # TODO: cache this operator (currently cached by JIT in capi/__init__.py)
-            return ol.call(self, [self, w_other])
+        # get here if no class-specific overloaded operator is available, try to
+        # find a global overload in gbl, in __gnu_cxx (for iterators), or in the
+        # scopes of the argument classes (TODO: implement that last)
+        for name in ["", "__gnu_cxx"]:
+            nss = scope_byname(self.space, name)
+            meth_idx = capi.c_get_global_operator(nss, self.cppclass, other.cppclass, "==")
+            if meth_idx != -1:
+                f = nss._make_cppfunction("operator==", meth_idx)
+                ol = W_CPPOverload(self.space, nss, [f])
+                # TODO: cache this operator
+                return ol.call(self, [self, w_other])
         
         # fallback: direct pointer comparison (the class comparison is needed since the
         # first data member in a struct and the struct have the same address)

File pypy/module/cppyy/pythonify.py

             return self
         pyclass.__iadd__ = __iadd__
 
-    # for STL iterators, whose comparison functions live globally for gcc
-    # TODO: this needs to be solved fundamentally for all classes
-    if 'iterator' in pyclass.__name__:
-        if hasattr(gbl, '__gnu_cxx'):
-            if hasattr(gbl.__gnu_cxx, '__eq__'):
-                setattr(pyclass, '__eq__', gbl.__gnu_cxx.__eq__)
-            if hasattr(gbl.__gnu_cxx, '__ne__'):
-                setattr(pyclass, '__ne__', gbl.__gnu_cxx.__ne__)
-
-    # map begin()/end() protocol to iter protocol
-    # TODO: the vector hack is there b/c it's safer/faster to use the normal
-    # index iterator (with len checking) rather than the begin()/end() iterators
-    if not 'vector' in pyclass.__name__ and \
+    # map begin()/end() protocol to iter protocol on STL(-like) classes, but
+    # not on vector, for which otherwise the user has to make sure that the
+    # global == and != for its iterators are reflected, which is a hassle ...
+    if not 'vector' in pyclass.__name__[:11] and \
             (hasattr(pyclass, 'begin') and hasattr(pyclass, 'end')):
         # TODO: check return type of begin() and end() for existence
         def __iter__(self):

File pypy/module/cppyy/src/cintcwrapper.cxx

 
     // actual typedef resolution; add back array declartion portion, if needed
     std::string rt = ti.TrueName();
+
+    // builtin STL types have fake typedefs :/
+    G__TypeInfo ti_test(rt.c_str());
+    if (!ti_test.IsValid())
+        return cppstring_to_cstring(cppitem_name);
+
     if (pos != std::string::npos)
         rt += tname.substr(pos, std::string::npos);
     return cppstring_to_cstring(rt);
     return method;
 }
 
-cppyy_index_t cppyy_get_global_operator(cppyy_scope_t lc, cppyy_scope_t rc, const char* op) {
+cppyy_index_t cppyy_get_global_operator(cppyy_scope_t scope, cppyy_scope_t lc, cppyy_scope_t rc, const char* op) {
     TClassRef lccr = type_from_handle(lc);
-    if (!lccr.GetClass()) 
+    TClassRef rccr = type_from_handle(rc);
+
+    if (!lccr.GetClass() || !rccr.GetClass() || scope != GLOBAL_HANDLE)
         return (cppyy_index_t)-1;  // (void*)-1 is in kernel space, so invalid as a method handle
+
     std::string lcname = lccr->GetName();
-
-    TClassRef rccr = type_from_handle(lc);
-    if (!rccr.GetClass())
-        return (cppyy_index_t)-1;
     std::string rcname = rccr->GetName();
 
     std::string opname = "operator";

File pypy/module/cppyy/src/reflexcwrapper.cxx

     return (cppyy_method_t)m.Stubfunction();
 }
 
-cppyy_index_t cppyy_get_global_operator(cppyy_scope_t lc, cppyy_scope_t rc, const char* op) {
-    return (cppyy_index_t)-1; /* not needed yet; covered in pythonify.py */
+cppyy_method_t cppyy_get_global_operator(cppyy_scope_t scope, cppyy_scope_t lc, cppyy_scope_t rc, const char* op) {
+    Reflex::Type lct = type_from_handle(lc);
+    Reflex::Type rct = type_from_handle(rc);
+    Reflex::Scope nss = scope_from_handle(scope);
+
+    if (!lct || !rct || !nss) 
+        return (cppyy_index_t)-1;  // (void*)-1 is in kernel space, so invalid as a method handle
+
+    std::string lcname = lct.Name(Reflex::SCOPED|Reflex::FINAL);
+    std::string rcname = rct.Name(Reflex::SCOPED|Reflex::FINAL);
+
+    std::string opname = "operator";
+    opname += op;
+
+    for (int idx = 0; idx < (int)nss.FunctionMemberSize(); ++idx) {
+        Reflex::Member m = nss.FunctionMemberAt(idx);
+        if (m.FunctionParameterSize() != 2)
+            continue;
+
+        if (m.Name() == opname) {
+            Reflex::Type mt = m.TypeOf();
+            if (lcname == mt.FunctionParameterAt(0).Name(Reflex::SCOPED|Reflex::FINAL) &&
+                rcname == mt.FunctionParameterAt(1).Name(Reflex::SCOPED|Reflex::FINAL)) {
+                return (cppyy_index_t)idx;
+            }
+        }
+    }
+
+    return (cppyy_index_t)-1;  
 }
 
 

File pypy/module/cppyy/test/std_streams_LinkDef.h

 #pragma link off all classes;
 #pragma link off all functions;
 
-#pragma link C++ class std::ostream;
-
 #endif

File pypy/module/cppyy/test/stltypes.cxx

 #include "stltypes.h"
 
-#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 > >;\
+#define STLTYPES_EXPLICIT_INSTANTIATION_WITH_COMPS(STLTYPE, TTYPE)              \
 namespace __gnu_cxx {                                                           \
 template bool operator==(const std::STLTYPE< TTYPE >::iterator&,                \
                          const std::STLTYPE< TTYPE >::iterator&);               \
                          const std::STLTYPE< TTYPE >::iterator&);               \
 }
 
-
-//- explicit instantiations of used types
-STLTYPES_EXPLICIT_INSTANTIATION(vector, int)
-STLTYPES_EXPLICIT_INSTANTIATION(vector, float)
-STLTYPES_EXPLICIT_INSTANTIATION(vector, double)
-STLTYPES_EXPLICIT_INSTANTIATION(vector, just_a_class)
+//- explicit instantiations of used comparisons
+STLTYPES_EXPLICIT_INSTANTIATION_WITH_COMPS(vector, int)
 
 //- class with lots of std::string handling
 stringy_class::stringy_class(const char* s) : m_string(s) {}

File pypy/module/cppyy/test/stltypes.h

 #include <string>
 #include <vector>
 
-#define STLTYPES_EXPLICIT_INSTANTIATION_DECL(STLTYPE, TTYPE)                    \
-extern template class std::STLTYPE< TTYPE >;                                    \
-extern template class __gnu_cxx::__normal_iterator<TTYPE*, std::STLTYPE< TTYPE > >;\
-extern template class __gnu_cxx::__normal_iterator<const TTYPE*, std::STLTYPE< TTYPE > >;\
-namespace __gnu_cxx {                                                           \
-extern template bool operator==(const std::STLTYPE< TTYPE >::iterator&,         \
-                         const std::STLTYPE< TTYPE >::iterator&);               \
-extern template bool operator!=(const std::STLTYPE< TTYPE >::iterator&,         \
-                         const std::STLTYPE< TTYPE >::iterator&);               \
-}
-
-
 //- basic example class
 class just_a_class {
 public:
     int m_i;
 };
 
+#define STLTYPE_INSTANTIATION(STLTYPE, TTYPE, N)                             \
+   std::STLTYPE<TTYPE > STLTYPE##_##N;                                       \
+   std::STLTYPE<TTYPE >::iterator STLTYPE##_##N##_i;                         \
+   std::STLTYPE<TTYPE >::const_iterator STLTYPE##_##N##_ci
 
-#ifndef __CINT__
-//- explicit instantiations of used types
-STLTYPES_EXPLICIT_INSTANTIATION_DECL(vector, int)
-STLTYPES_EXPLICIT_INSTANTIATION_DECL(vector, float)
-STLTYPES_EXPLICIT_INSTANTIATION_DECL(vector, double)
-STLTYPES_EXPLICIT_INSTANTIATION_DECL(vector, just_a_class)
-#endif
+//- instantiations of used STL types
+namespace {
+
+    struct _CppyyVectorInstances {
+
+        STLTYPE_INSTANTIATION(vector, int,          1);
+        STLTYPE_INSTANTIATION(vector, float,        2);
+        STLTYPE_INSTANTIATION(vector, double,       3);
+        STLTYPE_INSTANTIATION(vector, just_a_class, 4);
+
+    };
+
+    struct _CppyyListInstances {
+
+        STLTYPE_INSTANTIATION(list, int,          1);
+        STLTYPE_INSTANTIATION(list, float,        2);
+        STLTYPE_INSTANTIATION(list, double,       3);
+
+    };
+
+} // unnamed namespace
+
+#define STLTYPES_EXPLICIT_INSTANTIATION_DECL_COMPS(STLTYPE, TTYPE)           \
+namespace __gnu_cxx {                                                        \
+extern template bool operator==(const std::STLTYPE< TTYPE >::iterator&,      \
+                         const std::STLTYPE< TTYPE >::iterator&);            \
+extern template bool operator!=(const std::STLTYPE< TTYPE >::iterator&,      \
+                         const std::STLTYPE< TTYPE >::iterator&);            \
+}
+
+// comps for int only to allow testing: normal use of vector is looping over a
+// range-checked version of __getitem__
+STLTYPES_EXPLICIT_INSTANTIATION_DECL_COMPS(vector, int)
 
 
 //- class with lots of std::string handling

File pypy/module/cppyy/test/stltypes.xml

   <namespace name="std" />
 
   <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<*>" />
+  <class pattern="std::vector<*>::iterator" />
+  <class pattern="std::vector<*>::const_iterator" />
 
+  <class pattern="std::list<*>" />
+  <class pattern="std::_List_base<*>" />
+  <class pattern="std::list<*>::iterator" />
+  <class pattern="std::list<*>::const_iterator" />
+  <class name="std::__detail::_List_node_base" />
+
+  <class pattern="__gnu_cxx::__normal_iterator<*>" />
   <function name="__gnu_cxx::operator=="/>
   <function name="__gnu_cxx::operator!="/>
 

File pypy/module/cppyy/test/test_stltypes.py

 class AppTestSTLVECTOR:
     def setup_class(cls):
         cls.space = space
-        env = os.environ
         cls.w_N = space.wrap(13)
         cls.w_test_dct  = space.wrap(test_dct)
         cls.w_stlvector = cls.space.appexec([], """():
             assert tv1 is tv2
             assert tv1.iterator is cppyy.gbl.std.vector(p_type).iterator
 
-            #-----
+            #----- 
             v = tv1(); v += range(self.N)    # default args from Reflex are useless :/
-            assert v.begin().__eq__(v.begin())
-            assert v.begin() == v.begin()
-            assert v.end() == v.end()
-            assert v.begin() != v.end()
-            assert v.end() != v.begin()
+            if p_type == int:                # only type with == and != reflected in .xml
+                assert v.begin().__eq__(v.begin())
+                assert v.begin() == v.begin()
+                assert v.end() == v.end()
+                assert v.begin() != v.end()
+                assert v.end() != v.begin()
 
             #-----
             for i in range(self.N):
 class AppTestSTLSTRING:
     def setup_class(cls):
         cls.space = space
-        env = os.environ
         cls.w_test_dct  = space.wrap(test_dct)
         cls.w_stlstring = cls.space.appexec([], """():
             import cppyy
         c.set_string1(s)
         assert t0 == c.get_string1()
         assert s == c.get_string1()
+
+
+class AppTestSTLSTRING:
+    def setup_class(cls):
+        cls.space = space
+        cls.w_N = space.wrap(13)
+        cls.w_test_dct  = space.wrap(test_dct)
+        cls.w_stlstring = cls.space.appexec([], """():
+            import cppyy
+            return cppyy.load_reflection_info(%r)""" % (test_dct, ))
+
+    def test01_builtin_list_type(self):
+        """Test access to a list<int>"""
+
+        import cppyy
+        from cppyy.gbl import std
+
+        type_info = (
+            ("int",     int),
+            ("float",   "float"),
+            ("double",  "double"),
+        )
+
+        for c_type, p_type in type_info:
+            tl1 = getattr(std, 'list<%s>' % c_type)
+            tl2 = cppyy.gbl.std.list(p_type)
+            assert tl1 is tl2
+            assert tl1.iterator is cppyy.gbl.std.list(p_type).iterator
+
+            #-----
+            a = tl1()
+            for i in range(self.N):
+                a.push_back( i )
+
+            assert len(a) == self.N
+            assert 11 < self.N
+            assert 11 in a
+
+            #-----
+            ll = list(a)
+            for i in range(self.N):
+                assert ll[i] == i
+
+            for val in a:
+                assert ll[ll.index(val)] == val
+
+    def test02_empty_list_type(self):
+        """Test behavior of empty list<int>"""
+
+        import cppyy
+        from cppyy.gbl import std
+
+        a = std.list(int)()
+        for arg in a:
+           pass
+