from .schema import * from .writer import Writer from .enum import gen_enum from .struct import gen_struct from .function import gen_function from .converters import is_vmath_type, has_vmath_converter, set_enum_converter from typing import TYPE_CHECKING if TYPE_CHECKING: from .meta import Header class Library: def __init__(self, name: str) -> None: self.name = name # ['defines', 'structs', 'aliases', 'enums', 'callbacks', 'functions'] self.structs = [] # type: list[Struct] self.aliases = {} # type: dict[str, str | c_ast.Node] self.enums = [] # type: list[Enum] self.functions = [] # type: list[Function] self.callbacks = set() # type: set[str] def unalias(self, name: str) -> c_ast.Node | str: while name in self.aliases: node = self.aliases[name] if isinstance(node, str): name = node else: return node return name def build(self, *, glue_dir='.', stub_dir='.', includes: list[str] | None = None): self.remove_unsupported() for k in self.aliases.keys(): node = self.unalias(k) if isinstance(node, c_ast.Enum): set_enum_converter(k) w, pyi_w = Writer(), Writer() if has_vmath_converter(): pyi_w.write('from vmath import vec2, vec3, vec2i, vec3i, mat3x3, color32') pyi_w.write('from typing import overload') pyi_w.write('from stdc import intptr') pyi_w.write('') w.write('#include "pocketpy.h"') w.write(f'#include ') w.write(f'#include ') if includes: for include in includes: w.write(f'#include "{include}"') w.write('') w.write('#define ADD_ENUM(name) py_newint(py_emplacedict(mod, py_name(#name)), name)') w.write('') for k in self.aliases.keys(): node = self.unalias(k) if isinstance(node, str): w.write(f'#define tp_user_{k} tp_user_{node}') w.write('') reg_exprs = [ gen_struct(w, pyi_w, struct) for struct in self.structs ] w.write('/* functions */') for function in self.functions: gen_function(w, pyi_w, function) w.write(f'void py__add_module_{self.name}() {{') w.indent() w.write(f'py_GlobalRef mod = py_newmodule("{self.name}");') w.write('/* structs */') for reg_expr in reg_exprs: w.write(reg_expr) w.write('/* aliases */') pyi_w.write('# aliases') for k in self.aliases.keys(): node = self.unalias(k) if isinstance(node, str): w.write(f'py_setdict(mod, py_name("{k}"), py_getdict(mod, py_name("{node}")));') pyi_w.write(f'{k} = {node}') elif isinstance(node, c_ast.Enum): w.write(f'py_setdict(mod, py_name("{k}"), py_tpobject(tp_int));') pyi_w.write(f'{k} = int') w.write('/* functions */') for function in self.functions: w.write(f'py_bindfunc(mod, "{function.name}", &cfunc__{function.name});') w.write('/* enums */') pyi_w.write('# enums') for enum in self.enums: gen_enum(w, pyi_w, enum) w.dedent() w.write('}') with open(f'{glue_dir}/{self.name}.c', 'w') as f: f.write(str(w)) with open(f'{stub_dir}/{self.name}.pyi', 'w') as f: f.write(str(pyi_w)) def remove_unsupported(self): functions = [] for f in self.functions: if f.params and f.params[-1].type == '...': print('[WARN]', f.signature(), 'is variadic') continue for p in f.params: if p.type in self.callbacks: print('[WARN]', f.signature(), 'has callback param') break else: functions.append(f) self.functions.clear() self.functions.extend(functions) @staticmethod def from_raylib(data: dict): self = Library('raylib') for struct in data['structs']: name = struct['name'] if is_vmath_type(name): print(f'[INFO] {name} is a vmath type, skipping') continue self.structs.append(Struct( name=struct['name'], typedef_name=struct['name'], desc=struct['description'], fields=[StructField( type=field['type'], name=field['name'], desc=field['description'] ) for field in struct['fields']] )) for alias in data['aliases']: self.aliases[alias['name']] = str(alias['type']) for enum in data['enums']: self.enums.append(Enum( name=enum['name'], desc=enum['description'], values=[EnumValue( name=value['name'], value=value['value'], desc=value['description'] ) for value in enum['values']] )) for function in data['functions']: self.functions.append(Function( name=function['name'], desc=function['description'], params=[FunctionParam( type=param['type'], name=param['name'] ) for param in function['params'] ] if 'params' in function else [], ret_type=function['returnType'] )) for callback in data['callbacks']: self.callbacks.add(callback['name']) return self @staticmethod def from_header(name: str, header: 'Header'): from ffigen.meta import schema self = Library(name) for type in header.types: if isinstance(type, schema.NamedFields): if type.is_opaque(): continue else: fields = type.fields assert fields is not None self.structs.append(Struct( name=type.name, typedef_name=type.typedef_name, fields=[StructField( type=field_type, name=field_name ) for field_name, field_type in fields.items()] )) elif isinstance(type, schema.Enum): self.enums.append(Enum( name=type.name, values=[EnumValue( name=value, value=None ) for value in type.values] )) for k, v in header.type_aliases.items(): self.aliases[k] = v for function in header.functions: self.functions.append(Function( name=function.name, params=[FunctionParam( type=param[0], name=param[1] or f'_{i}' ) for i, param in enumerate(function.args)], ret_type=function.ret )) return self