circuitpython/tools/mkapi.py

1053 lines
33 KiB
Python
Executable file

#!/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()