def WrapFunc(self, f, ln, unused_ns, class_ns=''): """Process AST.FuncDecl f.""" cname = Ident(f.name.cpp_name) assert cname, 'cpp_name is empty for ' + f.name.native pyname = f.name.native.rstrip('#') if pyname.endswith('@'): ctxmgr = pyname pyname = pyname[:-1] else: ctxmgr = None if self.nested and cname.startswith('operator'): wrapper_name = 'wrap' + pyname elif pyname == '__init__': wrapper_name = 'wrap' + types.Mangle(self.name) + '_as___init__' else: wrapper_name = 'wrap' + types.Mangle(cname) if pyname != cname: wrapper_name += '_as_' + pyname if f.ignore_return_value: assert len( f.returns) < 2, ('Func with ignore_return_value has too many' ' returns (%d)' % len(f.returns)) del f.returns[:] for s in self._WrapAllCallables(f, cname, ln, class_ns, False): yield s if f.cpp_opfunction or (f.is_extend_method and not f.classmethod and not f.constructor): self_param = f.params.pop(0) else: self_param = None if ctxmgr: assert not f.classmethod, "Context manager methods can't be static" # Force context manager args API. meth = VARARGS if ctxmgr == '__exit__@' else NOARGS else: meth = VARARGS if len(f.params) else NOARGS for s in gen.FunctionCall( # Keep '@' in method name to distinguish a ctxmgr. f.name.native.rstrip('#'), wrapper_name, func_ast=f, lineno=ln, call=self._FunctionCallExpr(f, cname, pyname), doc=next(astutils.Docstring(f)), prepend_self=self_param, catch=self.catch_cpp_exceptions and not f.cpp_noexcept, postcall_init=None, typepostconversion=self.typemap): yield s if f.classmethod: meth += ' | METH_CLASS' # Keep '#' in method name to distinguish map/seq slots. self.methods.append( (f.name.native.rstrip('@'), wrapper_name, meth, '\\n'.join(astutils.Docstring(f)).replace('"', '\\"')))
def testMangle(self): self.assertEqual(types.Mangle('::A<B*, const C&>'), 'A_B_ptr_constC_ref') self.assertEqual(types.Mangle('::A<B::C>'), 'A_B_C') self.assertEqual(types.Mangle('Abc'), 'Abc') self.assertEqual(types.Mangle('Abc::D'), 'Abc_D') self.assertEqual(types.Mangle('Abc<D>'), 'Abc_D') self.assertEqual(types.Mangle('Abc<D>&&'), 'Abc_D__rref') self.assertEqual(types.Mangle('Abc<D::E>'), 'Abc_D_E') self.assertEqual(types.Mangle('Abc<D *> *'), 'Abc_D_ptr__ptr') self.assertEqual(types.Mangle('Abc<const D *> *'), 'Abc_constD_ptr__ptr') self.assertEqual(types.Mangle('Abc const&'), 'Abcconst_ref')
def testMangleEscapedCharLiteral(self): # Octal escapes. self.assertEqual( types.Mangle(r"A<'\0', '\000', '\5', '\777'>"), 'A_c0__c0__c5__c511_') # Hexadecimal escapes. self.assertEqual( types.Mangle(r"A<'\x00', '\xff', '\xFF', '\xA0'>"), 'A_c0__c255__c255__c160_') # Unicode escapes. self.assertEqual( types.Mangle(r"A<'\u0000', '\u0001', '\u1000', '\u9999'>"), 'A_c0__c1__c4096__c39321_') self.assertEqual( types.Mangle(r"A<'\U00000000', '\U00109999'>"), 'A_c0__c1087897_') # ASCII control code escapes. self.assertEqual( types.Mangle(r"A<'\a', '\b', '\f', '\n', '\r', '\t', '\v'>"), 'A_c7__c8__c12__c10__c13__c9__c11_') # Other low-ASCII escapes. self.assertEqual( types.Mangle("A<'\\'', '\\\"', '\\\\'>"), 'A_c39__c34__c92_')
def WrapClass(self, c, unused_ln, cpp_namespace, unused_class_ns=''): """Process AST.ClassDecl c.""" cname = Ident(c.name.cpp_name) pyname = c.name.native self.Add(Context(cname, pyname, c.name.cpp_name)) ns = self.class_namespace yield '' yield 'namespace %s {' % ns virtual, has_iterator = _IsSpecialClass(c, pyname) if virtual: for s in gen.VirtualOverriderClass( VIRTUAL_OVERRIDER_CLASS, pyname, c.name.cpp_name, c.name.cpp_name + '::' + cname, c.cpp_abstract, Ident, vfuncs=virtual, pcfunc=lambda t, tm=self.typemap: postconv.Initializer( t, tm)): yield s c.bases.add().cpp_name = c.name.cpp_name # Flag that we're now in the nested __iter__ class. iter_class = (pyname == _ITER_KW) for s in gen.WrapperClassDef( WRAPPER_CLASS_NAME, is_iter=iter_class, has_iter=has_iterator, iter_ns=_ClassNamespace(_ITER_KW), cname=c.name.cpp_name, ctype=VIRTUAL_OVERRIDER_CLASS if virtual else c.name.cpp_name, enable_instance_dict=c.enable_instance_dict): yield s tracked_slot_groups = {} cpp_has_ext_def_ctor = False if iter_class: # Special-case nested __iter__ class. for s in _WrapIterSubclass(c.members, self.typemap): yield s tp_slots = { 'tp_flags': ['Py_TPFLAGS_DEFAULT'], 'tp_iter': 'PyObject_SelfIter', 'tp_iternext': gen.IterNext.name } ctor = None else: # Normal class generator. if c.final: self.final = True else: yield '' yield 'static %s* ThisPtr(PyObject*);' % c.name.cpp_name ctor = ('DEF' if c.cpp_has_def_ctor and (not c.cpp_abstract or virtual) else None) for d in c.members: if d.decltype == d.FUNC: f = d.func if f.name.native == '__init__': if virtual: # Constructors are not virtual, but we mark the constructor as # virtual to indicate that there exist virtual functions in the # Clif class declaration. f.virtual = True if not f.params: if f.is_extend_method: cpp_has_ext_def_ctor = True else: # Skip generating wrapper function for unextended default # ctor. But wrapper function for extended default ctor is still # necessary. continue ctor = 'wrap%s_as___init__' % types.Mangle(cname) elif c.cpp_abstract and f.virtual: continue # Skip calling virtual func from the abstract base class. for s in self.WrapDecl(d, parent_ns=cpp_namespace, class_ns=ns): yield s if virtual and not ctor: raise ValueError( 'A constructor should be declared in the Clif wrapper for %s as it ' 'has virtual method declarations.' % c.name.cpp_name) # For Py2 slots.py relies on Py_TPFLAGS_DEFAULT being set. tp_slots = {'tp_flags': ['Py_TPFLAGS_DEFAULT']} if c.cpp_abstract: tp_slots['tp_flags'].append('Py_TPFLAGS_IS_ABSTRACT') if not self.py3output: tp_slots['tp_flags'].append('Py_TPFLAGS_CHECKTYPES') if not c.final: tp_slots['tp_flags'].append('Py_TPFLAGS_BASETYPE') if has_iterator: n = _ClassNamespace(_ITER_KW) + '::' w = n + WRAPPER_CLASS_NAME # Python convention to have a type struct named FOO_Type. for s in gen.NewIter(_GetCppObj(), n, w, w + '_Type'): yield s tp_slots['tp_iter'] = gen.NewIter.name if self.properties or c.enable_instance_dict: yield '' for s in gen.GetSetDef(self.properties, c.enable_instance_dict): yield s tp_slots['tp_getset'] = gen.GetSetDef.name for b in c.bases: if b.cpp_name and not b.native: p = b.cpp_name w = 'as_' + types.Mangle(p) # pyname == cname == w for s in gen.CastAsCapsule(_GetCppObj(), p, w): yield s self.methods.append((w, w, NOARGS, 'Upcast to %s*' % p)) _AppendReduceExIfNeeded(self.methods) if self.methods: for s in slots.GenSlots(self.methods, tp_slots, py3=self.py3output, tracked_groups=tracked_slot_groups): yield s if self.methods: # If all methods are slots, it's empty. for s in gen.MethodDef(self.methods): yield s tp_slots['tp_methods'] = gen.MethodDef.name qualname = '.'.join(f.pyname for f in self.nested) tp_slots['tp_name'] = '"%s.%s"' % (self.path, qualname) if c.docstring: docstring = c.docstring.strip() # Escape characters for inclusion in the raw C string. if str is bytes: # PY2 docstring = docstring.encode('unicode-escape') docstring = docstring.replace('\n', r'\n') docstring = docstring.replace('"', r'\"') tp_slots['tp_doc'] = '"%s"' % docstring if c.async_dtor and c.cpp_has_trivial_dtor: raise ValueError( 'Class %s has a trivial dtor yet @async__del__ decorator' % pyname) # Python convention to have a type struct named FOO_Type. # Generate wrapper Type object in wname+'_Type' static var. for s in gen.TypeObject( qualname, tracked_slot_groups, tp_slots, pyname, ctor, wname=WRAPPER_CLASS_NAME, fqclassname=c.name.cpp_name, abstract=c.cpp_abstract, iterator=_GetCppObj('iter') if iter_class else None, trivial_dtor=c.cpp_has_trivial_dtor, subst_cpp_ptr=VIRTUAL_OVERRIDER_CLASS if virtual else '', enable_instance_dict=c.enable_instance_dict, cpp_has_ext_def_ctor=cpp_has_ext_def_ctor): yield s if not iter_class: for s in types.GenThisPointerFunc(c.name.cpp_name, WRAPPER_CLASS_NAME, c.final): yield s yield '' yield '} // namespace ' + ns type_dict = self.dict wrapns = '::'.join(f.class_namespace for f in self.nested) + '::' wclass = wrapns + WRAPPER_CLASS_NAME vclass = wrapns + VIRTUAL_OVERRIDER_CLASS # Python convention to have a type struct named FOO_Type. wtype = wclass + '_Type' self.DropContext() base, wrapped_base = _ProcessInheritance( c.bases, '::%s_Type' % WRAPPER_CLASS_NAME, self.namemap) self.types_init.append((wtype, base, wrapped_base, type_dict)) if iter_class: if base: raise TypeError( "__iter__ class can't be derived, base '%s' found" % base) else: self.dict.append((pyname, types.AsPyObj(wtype))) self.types.append( types.ClassType(c.name.cpp_name, qualname, wclass, wtype, wrapns, can_copy=c.cpp_copyable and not c.cpp_abstract, can_move=c.cpp_movable and not c.cpp_abstract, can_destruct=c.cpp_has_public_dtor, virtual=vclass if virtual else '', ns=cpp_namespace))
def WrapVar(self, v, unused_ln, unused_ns, unused_class_ns=''): """Process AST.VarDecl v.""" assert self.nested, 'C++ global vars not allowed, use const' assert '::' not in v.name.cpp_name vname = v.name.cpp_name ctype = v.type.cpp_type if v.cpp_set and not v.cpp_get: raise NameError('Property %s has setter, but no getter' % v.name.native) getter = 'get_' + vname setter = 'set_' + vname if self.final: base = None cobj = _GetCppObj() + '->' else: base = 'auto cpp = ThisPtr(self); if (!cpp) ' cobj = 'cpp->' is_property = False if v.cpp_get.name.cpp_name: # It's a property var (with cpp only `getter`, `setter`). assert not v.cpp_get.name.native is_property = True if v.cpp_get.classmethod or v.cpp_set.classmethod: raise ValueError('Static properties not supported') if v.cpp_set.name.cpp_name: assert not v.cpp_set.name.native else: setter = 'nullptr' vname = Ident(v.cpp_get.name.cpp_name) + '()' nested_container = not is_property cvar = cobj + vname getval = cvar setval = (cobj + Ident(v.cpp_set.name.cpp_name) if v.cpp_set.name.cpp_name else '') if v.is_extend_variable: orig_getter_name = v.cpp_get.name.cpp_name.split( ast_manipulations.EXTEND_INFIX)[-1] cname = Ident(v.cpp_get.name.cpp_name) wrapper_name = 'wrap' + types.Mangle(cname) getval = wrapper_name + '_as_' + orig_getter_name + '(self)' if v.cpp_set: setval = v.cpp_set.name.cpp_name unproperty = False if v.cpp_get.name.native: # It's an unproperty var (@getter pyname / @setter pyname). assert not v.cpp_get.name.cpp_name unproperty = True nested_container = False self.methods.append((v.cpp_get.name.native, getter, NOARGS, '%s()->%s C++ %s.%s getter' % (v.cpp_get.name.native, v.type.lang_type, self.name, v.name.cpp_name))) if v.cpp_set.name.native: assert not v.cpp_set.name.cpp_name self.methods.append((v.cpp_set.name.native, setter, 'METH_O', '%s(%s) C++ %s.%s setter' % (v.cpp_set.name.native, v.type.lang_type, self.name, v.name.cpp_name))) else: setter = 'nullptr' else: self.properties.append( (v.name.native, getter, setter, 'C++ %s %s.%s' % (ctype, self.CppName(), vname))) if not v.type.cpp_abstract and not ctype.startswith( '::std::shared_ptr'): if v.type.cpp_raw_pointer or ctype.startswith( '::std::unique_ptr'): getval = '*' + cvar nested_container = False elif nested_container and v.type.cpp_toptr_conversion: # For a nested container we'll try to return it (we use # cpp_toptr_conversion as an indicator for a custom container). getval = '::clif::MakeStdShared(%s, &%s)' % (_GetCppObj(), cvar) for s in gen.VarGetter(getter, unproperty, base, getval, postconv.Initializer(v.type, self.typemap), is_extend=v.is_extend_variable): yield s if setter != 'nullptr': for s in gen.VarSetter(setter, unproperty, base, cvar, v, setval, as_str=('PyUnicode_AsUTF8' if self.py3output else 'PyString_AS_STRING'), is_extend=v.is_extend_variable): yield s
def WrapClass(self, c, unused_ln, cpp_namespace): """Process AST.ClassDecl c.""" cname = Ident(c.name.cpp_name) pyname = c.name.native self.Add(Context(cname, pyname, c.name.cpp_name)) ns = self.class_namespace yield '' yield 'namespace %s {' % ns virtual = False # If c has @virtual members, generate a derived redirector class. for d in c.members: if d.decltype == d.FUNC and d.func.virtual: if not virtual: yield '' yield 'struct %s : PyObj, %s {' % (VIRTUAL_OVERRIDER_CLASS, c.name.cpp_name) yield I + 'using %s::%s;' % (c.name.cpp_name, cname) virtual = True for s in gen.VirtualFunctionCall( Ident(d.func.name.cpp_name), d.func, pyname, c.cpp_abstract, lambda atype: postconv.Initializer( atype, self.typemap)): yield s if virtual: if c.final: raise ValueError("Final class %s can't have virtual methods." % pyname) yield '};' b = c.bases.add() b.cpp_name = c.name.cpp_name yield '' yield 'struct %s {' % self.wrapper_class_name yield I + 'PyObject_HEAD' yield I + '::clif::SharedPtr<%s> cpp;' % (VIRTUAL_OVERRIDER_CLASS if virtual else c.name.cpp_name) yield '};' if c.final: self.final = True else: yield 'static %s* ThisPtr(PyObject*);' % c.name.cpp_name ctor = ('DEF' if c.cpp_has_def_ctor and (not c.cpp_abstract or virtual) else None) for d in c.members: if d.decltype == d.FUNC: f = d.func if f.name.native == '__init__': if virtual: # Constructors are not virtual, but we mark the constructor as # virtual to indicate that there exist virtual functions in the # Clif class declaration. f.virtual = True if not f.params: continue # Skip generating wrapper function for default ctor. ctor = 'wrap%s_as___init__' % types.Mangle(cname) elif c.cpp_abstract and f.virtual: continue # Skip calling virtual func from the abstract base class. for s in (self.WrapDecl(d, cpp_namespace)): yield s if virtual and not ctor: raise ValueError( 'A constructor should be declared in the Clif wrapper for %s as it ' 'has virtual method declarations.' % c.name.cpp_name) # For Py2 slots.py relies on Py_TPFLAGS_DEFAULT being set. tp_slots = { 'tp_flags': ['Py_TPFLAGS_DEFAULT', 'Py_TPFLAGS_TYPE_SUBCLASS'] } if c.cpp_abstract: tp_slots['tp_flags'].append('Py_TPFLAGS_IS_ABSTRACT') if not self.py3output: tp_slots['tp_flags'].append('Py_TPFLAGS_CHECKTYPES') if not c.final: tp_slots['tp_flags'].append('Py_TPFLAGS_BASETYPE') if self.properties: for s in (gen.GetSetDef(self.properties)): yield s tp_slots['tp_getset'] = gen.GetSetDef.name for b in c.bases: if b.cpp_name and not b.native: p = b.cpp_name w = 'as_' + types.Mangle(p) # pyname == cname == w yield '' yield '// Implicit cast this as %s*' % p yield 'static PyObject* %s(PyObject* self) {' % w yield I + ('%s* p = ::clif::python::Get(reinterpret_cast<%s*>(' 'self)->cpp);' % (p, self.wrapper_class_name)) yield I + 'if (p == nullptr) return nullptr;' yield I + ('return PyCapsule_New(p, C("%s"), nullptr);') % p yield '}' self.methods.append((w, w, NOARGS, 'Upcast to %s*' % p)) if self.methods: for s in (slots.GenSlots(self.methods, tp_slots, py3=self.py3output)): yield s if self.methods: # If all methods are slots, it's empty. for s in (gen.MethodDef(self.methods)): yield s tp_slots['tp_methods'] = gen.MethodDef.name pypath = '.'.join(f.pyname for f in self.nested) tp_slots['tp_name'] = '"%s.%s"' % (self.path, pypath) for s in ( # Generate wrapper Type object in wname+'_Type' static var. gen.TypeObject( tp_slots, slots.GenTypeSlots, pyname, ctor=ctor, wname=self.wrapper_class_name, fqclassname=c.name.cpp_name, abstract=c.cpp_abstract, subst_cpp_ptr=VIRTUAL_OVERRIDER_CLASS if virtual else '', async_dtor=c.async_dtor, )): yield s for s in types.GenThisPointerFunc(c.name.cpp_name, self.wrapper_class_name, c.final): yield s yield '} // namespace ' + ns type_dict = self.dict wrapns = '::'.join(f.class_namespace for f in self.nested) + '::' wclass = wrapns + self.wrapper_class_name vclass = wrapns + VIRTUAL_OVERRIDER_CLASS wrap = '::' + self.wrapper_class_name wtype = wclass + '_Type' wrapt = wrap + '_Type' self.DropContext() base = cpp_replacement = None if c.bases: # Pytd fills bases with native name (cpp_name='') for Python inheritance. # Matcher extends it with cpp_name (native=='') for C++ parent classes. base = [b.native for b in c.bases if b.native and not b.cpp_name] if base: if len(base) > 1: raise ValueError('Multiple inheritance not supported') base = base[0] if '.' not in base: # base defined in the same .clif wrapper base = _ClassNamespace(base) + wrapt elif c.bases[0].native == 'replacement': assert c.bases[0].cpp_name cpp_replacement = c.bases[0].cpp_name self.types_init.append((wtype, base, type_dict)) self.dict.append( (pyname, types.AsPyObj(self.wrap_namespace + '::' + wtype))) if cpp_replacement: # Find replacement class namespace. for b in c.cpp_bases: if b.name == cpp_replacement: cpp_namespace = b.namespace break else: cpp_namespace = '' self.types.add( types.ClassType(c.name.cpp_name, pypath, wclass, wtype, wrapns, can_copy=c.cpp_copyable and not c.cpp_abstract, can_destruct=c.cpp_has_public_dtor, down_cast=cpp_replacement, virtual=vclass if virtual else '', ns=cpp_namespace))
def WrapFunc(self, f, ln, unused_ns): """Process AST.FuncDecl f.""" cname = Ident(f.name.cpp_name) assert cname pyname = f.name.native.rstrip('#') if pyname.endswith('@'): ctxmgr = pyname pyname = pyname[:-1] else: ctxmgr = None if self.nested and cname.startswith('operator'): wrapper_name = 'wrap' + pyname elif pyname == '__init__': wrapper_name = 'wrap' + types.Mangle(self.name) + '_as___init__' else: wrapper_name = 'wrap' + types.Mangle(cname) if pyname != cname: wrapper_name += '_as_' + pyname if f.ignore_return_value: assert len( f.returns) < 2, ('Func with ignore_return_value has too many' ' returns (%d)' % len(f.returns)) del f.returns[:] for i, r in enumerate(f.returns): if r.type.HasField('callable'): for s in (self.WrapCallable(r.type, cname, i, ln)): yield s self_param = f.params.pop(0) if f.cpp_opfunction else None if ctxmgr: assert not f.classmethod, "Context manager methods can't be static" # Force context manager args API. meth = VARARGS if ctxmgr == '__exit__@' else NOARGS else: meth = VARARGS if len(f.params) else NOARGS call = f.name.cpp_name postcall = None if self.nested and not f.classmethod: cpp = 'reinterpret_cast<%s*>(self)->cpp' % self.wrapper_class_name if f.constructor: assert not f.returns, cname + ' ctor must return void' if f.virtual: ctor = VIRTUAL_OVERRIDER_CLASS postcall = '->::clif::PyObj::Init(self);' else: ctor = self.fqname if pyname == '__init__': call = '%s = ::clif::MakeShared<%s>' % (cpp, ctor) if postcall: postcall = cpp + postcall # C++ constructors do not return anything. f.cpp_void_return = True else: # additional ctors f.classmethod = True call = '::gtl::MakeUnique<%s>' % ctor # Pretend we're returning a new instance. r = f.returns.add() r.type.lang_type = self.pyname r.type.cpp_type = 'std::unique_ptr<%s>' % ctor f.cpp_void_return = False elif not f.cpp_opfunction: if self.final: call = cpp + '->' + cname else: call = [ '%s* c = ThisPtr(self);' % self.fqname, 'if (!c) return nullptr;', ] if f.virtual: call.append('c->' + self.name + '::' + cname) else: call.append('c->' + cname) for s in ( # Keep '@' in method name to distinguish ctxmgr. gen.FunctionCall(f.name.native.rstrip('#'), wrapper_name, doc=next(astutils.Docstring(f)), catch=self.catch_cpp_exceptions and not f.cpp_noexcept, call=call, postcall_init=postcall, lineno=ln, prepend_self=self_param, typepostconversion=self.typemap, func_ast=f)): yield s if f.classmethod: meth += ' | METH_CLASS' # Keep '#' in method name to distinguish map/seq slots. self.methods.append( (f.name.native.rstrip('@'), wrapper_name, meth, '\\n'.join(astutils.Docstring(f)).replace('"', '\\"')))