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 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(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 generate_struct_fields(self, elem): fields = [] for e in elem.get_children(): if e.kind == CursorKind.FIELD_DECL: field_type = self.get_typename(e.type) if isinstance(field_type, str): field_type = T.Name(field_type) fields.append(T.Tuple([T.Str(e.spelling), field_type])) return T.Assign([T.Name('_fields_')], T.List(fields))
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 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 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_macro_definition(self, elem: Cursor, **kwargs): """Transform a macro into a function if possible Examples -------- >>> from tide.generators.clang_utils import parse_clang >>> tu, index = parse_clang('#define PI 3.14') >>> module = BindingGenerator().generate(tu) >>> print(compact(unparse(module))) <BLANKLINE> PI = 3.14 <BLANKLINE> >>> tu, index = parse_clang('' ... '#define SDL_AUDIO_ALLOW_FREQUENCY_CHANGE 0x00000001\\n' ... '#define SDL_AUDIO_ALLOW_FORMAT_CHANGE 0x00000002\\n' ... '#define SDL_AUDIO_ALLOW_CHANNELS_CHANGE 0x00000004\\n' ... '#define SDL_AUDIO_ALLOW_ANY_CHANGE (SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_FORMAT_CHANGE|SDL_AUDIO_ALLOW_CHANNELS_CHANGE)\\n' ... ) >>> module = BindingGenerator().generate(tu) >>> print(compact(unparse(module))) <BLANKLINE> SDL_AUDIO_ALLOW_FREQUENCY_CHANGE = 1 <BLANKLINE> SDL_AUDIO_ALLOW_FORMAT_CHANGE = 2 <BLANKLINE> SDL_AUDIO_ALLOW_CHANNELS_CHANGE = 4 <BLANKLINE> SDL_AUDIO_ALLOW_ANY_CHANGE = (SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | (SDL_AUDIO_ALLOW_FORMAT_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE)) <BLANKLINE> """ # builtin macros if elem.location.file is None: return log.debug(f'Macro definition {elem.spelling}') args = list(elem.get_arguments()) children = list(elem.get_children()) tokens = list(elem.get_tokens()) if len(args) != 0: for arg in args: show_elem(arg, print_fun=log.debug) assert False if len(children) != 0: for child in children: show_elem(child, print_fun=log.debug) assert False if len(tokens) == 1: return name, tok_args, tok_body = parse_macro(tokens) if len(tok_body) == 0: return if name.spelling == 'NULL': return T.Assign([T.Name('NULL')], T.Name('None')) try: bods = {t.spelling for t in tok_body} if not bods.isdisjoint(self.unsupported_macros): raise UnsupportedExpression() py_body = parse_macro2(name, tok_args, tok_body, self.definitions, self.type_registry, self.renaming) except UnsupportedExpression: self.unsupported_macros.add(name.spelling) body = [b.spelling for b in tok_body] log.warning( f'Unsupported expression, cannot transform macro {name.spelling} {"".join(body)}' ) return name = name.spelling # if name == 'SDL_TOUCH_MOUSEID': # print(py_body) # assert False if len(tok_args) == 0 and not isinstance(py_body, T.If): return T.Assign([T.Name(name)], py_body) func = T.FunctionDef( name, T.Arguments(args=[T.Arg(arg=a.spelling) for a in tok_args])) if isinstance(py_body, T.Expr): py_body = py_body.value # we use ifs as a makeshift Body expression if isinstance(py_body, T.If) and isinstance(py_body.test, T.Constant) \ and py_body.test.value is True: func.body = py_body.body else: func.body = [T.Return(py_body)] return func
def generate_enum(self, elem: Cursor, depth=0, **kwargs): """ Generate a enum class Notes ----- we cannot use enum.Enum because we need the class to be a ctype/c_int when they are used a arguments Examples -------- >>> from tide.generators.clang_utils import parse_clang >>> tu, index = parse_clang('enum Colors { Red, Green, Blue;};') >>> module = BindingGenerator().generate(tu) >>> print(compact(unparse(module))) <BLANKLINE> @enumeration class Colors(c_int): Red = 0 Green = 1 Blue = 2 <BLANKLINE> """ log.debug(f'{d(depth)}Generate Enum') name = self.get_name(elem) enum = [] ctypes = self.type_registry.get('int') common = self.find_duplicate_name(elem) if name == '': name = common if name[-1] == '_': name = name[:-1] if self.global_enum: enum.append(T.Assign([T.Name(name)], self.type_registry.get('int'))) 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 for value in elem.get_children(): if value.kind == CursorKind.ENUM_CONSTANT_DECL: n = shorten_name(self.get_name(value)) enum.append(T.Assign([T.Name(n)], T.Constant(value.enum_value))) else: log.error(f'Unexpected children {value.kind}') raise RuntimeError() if not self.global_enum: # this require renaming the code where the enum were used # not implemented yet self.import_enum = True enum_class = T.ClassDef(name, bases=[ctypes]) enum_class.body = enum enum_class.decorator_list.append(T.Name('enumeration')) return enum_class return enum
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
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