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 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_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_struct_union(self, elem: Cursor, depth=1, nested=False, rename=None): """Generate the Python function corresponding to the c function Examples -------- >>> from tide.generators.clang_utils import parse_clang >>> tu, index = parse_clang('struct Point { int x, y; };') >>> modules = APIGenerator().generate(tu) >>> for k, m in modules.items(): ... print(f'# {k}') ... print(unparse(m)) # temporary_buffer_1234.c <BLANKLINE> <BLANKLINE> <BLANKLINE> class Point(Structure): _fields_ = [('x', int), ('y', int)] <BLANKLINE> """ dataclass_type = 'struct' if elem.kind == CursorKind.UNION_DECL: dataclass_type = 'union' # -- c_name = self.get_name(elem, rename=rename) module, names = self.parse_name(c_name) pyname = self.topyclassname(names) class_def = T.ClassDef(pyname) # if this class is a C struct if True: c_fields = self.generate_struct_fields(elem) class_def.body.append(c_fields) class_def.bases.append(T.Name('Structure')) # Insert the def inside the registry so we can insert method to it self.type_registry[c_name] = class_def # If this class wraps a C struct if False: ctor = T.FunctionDef('__init__', args=T.Arguments(args=[T.Arg('self'), T.Arg('handle', T.Name(c_name))])) ctor.body = [ T.Assign([T.Attribute(T.Name('self'), 'handle')], T.Name('handle')) ] class_def.body.append(ctor) return class_def
def shorten_name(n): if len(n) == len(common): return n if self.short_enum_names: new_name = n[len(common):] if not new_name[0].isalpha(): return n # rename SDL_PIXELTYPE_UNKNOWN to SDL_PIXELTYPE.UNKNOWN # useful to get editor auto-completion to show relevant values self.renaming[n] = T.Attribute(T.Name(name), new_name) return new_name return n
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_struct_union(self, elem: Cursor, depth=1, nested=False, rename=None, **kwargs): """Generate a struct or union alias Examples -------- >>> from tide.generators.clang_utils import parse_clang >>> tu, index = parse_clang('struct Point { float x, y;};') >>> module = BindingGenerator().generate(tu) >>> print(compact(unparse(module))) <BLANKLINE> class Point(Structure): pass <BLANKLINE> Point._fields_ = [('x', c_float), ('y', c_float)] <BLANKLINE> >>> from tide.generators.clang_utils import parse_clang >>> tu, index = parse_clang('union Point { float x; int y;};') >>> module = BindingGenerator().generate(tu) >>> print(compact(unparse(module))) <BLANKLINE> class Point(Union): pass <BLANKLINE> Point._fields_ = [('x', c_float), ('y', c_int)] <BLANKLINE> """ log.debug(f'{d(depth)}Generate struct `{elem.spelling}`') pyname = self.get_name(elem, rename=rename, depth=depth + 1) base = 'Structure' if elem.kind == CursorKind.UNION_DECL: base = 'Union' # For recursive data structure # log.debug(pyname) self.type_registry[f'struct {pyname}'] = T.Name(pyname) self.type_registry[f'const struct {pyname}'] = T.Name(pyname) parent = pyname anonymous_renamed = self.find_anonymous_fields(elem, parent, depth=depth + 1) # Docstring is the first element of the body # T.Constant(get_comment(elem)) body = [] docstring = get_comment(elem) if docstring: body.append(T.Expr(T.Constant(docstring, docstring=True))) attrs = T.List() need_pass = len(body) attr: Cursor for attr in elem.get_children(): self.generate_field(body, attrs, attr, anonymous_renamed, depth) # insert pass even if we have a docstring because # we will remove the docstring later if need_pass == len(body): body.append(ast.Pass()) if not attrs.elts: return T.ClassDef(name=pyname, bases=[T.Name(base)], body=body) fields = T.Assign([T.Attribute(T.Name(pyname), '_fields_')], attrs) return [ T.ClassDef(name=pyname, bases=[T.Name(base)], body=body), fields ]
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
class APIPass: """Generate a more friendly API for C bindings Notes ----- * removes the explicit library namespace from the name of functions and struct * Rewrites functions as method if first argument is a pointer to struct * Method use original c argument names * Method has the origin c docstring * Method has a short name removing the object name and the library namespace * enum values are now scoped inside an enum class * enum values have shorter names since name clashing cannot happen anymore * rewrites function that takes pointer arguments to return multiple values """ def __init__(self): self.dispatcher = { 'Expr': self.expression, 'Assign': self.assign, 'ClassDef': self.class_definition, 'FunctionDef': self.function_definition } self.pre_dispatcher = { 'Expr': self.pre_expression, 'Assign': self.pre_assign, 'ClassDef': self.pre_class_definition, } self.ctypes = dict() self.wrappers = dict() self.wrappers_2_ctypes = dict() # keep the order to know if we can annotate with Name of with string self.wrapper_order = dict() # we will remove wrappers that do not have methods # i.e they are data struct only self.wrapper_method_count = defaultdict(int) self.wrapper_ctor = defaultdict(list) self.ctypes_fields = dict() self.new_code = [] self.names = Trie() self.rename_types = dict() self.current_class_name = None self.new_names = set() def pre_expression(self, expr: T.Expr, depth): values = self.preprocess(expr.value, depth) if isinstance(values, (tuple, list)): return [T.Expr(v) for v in values] return T.Expr(values) def pre_class_definition(self, class_def: T.ClassDef, depth=0): self.ctypes[class_def.name] = class_def def pre_assign(self, expr: T.Assign, depth): # <c-type>._fields_ = [] if match(expr.targets[0], 'Attribute') and match( expr.value, 'List') and expr.targets[0].attr == '_fields_': ctype_name = expr.targets[0].value.id data = [] self.ctypes_fields[ctype_name] = data for elem in expr.value.elts: name = elem.elts[0].value ctype = elem.elts[1] data.append((name, ctype)) def preprocess(self, expr, depth): handler = self.pre_dispatcher.get(expr.__class__.__name__, None) if handler is not None: handler(expr, depth) def preprocessor(self, module: T.Module, depth=0): for expr in module.body: self.preprocess(expr, depth) def post_process_class_defintion(self, class_def: T.ClassDef): """Make a final pass over the generated class to improve function names""" # move the documentation over to our new class c_class_name = self.wrappers_2_ctypes[class_def.name] c_class_def = self.ctypes[c_class_name] docstring = fetch_docstring(c_class_def) if docstring: class_def.body.insert(0, docstring) # -- class_name = class_def.name.lower() names = defaultdict(int) for expr in class_def.body: if not isinstance(expr, T.FunctionDef): continue for n in expr.name.split('_'): if len(n) > 0: names[n] += 1 names = list(names.items()) # remove names that are not used that often names = sorted(filter(lambda x: x[1] > 2, names), key=lambda x: x[1]) # this is the magic filter that makes thing work # by forcing the name we are removing to be part of the type name # we are almost sure to remove duplicated data that is not useful # even when it appears multiple times it can make sense to have it duplicated # Example: # read_le16, read_le32, read_le64 # write_le16, write_le32, write_le64 names = list( map(lambda x: x[0], filter(lambda x: x[0] in class_name, names))) for expr in class_def.body: if not isinstance(expr, T.FunctionDef): continue for useless_name in names: expr.name = clean_name(expr.name, useless_name) def group_constant_to_enums(self): pass def clean_up_enumeration(self, class_def: T.ClassDef): """Removes superfluous prefix""" _, names = parse_sdl_name(class_def.name) cl_name = class_name(*names) # make a short alias of the enum (without the hardcoded c namespace) if cl_name != class_def.name: self.new_code.append( (None, T.Assign([T.Name(cl_name)], T.Name(class_def.name)))) t = Trie() counts = 0 for expr in class_def.body: if match(expr, 'Assign') and match(expr.targets[0], 'Name'): constant_name = expr.targets[0].id t.insert(constant_name) counts += 1 _ = list(t.redundant_prefix()) if len(_) > 1: # select the shortest prefix that is common to all most_likely = _[0] for c, n in _: if len(n) < len(most_likely[1]): most_likely = (c, n) # check that the prefix is common to all good = True for c, n in _: if not most_likely[1] in n: good = False break if good: _ = [most_likely] else: _ = [] if len(_) == 1: c, namespace = _[0] # SDL insert a None/Max enum that does not follow the pattern if counts != c + 1: return class_def for expr in class_def.body: if match(expr, 'Assign') and match(expr.targets[0], 'Name'): constant_name = expr.targets[0].id expr.targets[0].id = clean_name(constant_name, namespace) return class_def def is_opaque_container(self, name): # does this class define fields return self.ctypes_fields.get(name, None) is None 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 function_definition(self, function_def: T.FunctionDef, depth): return function_def SELF_HANDLE = T.Attribute(T.Name('self'), 'handle') def rename(self, n): new_name = self.rename_types.get(n, None) if new_name: arg_type_order = self.wrapper_order.get(new_name, None) class_order = self.wrapper_order.get(self.current_class_name, None) # arg type is defined after current class if arg_type_order and class_order and arg_type_order > class_order: return T.Constant(new_name) if new_name == self.current_class_name: return T.Constant(new_name) return T.Name(new_name) return n 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 add_docstring(self, new_fun: T.FunctionDef, binding: BindCall, indent_level: int): docstring = binding.docstring if docstring: docstring.value = docstring.value.replace( '\n ', '\n' + ' ' * indent_level) new_fun.body.insert(0, T.Expr(docstring)) 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 get_arg_names(self, call: T.Call): arg_names = get_kwarg_arg('arg_names', call.keywords, None) if arg_names is not None: return [a.value.lower() for a in arg_names.elts] else: return self.ARGS 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_wrapper(self, call: BindCall): fun_name = call.function_name _, names = parse_sdl_name(fun_name) args, c_call = self.make_wrapping_call(call) new_name = function_name(*names) if new_name not in self.new_names: self.new_names.add(new_name) else: log.warning( f'{new_name} already exist in this scope cannot rename {fun_name}' ) new_name = fun_name new_fun = T.FunctionDef(new_name) new_fun.returns = self.rename(call.return_type) new_fun.args = args new_fun.body = [T.Return(c_call)] self.add_docstring(new_fun, call, 1) call.clear_kwargs() if self.is_multi_output(new_fun, offset=0): new_fun = self.rewrite_multi_output_function(new_fun, offset=0) self.new_code.append((None, new_fun)) return def process_bind_call(self, call: BindCall, parent): """Try to find the class this function belongs to""" fun_name = call.function_name # constructor self_name = call.is_constructor() if self_name and call.is_method() is None: class_def: T.ClassDef = self.wrappers.get(self_name) if class_def: return self.generate_constructor(class_def, call) return self.generate_function_wrapper(call) # --- self_name = call.is_method() # not a method if self_name is None: self.generate_function_wrapper(call) return parent self_def = self.ctypes.get(self_name) if not self_def: log.debug(f'Method `{fun_name}` does not have c-class') self.generate_function_wrapper(call) return parent # not a known class if not match(self_def, 'ClassDef'): self.generate_function_wrapper(call) return parent # Generate our new python class that will wrap the ctypes self_wrap: T.ClassDef = self.wrappers.get(self_name) if self_wrap is None and match(self_def, 'ClassDef'): log.debug( f'Method `{fun_name}` does not have wrapped class `{self_name}`' ) return parent self.generate_method(call, self_wrap) return parent handle_is_not_none = T.Assert(test=T.Compare( left=T.Attribute(value=T.Name(id='self', ctx=T.Load()), attr='handle', ctx=T.Load()), ops=[T.IsNot()], comparators=[T.Constant(value=None, kind=None)]), msg=None) def is_multi_output(self, func: T.FunctionDef, offset): expr = func.body[0] if not match(expr, 'Expr', ('value', 'Constant')): return False expr: T.Constant = func.body[0].value if not expr.docstring: return False # if doc string specify it returns an error # or if function is known to return nothing if (':return 0 on success, or -1' not in expr.value) and (match( func.returns, 'Name') and func.returns.id != 'None'): return False if len(func.args.args[offset:]) == 0: return False count = 0 # multi output should group all the output at the end of the function call previous_was_ptr = False for arg in func.args.args[offset:]: if match(arg.annotation, 'Call', ('func', 'Name')) and arg.annotation.func.id == 'POINTER': count += 1 previous_was_ptr = True elif previous_was_ptr: return False if count > 0: log.debug(f'{func.name} has multiple outputs') return True return False 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 struct_fields(self, expr: T.Assign): ctype_name = expr.targets[0].value.id assert ctype_name in self.ctypes_fields def assign(self, expr: T.Assign, depth): # <function> = _bind('c-function', [cargs], rtype, docstring=, arg_names=) if BindCall.is_binding_call(expr.value): expr = self.process_bind_call(BindCall(expr.value), expr) # <c-type>._fields_ = [] elif match(expr.targets[0], 'Attribute') and match( expr.value, 'List') and expr.targets[0].attr == '_fields_': self.struct_fields(expr) # <Name> = <Name> elif match(expr.value, 'Name') and match(expr.targets[0], 'Name'): alias_name = expr.targets[0].id aliased_name = expr.value.id aliased_ctype = self.ctypes.get(aliased_name) aliased_wrapped = self.wrappers.get(aliased_name) self.rename_types[aliased_name] = alias_name self.ctypes[alias_name] = aliased_ctype self.wrappers[alias_name] = aliased_wrapped return expr def expression(self, expr: T.Expr, depth): values = self.dispatch(expr.value, depth) if isinstance(values, (tuple, list)): return [T.Expr(v) for v in values] return T.Expr(values) def generate(self, module: T.Module, depth=0): new_module: T.Module = ast.Module() new_module.body = [] self.preprocessor(module) for expr in module.body: self.dispatch(expr, depth + 1) # insert our new bindings at the end for k, v in self.new_code: if isinstance(v, T.ClassDef): if self.wrapper_method_count.get(v.name, 0) > 0: self.post_process_class_defintion(v) module.body.append(v) elif v.name in self.wrappers_2_ctypes: c_struct = self.wrappers_2_ctypes.get(v.name) # make it an alias for the ctype module.body.append( T.Expr(T.Assign([T.Name(v.name)], T.Name(c_struct)))) else: module.body.append(T.Expr(v)) return module def dispatch(self, expr, depth) -> T.Expr: handler = self.dispatcher.get(expr.__class__.__name__, None) if handler is None: print(expr) assert False return handler(expr, depth)
def parse_expression_1(self, lhs, min_precedence, depth): lookahead = self.peek() precedence = fetch_precedence(lookahead.spelling) log.debug( f'{d(depth)} parse_expression_1 {lhs} {lookahead.kind} {lookahead.spelling}' ) while lookahead and precedence is not None and precedence >= min_precedence: op = lookahead self.next() rhs = self.parse_primary(depth + 1) lookahead = self.peek() if lookahead is None: break is_binary = is_binary_operator(lookahead.spelling) lookahead_pred = fetch_precedence(lookahead.spelling) is_right_asso = is_right_associative(lookahead.spelling) while lookahead and (is_binary and lookahead_pred > precedence ) or (is_right_asso and lookahead_pred == precedence): rhs = self.parse_expression_1(rhs, lookahead_pred, depth + 1) lookahead = self.peek() is_binary = is_binary_operator(lookahead.spelling) lookahead_pred = fetch_precedence(lookahead.spelling) is_right_asso = is_right_associative(lookahead.spelling) if lookahead.spelling == ')': break # the result of applying op with operands lhs and rhs pyop = fetch_python_op(op.spelling) if pyop is not None and not isinstance(pyop, str): if is_comparison(op.spelling): lhs = T.Compare(left=lhs, ops=[pyop()], comparators=[rhs]) elif is_bool(op.spelling): lhs = T.BoolOp(op=pyop(), values=[lhs, rhs]) else: lhs = T.BinOp(left=lhs, op=pyop(), right=rhs) elif pyop == 'if': raise UnsupportedExpression() elif pyop == 'assign': lhs = T.Assign(targets=[lhs], value=rhs) elif op.spelling == '[': lhs = T.Subscript(value=lhs, slice=T.Index(value=rhs), ctx=T.Load()) tok = self.peek() assert tok.spelling == ']' self.next() elif op.spelling == '->': if isinstance(rhs, T.Name): rhs = rhs.id lhs = T.Attribute(lhs, rhs, ctx=T.Load()) elif op.spelling == ',': lhs = T.If(T.Constant(True), [T.Expr(lhs), T.Return(rhs)]) else: show_elem(op) assert False precedence = fetch_precedence(lookahead.spelling) return lhs