#!/usr/bin/python import pathlib import sys import subprocess import textwrap import yaml import dataclasses import click from functools import singledispatchmethod from dataclasses import dataclass, Field from typing import Any, get_args, get_origin, Union script_dir = pathlib.Path(__file__).parent if sys.version_info >= (3, 10): from types import UnionType else: UnionType = type(Union[int, float]) p = pathlib.Path("etc/needs-glue.txt") if p.exists(): needs_glue = set(p.read_text().split("\n")) else: needs_glue = set() typecodes = { 'bool': 'B', 'char': 'B', 'uint8_t': 'B', 'uint16_t': 'H', 'uint32_t': 'L', 'uint64_t': 'Q', 'float': 'f', 'double': 'd', 'int8_t': 'b', 'int16_t': 'h', 'int32_t': 'l', 'int64_t': 'q', } @dataclass(frozen=True) class Ptr: pointee: Any is_const: bool = False def __str__(self): return f"{self.pointee}*" @dataclass(frozen=True) class Array: pointee: Any bound: int is_const: bool = False @dataclass class CommonFields: comment: str | None = None only_for: str | None = None not_for: str | None = None api: str | None = None doc: str | None = None emit: bool = True def toplevel(cls): for f in dataclasses.fields(CommonFields): setattr(cls, f.name, f) cls.__annotations__[f.name] = CommonFields.__annotations__[f.name] return dataclass(cls) @dataclass class StructMember: name: str type: str comment: str | None = None @toplevel class Union: name: str members: list[StructMember] = dataclasses.field(default_factory=list) size: int | None = None @toplevel class Struct: name: str members: list[StructMember] = dataclasses.field(default_factory=list) size: int | None = None @dataclass class EnumMember: name: str value: int | str | None = None # a number or a 4 character literal old_name: str | None = None @toplevel class Enum: values: list[EnumMember] name: str | None = None @toplevel class Typedef: name: str type: str size: int | None = None # typedef AE_hdlr_t has this .. why? fulltype: any = None @toplevel class LowMem: name: str type: str address: int @toplevel class PyVerbatim: content: str | None = None typedef_content: str | None = None name: str | None = None @toplevel class Verbatim: verbatim: str @dataclass class Argument: type: str name: str = "" register: str | None = None @toplevel class Function: name: str trap: int | None = None args: list[Argument] = dataclasses.field(default_factory=list) executor: str | bool | None = None return_: str | None = None inline: str | None = None noinline: str | None = None dispatcher: str | None = None selector: int | None = None returnreg: str | None = None old_name: str | None = None @toplevel class FunPtr: name: str args: list[Argument] = dataclasses.field(default_factory=list) return_: str | None = None @toplevel class Dispatcher: name: str trap: int selector_location: str | None = None yaml_types = { 'function': Function, 'funptr': FunPtr, 'verbatim': Verbatim, 'pyverbatim': PyVerbatim, 'lowmem': LowMem, 'typedef': Typedef, 'enum': Enum, 'struct': Struct, 'union': Union, 'dispatcher': Dispatcher, } def fix_key(k): k = k.replace('-', '_') if k == 'return': k += "_" return k def identify(y): for k in y.keys(): if r := yaml_types.get(k): result = dict(y) if k != 'verbatim': # Verbatim just has a str value, it's not a namespace result.update(result.pop(k, {})) return result, r raise RuntimeError(f"Could not identify field type for {y!r}") @dataclass class OneOf: types: tuple[type] def __call__(self, value): for t in self.types: try: return t(value) except TypeError: continue else: raise TypeError(f"Could not convert {value} to any of {self.types}") def get_field_type(field: Field[Any] | type) -> Any: if isinstance(field, type): return field field_type = field.type if isinstance(field_type, str): raise RuntimeError("parameters dataclass may not use 'from __future__ import annotations") origin = get_origin(field_type) if origin in (Union, UnionType): return OneOf(tuple(a for a in get_args(field_type) if a is not None)) if origin is list: return [get_field_type(get_args(field_type)[0])] return field_type def yaml_to_type(y, t=None): if t is None: y, t = identify(y) if isinstance(t, OneOf): if isinstance(y, t.types): return y else: if isinstance(y, t): return y try: kwargs = {} fields = {f.name: f for f in dataclasses.fields(t)} for k, v in y.items(): k1 = fix_key(k) field = fields[k1] field_type = get_field_type(field) if isinstance(v, list): kwargs[k1] = [yaml_to_type(vi, field_type[0]) for vi in v] else: kwargs[k1] = yaml_to_type(v, field_type) return t(**kwargs) except (KeyError, TypeError, AttributeError) as e: raise TypeError(f"Converting node {y} to {t}") from e def load_defs(path): with open(path) as f: defs = yaml.safe_load(f) return [yaml_to_type(d) for d in defs] signed_integer_types = {'uint8_t', 'uint16_t', 'uint32_t'} unsigned_integer_types = {'bool', 'char', 'int8_t', 'int16_t', 'int32_t'} all_integer_types = signed_integer_types | unsigned_integer_types all_float_types = {"float", "double"} all_scalar_types = all_integer_types | all_float_types @dataclass class descr_maker_scalar: tag: str def __call__(self, emitter, offset): return f"MP_ROM_INT(UCTYPE_TYPE({self.tag}) | {offset})" @dataclass class descr_maker_struct: tag: str def __call__(self, emitter, offset): obj = emitter.common_definition( "const mp_rom_obj_tuple_t", f"ROM_TUPLE(MP_ROM_INT({offset}), MP_ROM_PTR((void*)&{self.tag}_obj))", ) return f"MP_ROM_PTR((void*)&{obj})" @dataclass class descr_maker_arr_scalar: tag: str size: int def __call__(self, emitter, offset): obj = emitter.common_definition( "const mp_rom_obj_tuple_t", f"ROM_TUPLE(MP_ROM_INT({offset} | UCTYPE_AGG(ARRAY)), MP_ROM_INT(UCTYPE_TYPE({self.tag}) | {self.size}))", ) return f"MP_ROM_PTR((void*)&{obj})" @dataclass class descr_maker_arr_struct: tag: str size: int def __call__(self, emitter, offset): obj = emitter.common_definition( "const mp_rom_obj_tuple_t", f"ROM_TUPLE(MP_ROM_INT({offset} | UCTYPE_AGG(ARRAY)), MP_ROM_INT({self.size}), MP_ROM_PTR((void*)(&{self.tag}_obj)))", ) return f"MP_ROM_PTR((void*)&{obj})" @dataclass class descr_maker_ptr_scalar: tag: str def __call__(self, emitter, offset): obj = emitter.common_definition( "const mp_rom_obj_tuple_t", f"ROM_TUPLE(MP_ROM_INT({offset} | UCTYPE_AGG(PTR)), MP_ROM_INT(UCTYPE_TYPE({self.tag})))", ) return f"MP_ROM_PTR((void*)&{obj})" class descr_maker_ptr_struct: def __init__(self, tag): self.tag = tag def __call__(self, emitter, offset): obj = emitter.common_definition( "const mp_rom_obj_tuple_t", f"ROM_TUPLE(MP_ROM_INT({offset} | UCTYPE_AGG(PTR)), MP_ROM_PTR((void*)&{self.tag}_obj))", ) return f"MP_ROM_PTR((void*)&{obj})" @dataclass class PointConverter: fieldname: str def emit_to_c(self, name_py, name_c): return f"Point {name_c} = Point_to_c({name_py}, MP_QSTR_{self.fieldname});\n" def emit_to_py(self, name_c): raise RuntimeError("not implemented") converters = { 'Point': PointConverter, } class ScalarConverter: def __init__(self, type_c, to_c, to_py): self.type_c = type_c self.to_c = to_c self.to_py = to_py def emit_decls(self): pass def emit_to_c(self, name_py, name_c): return f"{self.type_c} {name_c} = {self.to_c}({name_py});\n" def emit_to_py(self, name_c): return f"{self.to_py}({name_c})" def emit_call_arg(self, name_c): return name_c @dataclass class PtrConverter: emitter: object fieldname: str type_c: object type_obj: object deref: bool = False is_const: bool = False def emit_to_c(self, name_py, name_c): is_const = +self.is_const # to get 0/1, not True/False resolved_type = self.emitter.parse_type(self.type_c) if ( isinstance(resolved_type, Ptr) and self.emitter.parse_type(resolved_type.pointee) not in all_scalar_types ): type_obj = f"(void*)&{resolved_type.pointee}_obj" return f"{self.type_c} {name_c} = to_struct_helper({name_py}, {type_obj}, {is_const}, MP_QSTR_{self.fieldname}); // 1\n" else: return f"{self.type_c} {name_c} = to_struct_helper({name_py}, {self.type_obj}, {is_const}, MP_QSTR_{self.fieldname}); // 2\n" def emit_to_py(self, name_c): resolved_type = self.emitter.parse_type(self.type_c) if resolved_type in self.emitter.types: resolved_type = self.emitter.parse_type(self.emitter.types[resolved_type]) print("emit_to_py", self.type_c, resolved_type) if isinstance(resolved_type, Ptr): assert self.type_obj and self.type_obj != "null" type_str = resolved_type.pointee.replace("*", "Ptr") return f"from_struct_helper({name_c}, (void*)&{type_str}_obj) /* {self.type_c} 3 */" else: type_str = self.type_obj.replace("*", "Ptr") return f"from_struct_helper({name_c}, {self.type_obj}) /* {resolved_type} 4 */" def emit_call_arg(self, name_c): if self.deref: return f"*{name_c}" return name_c def make_converter(emitter, fieldname, type_c): print(f"make_converter {type_c=}") if converter := converters.get(type_c): return converter(fieldname) resolved_type = emitter.parse_type(type_c) if resolved_type in signed_integer_types: return ScalarConverter(resolved_type, "mp_obj_get_int", "mp_obj_new_int") if resolved_type in emitter.types: resolved_type = emitter.parse_type(emitter.types[resolved_type]) if resolved_type in emitter.funptrs: return ScalarConverter( resolved_type, f"({type_c})mp_obj_get_int_truncated", "mp_obj_new_int_from_ptr" ) if resolved_type in unsigned_integer_types: return ScalarConverter( resolved_type, "mp_obj_get_int_truncated", "mp_obj_new_int_from_uint" ) if type_c == "void*": return ScalarConverter(resolved_type, "void_ptr_from_py", "mp_obj_new_int_from_uint") if isinstance(resolved_type, Ptr): base_type = resolved_type.pointee if base_type in all_scalar_types: return PtrConverter( emitter, fieldname, type_c, f"(const mp_obj_type_t*)&{base_type}_obj", is_const=resolved_type.is_const, ) if type in emitter.structs or type_c in emitter.types: return PtrConverter( emitter, fieldname, type_c, f"(const mp_obj_type_t*)&{type_c}_obj", is_const=resolved_type.is_const, ) if base_type in emitter.structs: return PtrConverter( emitter, fieldname, type_c, f"(const mp_obj_type_t*)&{base_type}_obj", is_const=resolved_type.is_const, ) elif base_type in emitter.typedef_objs: return PtrConverter( emitter, fieldname, type_c, f"(const mp_obj_type_t*)&{base_type}_obj", is_const=resolved_type.is_const, ) emitter.info.append(f"need to handle typedef obj {base_type}") elif base_type != 'void': emitter.info.append(f"confused about {base_type} from {resolved_type} from {type_c}") return PtrConverter(emitter, fieldname, type_c, "NULL", is_const=resolved_type.is_const) print(f"note: {emitter.funptrs=}") raise ValueError(f"no converter possible for {type_c} ({resolved_type=})") class Processor: def __init__(self, modname): self.modname = modname self.decls = [] self.body = [] self.locals = [] self.definitions = {} self.info = [] self.unknowns = set() self.types = {} self.typedef_objs = set() self.structs = {} self.funptrs = set(("ProcPtr",)) self.decls_dedent(""" #include "extmod/multiverse_support.h" """) self.add_local("__name__", f"MP_ROM_QSTR(MP_QSTR_{self.modname})") def common_definition(self, c_type: str, c_value: str) -> str: k = (c_type, c_value) if k not in self.definitions: i = len(self.definitions) self.definitions[k] = i self.decls_dedent(f"static {c_type} common_{i} = {c_value};") return f"common_{self.definitions[k]}" def is_array(self, typename): if isinstance(typename, Array): return True if isinstance(typename, Ptr): return False if typename.count(']') + ("*" in typename) > 1: raise ValueError( f"array-of-array or pointer-to-array or array-of-pointers NYI {typename}" ) return typename.endswith("]") def remove_array(self, typename): if isinstance(typename, Array): return typename.pointee return typename.partition("[")[0] def remove_ptr(self, typename): if isinstance(typename, Ptr): return typename.pointee return typename.removesuffix("*") def remove_const(self, typename): if hasattr(typename, 'is_const'): return dataclasses.replace(typename, is_const=False) return typename.removeprefix("const ") def array_size(self, typename): if isinstance(typename, Array): return typename.bound return int(typename.partition("[")[2].removesuffix("]")) def is_ptr(self, typename): if isinstance(typename, Ptr): return True if isinstance(typename, Array): return False return typename.endswith("*") def is_scalar(self, typename): if hasattr(typename, 'pointee'): typename = typename.pointee return typename in all_scalar_types def is_const(self, typename): if hasattr(typename, 'is_const'): return typename.is_const return typename.startswith("const ") def decls_dedent(self, text): self.decls.append(textwrap.dedent(text.rstrip())) def body_dedent(self, text): self.body.append(textwrap.dedent(text.rstrip())) def parse_type(self, typestr): while typedef_type := self.types.get(typestr, None): typestr = typedef_type is_const = self.is_const(typestr) base_type = self.remove_const(typestr) if self.is_array(base_type): bound = self.array_size(base_type) base_type = self.parse_type(self.remove_array(base_type)) return Array(base_type, bound, is_const=is_const) elif self.is_ptr(base_type): base_type = self.remove_ptr(base_type) return Ptr(base_type, is_const=is_const) else: if is_const: base_type = self.parse_type(self.remove_const(base_type)) if isinstance(base_type, str): return f"const {base_type}" return dataclasses.replace(base_type, is_const=True) return typestr def typedefs(self, defs): for d in defs: if isinstance(d, Typedef): d.fulltype = self.parse_type(d.type) print(f"full type of {d.name} is {d.fulltype}") self.types[d.name] = d.type self.decls_dedent( f"MP_DECLARE_CTYPES_STRUCT({d.name}_obj); // typedef {d.fulltype} is_scalar? {self.is_scalar(d.fulltype)}" ) if isinstance(d, Struct) and d.members: self.structs[d.name] = d self.decls_dedent(f"MP_DECLARE_CTYPES_STRUCT({d.name}_obj); // struct") if isinstance(d, Union) and d.members: self.structs[d.name] = d self.decls_dedent(f"MP_DECLARE_CTYPES_STRUCT({d.name}_obj); // union") if isinstance(d, PyVerbatim) and d.typedef_content: self.decls_dedent(d.typedef_content) if isinstance(d, FunPtr): self.funptrs.add(d.name) def document(self, defs): self.body.append(f'"""{self.modname} - Wrapped MacOS APIs"""') self.body.append("import array") for d in defs: try: self.document_node(d) except Exception as e: raise RuntimeError(f"failed to document {d}") from e def pytype(self, ctype): if ctype is None: return None if ctype in {"LONGINT", "INTEGER"}: return "int" if ctype == "INTEGER*": return "array.array['h']" if ctype == "LONGINT*": return "array.array['i']" parsed = self.parse_type(ctype) print("pytype", ctype, "->", repr(parsed)) if isinstance(parsed, Ptr): return parsed.pointee return ctype @singledispatchmethod def document_node(self, node): if type(node) in self.unknowns: return self.unknowns.add(type(node)) raise RuntimeError(f"# Unknown {node!r:.68s}...") @document_node.register def document_typedef(self, typedef: Typedef): print(typedef) @document_node.register def document_struct(self, struct: Struct): self.body.append(f"class {struct.name}:") self.body.append(' """A uctypes structure"""') for m in struct.members: self.body.append(f' {m.name}: "{self.pytype(m.type)}"') @document_node.register def document_union(self, union: Union): self.body.append(f"class {union.name}:") self.body.append(' """A uctypes union"""') for m in union.members: self.body.append(f' {m.name}: "{self.pytype(m.type)}"') @document_node.register def document_pyverbatim(self, p: PyVerbatim): pass @document_node.register def document_function(self, f: Function): self.body.append(f"def {f.name}(") for i, m in enumerate(f.args): argname = m.name or f"arg{i}" self.body.append(f' {argname}: "{self.pytype(m.type)}",') self.body.append(f") -> {self.pytype(f.return_)}: ...\n") @document_node.register def document_enum(self, e: Enum): for m in e.values: self.body.append(f"{m.name} : int") @document_node.register def document_lm(self, lm: LowMem): # TODO type of LMGet/Set self.body.append(f"def LMGet{lm.name}() -> object: ...") self.body.append(f"def LMSet{lm.name}(value) -> None: ...") @document_node.register def document_dispatcher(self, d: Dispatcher): pass @document_node.register def document_funptr(self, f: FunPtr): pass @document_node.register def document_verbatim(self, lm: Verbatim): pass def emit(self, defs): for d in defs: print("emit", id(d), d) try: self.emit_node(d) except Exception as e: raise RuntimeError(f"failed to convert {d}") from e @singledispatchmethod def emit_node(self, node): if type(node) in self.unknowns: return self.unknowns.add(type(node)) raise RuntimeError(f"# Unknown {node!r:.68s}...") @emit_node.register def emit_dispatcher(self, dispatcher: Dispatcher): pass # Nothing to emit @emit_node.register def emit_typedef(self, typedef: Typedef): print("emit_typedef", typedef) self.body_dedent(f"// typedef {typedef}") name = typedef.name type = typedef.type if type.endswith("*") or type.endswith("]"): make_descr = self.type_details(typedef.fulltype) self.body_dedent(f"// {type} {make_descr=}") if make_descr is None: self.body_dedent(f"// {typedef}: no make_descr") return offset = 0 self.body_dedent(f""" MP_DEFINE_CTYPES_STRUCT({name}_obj, MP_QSTR_{name}, {make_descr(self, offset)}, LAYOUT_NATIVE); // 1 """) self.typedef_objs.add(name) self.add_local(name) offset = 0 else: self.body_dedent(f"// no need for {typedef} !?") def type_details(self, typename): if typename in self.funptrs: return None if isinstance(typename, Ptr): is_ptr = True is_array = False basetypename = typename.pointee print(f"{typename=} -- {basetypename=} {basetypename in self.types}") elif isinstance(typename, Array): is_ptr = False is_array = True fulltype = typename basetypename = typename.pointee print(f"{typename=} -- {basetypename=} {basetypename in self.types}") else: fulltype = self.parse_type(typename) is_ptr = isinstance(fulltype, Ptr) is_array = isinstance(fulltype, Array) basetypename = fulltype if isinstance(fulltype, str) else fulltype.pointee if basetypename in all_scalar_types: u = "U" if basetypename.startswith("u") else "" if basetypename == "bool": type_str = "UINT8" elif basetypename == "char": type_str = "INT8" elif basetypename == "double": type_str = "FLOAT64" elif basetypename == "float": type_str = "FLOAT32" elif basetypename.endswith(("32", "32_t")): type_str = f"{u}INT32" elif basetypename.endswith(("16", "16_t")): type_str = f"{u}INT16" elif basetypename.endswith(("8", "8_t")): type_str = f"{u}INT8" else: raise RuntimeError(f"teach me about {basetypename}") if is_ptr: descr = descr_maker_ptr_scalar(type_str) elif is_array: descr = descr_maker_arr_scalar(type_str, fulltype.bound) else: descr = descr_maker_scalar(type_str) else: if is_ptr: if typename in self.types: deref_typename = self.parse_type(self.types[typename]) if ( isinstance(deref_typename, Ptr) and deref_typename.pointee in self.structs or deref_typename.pointee in self.types ): typename = deref_typename.pointee descr = descr_maker_ptr_struct(typename) elif basetypename in self.structs: descr = descr_maker_ptr_struct(basetypename) elif basetypename in self.types: descr = descr_maker_ptr_struct(basetypename) else: descr = descr_maker_ptr_scalar("UINT8") elif is_array: descr = descr_maker_arr_struct(basetypename, fulltype.bound) else: descr = descr_maker_struct(basetypename) return descr def struct_make_table(self, struct): rows = [] for member in struct.members: make_descr = self.type_details(member.type) if make_descr is None: continue offset = f"offsetof({struct.name}, {member.name})" rows.append(f"{{ MP_ROM_QSTR(MP_QSTR_{member.name}), {make_descr(self, offset)} }},") return "\n".join(rows) def union_make_table(self, union): rows = [] for member in union.members: make_descr = self.type_details(member.type) if make_descr is None: continue rows.append(f"{{ MP_ROM_QSTR(MP_QSTR_{member.name}), {make_descr(self, 0)} }},") return "\n".join(rows) @emit_node.register def emit_union(self, e: Union): name = e.name self.body_dedent(f""" static const mp_rom_map_elem_t {name}_descr_table[] = {{ {self.union_make_table(e)} }}; static MP_DEFINE_CONST_DICT({name}_descr_dict, {name}_descr_table); MP_DEFINE_CTYPES_STRUCT({name}_obj, MP_QSTR_{name}, MP_ROM_PTR((void*)&{name}_descr_dict), LAYOUT_NATIVE); // 2 """) self.add_local(name) @emit_node.register def emit_struct(self, e: Struct): name = e.name ee = self.structs.get(e.name) if ee and (e is not ee): print(f"omitting {e} in favor of {ee}") return # better defn available self.body_dedent(f""" static const mp_rom_map_elem_t {name}_descr_table[] = {{ {self.struct_make_table(e)} }}; static MP_DEFINE_CONST_DICT({name}_descr_dict, {name}_descr_table); MP_DEFINE_CTYPES_STRUCT({name}_obj, MP_QSTR_{name}, MP_ROM_PTR((void*)&{name}_descr_dict), LAYOUT_NATIVE); // 3 """) self.add_local(name) @emit_node.register def emit_enum(self, e: Enum): for v in e.values: if v.value is not None and v.name is not None: self.locals.append(f"{{ MP_ROM_QSTR(MP_QSTR_{v.name}), MP_ROM_INT({v.value}) }},") # else: # self.info.append(f"enumerant without value: {v['name']}") @emit_node.register def emit_lowmem(self, lm: LowMem): name = lm.name address = lm.address typename = lm.type self.body_dedent(f""" static mp_obj_t LMGet{name}_fn(size_t n_args, const mp_obj_t *args) {{ return LMGet_common({address}, sizeof({typename}), n_args == 0 ? mp_const_none : args[0]); }} MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(LMGet{name}_obj, 0, 1, LMGet{name}_fn); static mp_obj_t LMSet{name}_fn(mp_obj_t value) {{ LMSet_common({address}, sizeof({typename}), value); return mp_const_none; }} MP_DEFINE_CONST_FUN_OBJ_1(LMSet{name}_obj, LMSet{name}_fn); """) self.add_local(f"LMGet{name}") self.add_local(f"LMSet{name}") def add_local(self, name, value=...): if value is ...: value = f"MP_ROM_PTR((void*)&{name}_obj)" self.locals.append(f"{{ MP_ROM_QSTR(MP_QSTR_{name}), {value} }},") @emit_node.register def emit_verbatim(self, v: Verbatim): pass # Ignore verbatim blocks @emit_node.register def emit_pyverbatim(self, v: PyVerbatim): if v.content: self.body.append(v.content) if v.name is not None: self.add_local(v.name) # {'args': [{'name': 'src_bitmap', 'type': 'BitMap*'}, # {'name': 'dst_bitmap', 'type': 'BitMap*'}, # {'name': 'src_rect', 'type': 'const Rect*'}, # {'name': 'dst_rect', 'type': 'const Rect*'}, # {'name': 'mode', 'type': 'INTEGER'}, # {'name': 'mask', 'type': 'RgnHandle'}], # 'executor': 'C_', # 'name': 'CopyBits', # 'trap': 43244} def fun_declare_args_enum(self, args): if args: argnames = [arg.name or f"arg{i}" for i, arg in enumerate(args)] args = ", ".join(f"ARG_{argname}" for argname in argnames) return f"enum {{ {args} }};" return "" @staticmethod def fun_declare_allowed_arg(arg): name = arg.name return f"{{ MP_QSTR_{name}, MP_ARG_OBJ | MP_ARG_REQUIRED, {{0}}, }}," def fun_parse_args(self, args): body = [ self.fun_declare_args_enum(args), "static const mp_arg_t allowed_args[] = {", *(self.fun_declare_allowed_arg(a) for a in args), "};", "mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];", "mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);", ] return "\n".join(f" {line}" for line in body) def make_converter(self, fieldname, typename): return make_converter(self, fieldname, typename) def fun_convert_arg(self, idx, arg): return self.make_converter(arg.name, arg.type).emit_to_c( f"args[{idx}].u_obj", arg.name or f"arg{idx}" ) def fun_convert_args(self, args): return "".join(" " + self.fun_convert_arg(i, a) for i, a in enumerate(args)) def fun_call_fun(self, fun): return_type = fun.return_ args = fun.args argnames = [arg.name or f"arg{i}" for i, arg in enumerate(args)] fun_args = ", ".join(argnames) if fun.inline: funcall = f"{fun.inline};" funcall = f"{fun.name}({fun_args});" if return_type: funcall = f"{return_type} retval = {funcall}" return " " + funcall def fun_convert_return(self, fun): return_type = fun.return_ if return_type: converter = self.make_converter(0, return_type) return f" return {converter.emit_to_py('retval')};\n" else: return " return mp_const_none;\n" @emit_node.register def emit_function(self, node: Function): name = node.name if name in needs_glue: self.info.append(f"Not binding {name}, it needs glue") return args = node.args if node.api == 'carbon': return self.body_dedent(f""" static mp_obj_t {name}_fn(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {{ {self.fun_parse_args(args)} {self.fun_convert_args(args)} {self.fun_call_fun(node)} {self.fun_convert_return(node)} }} MP_DEFINE_CONST_FUN_OBJ_KW({name}_obj, {len(args)}, {name}_fn); """) self.add_local(name) @emit_node.register def emit_funptr(self, node: FunPtr): pass # Ignore function pointers for now def make_output(self, target): def do_print(*args): print(*args, file=target) for row in self.decls: do_print(row) for row in self.body: do_print(row) do_print() do_print("static const mp_rom_map_elem_t module_globals_table[] = {") for row in self.locals: do_print(f" {row}") do_print("};") do_print("static MP_DEFINE_CONST_DICT(module_globals, module_globals_table);") do_print( textwrap.dedent(f""" const mp_obj_module_t {self.modname}_module = {{ .base = {{ &mp_type_module }}, .globals = (mp_obj_dict_t *)&module_globals, }}; MP_REGISTER_MODULE(MP_QSTR_{self.modname}, {self.modname}_module);""") ) for row in self.info: print(row, file=sys.stderr) @click.command @click.argument("defs_files", type=click.Path(path_type=pathlib.Path, exists=True), nargs=-1) @click.option("-o", "--output", type=click.Path(path_type=pathlib.Path), default=None) @click.option( "-t", "--typedefs", multiple=True, type=click.Path(path_type=pathlib.Path, exists=True) ) @click.option("-m", "--modname", type=str) @click.option("-d/-c", "--doc/--code", "do_doc", is_flag=True) @click.option("--format/-no-format", "do_format") def main(defs_files, output, modname, typedefs, do_format=False, do_doc=False): if modname is None: modname = defs_files[0].stem if output is None: if do_doc: output = pathlib.Path(f"{modname}.pyi") else: output = pathlib.Path(f"mod{modname}.c") processor = Processor(modname) for t in typedefs: defs = load_defs(t) if not do_doc: processor.typedefs(defs) defs = [] for f in defs_files: defs.extend(load_defs(f)) if not do_doc: processor.typedefs(defs) if do_doc: processor.document(defs) with open(output, "w") as f: for line in processor.body: print(line, file=f) else: processor.emit(defs) if do_format: tmpfile = output.with_suffix(".tmp.c") with open(tmpfile, "w") as f: processor.make_output(f) print(f"Formatting {output}: ", [script_dir / "codeformat.py", "-c", tmpfile]) subprocess.check_call([script_dir / "codeformat.py", "-c", tmpfile]) tmpfile.rename(output) else: with open(output, "w") as f: processor.make_output(f) if __name__ == '__main__': main()