def rewrite_multi_output_function(self, func: T.FunctionDef, offset): """ Notes ----- Outputs should be grouped at the end of the function to differentiate between an output and an array input """ new_func = T.FunctionDef(func.name) new_func.body = [func.body[0]] arg_len = len(func.args.args[offset:]) if arg_len == 0: return func output_args = [] remaining_args = [] arg_type = dict() # Filter output arguments for i, arg in enumerate(func.args.args[offset:]): if match(arg.annotation, 'Call', ('func', 'Name')) and arg.annotation.func.id == 'POINTER': # Generate the result argument var_type = arg.annotation.args[0] new_func.body.append( T.Assign([T.Name(arg.arg)], T.Call(var_type))) output_args.append(arg) arg_type[i] = 'O' else: remaining_args.append(arg) arg_type[i] = 'I' original_call: T.Call = func.body[1].value for i in range(len(original_call.args[offset:])): if arg_type[i] == 'O': original_call.args[i + offset] = T.Call( T.Name('byref'), [T.Name(func.args.args[i + offset].arg)]) new_func.body.append(T.Assign([T.Name('error')], original_call)) returns = [T.Name(arg.arg) for arg in output_args] returns_types = [arg.annotation.args[0] for arg in output_args] if len(returns) == 1: new_func.body.append(T.Return(returns[0])) new_func.returns = returns_types[0] else: new_func.body.append(T.Return(T.Tuple(returns))) # new_func.returns = T.Call(T.Name('Tuple'), returns_types) new_func.returns = T.Subscript(T.Name('Tuple'), T.ExtSlice(dims=returns_types), T.Load()) if offset == 1: remaining_args = [T.Arg('self')] + remaining_args new_func.args = T.Arguments(args=remaining_args) return new_func
def class_definition(self, class_def: T.ClassDef, depth): assert class_def.name in self.ctypes # Opaque struct: move on # if class_def.name[0] == '_': # return class_def self_wrap = self.wrappers.get(class_def.name) if self_wrap is None: if T.Name('enumeration') in class_def.decorator_list: return self.clean_up_enumeration(class_def) _, names = parse_sdl_name(class_def.name) cl_name = class_name(*names) self_wrap = T.ClassDef(cl_name) self.new_code.append((class_def.name, self_wrap)) self.wrappers[class_def.name] = self_wrap self.wrappers_2_ctypes[self_wrap.name] = class_def.name self.wrapper_order[self_wrap.name] = len(self.wrappers) self_type = T.Call(T.Name('POINTER'), [T.Name(class_def.name)]) self_wrap.body.append( T.AnnAssign(T.Name('handle'), self_type, T.Name('None'))) # Factory to build the wrapper form the ctype # create an uninitialized version of the object and set the handle from_ctype = T.FunctionDef('from_handle', decorator_list=[T.Name('staticmethod')]) from_ctype.args = T.Arguments(args=[T.Arg('a', self_type)]) from_ctype.returns = T.Constant(cl_name) from_ctype.body = [ T.Assign([T.Name('b')], T.Call(T.Attribute(T.Name('object'), '__new__'), [T.Name(cl_name)])), T.Assign([T.Attribute(T.Name('b'), 'handle')], T.Name('a')), T.Return(T.Name('b')) ] self_wrap.body.append(from_ctype) if not self.is_opaque_container(class_def.name): # default init allocate a dummy object for it default_init = T.FunctionDef('__init__') default_init.args = T.Arguments(args=[T.Arg('self')]) default_init.body = [ T.Assign([T.Attribute(T.Name('self'), '_value')], T.Call(T.Name(class_def.name), [])), T.Assign([T.Attribute(T.Name('self'), 'handle')], T.Call(T.Name('byref'), [T.Attribute(T.Name('self'), '_value')])) ] self_wrap.body.append(default_init) self.rename_types[T.Call(T.Name('POINTER'), [T.Name(class_def.name)])] = cl_name return class_def
def generate_constructor(self, class_def: T.ClassDef, call: BindCall): self.current_class_name = class_def.name cl_name = class_def.name # because we can have multiple constructors we have a to generate a constructor as a static method fun_name = call.function_name ctype_return = call.return_type _, names = parse_sdl_name(fun_name) args, c_call = self.make_wrapping_call(call) new_fun = T.FunctionDef(function_name(*names), decorator_list=[T.Name('staticmethod')]) new_fun.returns = self.rename(ctype_return) new_fun.args = args new_fun.body = [ T.Assign([T.Name('b')], T.Call(T.Attribute(T.Name('object'), '__new__'), [T.Name(cl_name)])), T.Assign([T.Attribute(T.Name('b'), 'handle')], c_call), T.Return(T.Name('b')) ] self.add_docstring(new_fun, call, 2) call.clear_kwargs() class_def.body.append(new_fun) self.current_class_name = None
def make_wrapping_call( self, call: BindCall, replace_arg_names: Optional[List] = None, replace_args: Optional[List] = None) -> Tuple[T.Arguments, T.Call]: if replace_arg_names is None: replace_arg_names = [] if replace_args is None: replace_args = [] assert len(replace_args) == len(replace_arg_names) fun_name = call.function_name offset = len(replace_args) ctype_args = list(call.arguments[offset:]) arg_names = list(call.argument_names[offset:]) fun_args = T.Arguments( args=replace_arg_names + [T.Arg(n, self.rename(t)) for n, t in zip(arg_names, ctype_args)]) fun_call = T.Call( T.Name(fun_name), replace_args + [T.Name(arg_names[i]) for i in range(len(ctype_args))]) return fun_args, fun_call
def generate_method(self, call: BindCall, self_wrap: T.ClassDef): fun_name = call.function_name _, names = parse_sdl_name(fun_name) self.current_class_name = self_wrap.name args, c_call = self.make_wrapping_call( call, replace_arg_names=[T.Arg('self')], replace_args=[self.SELF_HANDLE]) new_fun = T.FunctionDef(function_name(*names)) new_fun.returns = self.rename(call.return_type) new_fun.args = args # does the return type need to be wrapped if new_fun.returns != call.return_type: cast_to = new_fun.returns if isinstance(new_fun.returns, T.Constant): cast_to = T.Name(new_fun.returns.value) c_call = T.Call(T.Attribute(cast_to, 'from_handle'), [c_call]) new_fun.body = [T.Return(c_call)] self.add_docstring(new_fun, call, 2) call.clear_kwargs() if self.is_multi_output(new_fun, offset=1): new_fun = self.rewrite_multi_output_function(new_fun, offset=1) self_wrap.body.append(new_fun) # we cannot automatically add a destructor because we do not know # which object is owning what # # try to find destructor and constructor functions # for i, n in enumerate(names): # arg_n = len(new_fun.args.args) # # # check if the function is named destroy + class_name # if arg_n == 1 and n == 'destroy' and i + 2 == len(names) and names[i + 1] == self_wrap.name.lower(): # destructor = copy.deepcopy(new_fun) # destructor.name = '__del__' # self_wrap.body.append(destructor) # break # # # sdl is not consistent with that one # if n == 'free': # destructor = copy.deepcopy(new_fun) # destructor.name = '__del__' # self_wrap.body.append(destructor) self.wrapper_method_count[self.current_class_name] += 1 self.current_class_name = None
def generate_function(self, elem: Cursor, depth=0, **kwargs): """Generate a type alias Examples -------- >>> from tide.generators.clang_utils import parse_clang >>> tu, index = parse_clang('float add(float a, float b);') >>> module = BindingGenerator().generate(tu) >>> print(compact(unparse(module))) <BLANKLINE> add = _bind('add', [c_float, c_float], c_float, arg_names=['a', 'b']) <BLANKLINE> """ log.debug(f'{d(depth)}Generate function `{elem.spelling}`') definition: Cursor = elem.get_definition() if definition is None: definition = elem rtype = self.generate_type(definition.result_type, depth + 1) args = definition.get_arguments() docstring = get_comment(elem) pyargs = [] arg_names = [] for a in args: atype = self.generate_type(a.type, depth + 1) pyargs.append(atype) if a.spelling != '': arg_names.append(T.Constant(a.spelling)) funnane = definition.spelling if not pyargs: pyargs = T.Name('None') else: pyargs = T.List(elts=pyargs) kwargs = [] if len(docstring) > 0: kwargs.append( T.Keyword('docstring', T.Constant(docstring, docstring=True))) if arg_names: kwargs.append(T.Keyword('arg_names', T.List(arg_names))) binding_call = T.Call(T.Name('_bind'), [T.Constant(funnane), pyargs, rtype], kwargs) return T.Assign([T.Name(funnane)], binding_call)
def generate_c_cast(self, elem, **kwargs): show_elem(elem) children = list(elem.get_children()) if len(children) == 2: type, expr = children traverse(type, print_fun=log.debug) traverse(expr, print_fun=log.debug) typecast = self.dispatch(type, **kwargs) if isinstance(typecast, str): typecast = T.Name(typecast) return T.Call(typecast, [self.dispatch(expr, **kwargs)]) if len(children) == 1: return self.dispatch(children[0], **kwargs) return None
def parse_call(self, expr, depth): log.debug(f'{d(depth)} parse_call {expr}') args = [] tok = self.peek() self.is_call.append(True) while tok.spelling != ')': args.append(self.parse_expression(depth + 1)) tok = self.peek() if tok.spelling == ',': self.next() tok = self.peek() self.is_call.pop() assert tok.spelling == ')' self.next() return T.Call(expr, args=args)
def generate_field(self, body, attrs, attr, anonymous_renamed, depth, **kwargs): if attr.kind == CursorKind.FIELD_DECL: # Rename anonymous types uid = self.get_underlying_type_uid(attr.type) log.debug( f'{d(depth)}uid: {uid} {attr.type.spelling} {anonymous_renamed}' ) if uid in anonymous_renamed: parent, name = anonymous_renamed[uid] if parent: typename = T.Attribute(T.Name(parent), name) else: typename = T.Name(name) if attr.type.kind == TypeKind.POINTER: typename = T.Call(T.Name('POINTER'), [typename]) else: typename = self.generate_type(attr.type, depth + 1) pair = T.Tuple() pair.elts = [T.Constant(attr.spelling), typename] attrs.elts.append(pair) elif attr.kind in (CursorKind.UNION_DECL, CursorKind.STRUCT_DECL): nested_struct = self.generate_struct_union( attr, depth + 1, nested=True, rename=anonymous_renamed) body.append(nested_struct) elif attr.kind == CursorKind.PACKED_ATTR: for attr2 in attr.get_children(): self.generate_field(body, attrs, attr2, anonymous_renamed, depth + 1) # attrs.append(field) else: show_elem(attr) print('NESTED ', attr.kind) raise RuntimeError('')
def _generate_type(self, type: Type, depth=0): # print(type.kind, type.spelling, self.type_registry.get(type.spelling, 'NOT FOUND')) if type.kind == TypeKind.VOID: return T.Name('None') if type.kind == TypeKind.INT: return T.Name('c_int') if type.kind == TypeKind.POINTER and type.get_pointee( ).kind == TypeKind.VOID: return T.Name('c_void_p') if type.kind == TypeKind.POINTER and type.get_pointee( ).kind == TypeKind.CHAR_S: return T.Name('c_char_p') if type.kind == TypeKind.POINTER: pointee: Type = type.get_pointee() # Typedef use the name that it is aliased to if pointee.kind is TypeKind.TYPEDEF: pointee = get_typename(pointee) elif pointee.kind != TypeKind.VOID: pointee = self.generate_type(pointee, depth + 1) else: pointee = T.Name('c_void_p') # Function pointer do not need to be decorated by POINTER call if isinstance(pointee, T.Call) and isinstance( pointee.func, T.Name) and pointee.func.id == 'CFUNCTYPE': return pointee # for native types we need to keep ref because python will copy them return T.Call(T.Name('POINTER'), [pointee]) if type.kind == TypeKind.CHAR_S: return T.Name('c_char_p') # if type.is_const_qualified(): # return T.Name(get_typename(type)) if type.kind == TypeKind.TYPEDEF: return get_typename(type) if type.kind == TypeKind.FUNCTIONPROTO or ( type.kind == TypeKind.UNEXPOSED and type.get_canonical().kind == TypeKind.FUNCTIONPROTO): # SDL_HitTest = CFUNCTYPE(SDL_HitTestResult, POINTER(SDL_Window), POINTER(SDL_Point), c_void_p) canon = type if type.kind == TypeKind.UNEXPOSED: canon = type.get_canonical() rtype = canon.get_result() args = [] for arg in canon.argument_types(): args.append(self.generate_type(arg, depth + 1)) returntype = self.generate_type(rtype, depth + 1) cargs = [returntype] cargs.extend(args) return T.Call(T.Name('CFUNCTYPE'), args=cargs) if type.kind == TypeKind.CONSTANTARRAY: t = self.generate_type(type.element_type, depth + 1) return T.BinOp(t, ast.Mult(), T.Constant(type.element_count)) # Represents a C array with an unspecified size. if type.kind == TypeKind.INCOMPLETEARRAY: element_type = self.generate_type(type.element_type, depth + 1) # char *[] -> char ** return T.Call(T.Name('POINTER'), [element_type]) # struct <TYPENAME> if type.kind == TypeKind.ELABORATED: return T.Name(get_typename(type).id.replace('struct', '').strip()) if type.kind == TypeKind.ENUM: return get_typename(type) # print('gentype') show_elem(type, print_fun=log.debug) return get_typename(type)
def generate_function(self, elem: Cursor): """Generate the Python function corresponding to the c function Examples -------- >>> from tide.generators.clang_utils import parse_clang >>> tu, index = parse_clang('double add(double a, double b);') >>> modules = APIGenerator().generate(tu) >>> for k, m in modules.items(): ... print(f'# {k}') ... print(unparse(m)) # temporary_buffer_1234.c <BLANKLINE> <BLANKLINE> <BLANKLINE> def add(a: double, b: double) -> double: return add(a, b) <BLANKLINE> """ definition: Cursor = elem.get_definition() if definition is None: definition = elem log.debug(f'Generate function {definition.spelling}') rtype = self.get_typename(definition.result_type) args = definition.get_arguments() pyargs = [] cargs = [] for a in args: atype = self.get_typename(a.type) aname = a.displayname pyargs.append(T.Arg(arg=aname, annotation=T.Name(atype))) cargs.append(T.Name(aname)) c_function = definition.spelling module, names = self.parse_name(c_function) fundef = T.FunctionDef(self.topyfunname(names), T.Arguments(args=pyargs)) fundef.returns = T.Name(rtype) fundef.body = [ # this is docstring but it is not unparsed correctly # T.Expr(T.Str(get_comment(elem))), T.Return(T.Call(T.Name(c_function), cargs)) ] # This could be a method if len(pyargs) > 0: # log.debug(f'Looking for {pyargs[0].annotation} in {self.type_registry}') selftype = pyargs[0].annotation classdef = None # For class grouping the first arg needs to be a reference to the class type lookuptype = selftype.id if isinstance(lookuptype, Ref): lookuptype = lookuptype.base classdef: T.ClassDef = self.type_registry.get(lookuptype) if isinstance(classdef, T.ClassDef): log.debug(f'found {classdef} for {lookuptype}') else: classdef = None if classdef is not None: # Remove annotation for `self` pyargs[0].arg = 'self' pyargs[0].annotation = None # replace the object by self.handle cargs[0] = T.Attribute(T.Name('self'), 'handle') # Generate Accessor properties if len(pyargs) == 1 and names[0] == 'get': # @property # def x(self): # return SDL_GetX(self.handle) offset = 1 if names[1] == 'set': offset = 2 fundef.name = self.topyfunname(names[offset:]) fundef.decorator_list.append(T.Name('property')) if len(pyargs) == 2 and (names[0] == 'get' or names[1] == 'get') and is_ref(pyargs[1].annotation): rtype = pyargs[1].annotation cargs[1] = T.Name('result') offset = 1 if names[1] == 'get': offset = 2 fundef.name = self.topyfunname(names[offset:]) fundef.decorator_list.append(T.Name('property')) fundef.returns = rtype fundef.args.args = [fundef.args.args[0]] fundef.body = [ # T.Expr(T.Str(get_comment(elem))), T.Assign([T.Name('result')], T.Call(rtype)), T.Expr(T.Call(T.Name(c_function), cargs)), # T.Assign([T.Name('err')], T.Call(T.Name(c_function), cargs)), # T.If(T.Name('err'), T.Return(T.Name('None'))), T.Return(T.Name('result')) ] if len(pyargs) == 2 and (names[0] == 'set' or names[1] == 'set'): # @x.setter # def x(self, x): # return SDL_SetX(self.handle, x) offset = 1 if names[1] == 'set': offset = 2 fundef.name = self.topyfunname(names[offset:]) fundef.decorator_list.append(T.Attribute(T.Name(fundef.name), 'setter')) # Standard method log.debug(f'Adding method to {classdef.name}') classdef.body.append(fundef) return None return fundef
def parse_cast(self, cast_expr, depth): tok = self.peek() log.debug(f'{d(depth)} parse_cast {tok.kind} {tok.spelling}') expr = self.parse_expression(depth + 1) return T.Call(cast_expr, [expr])