|
| 1 | +import pathlib |
| 2 | +import sys |
| 3 | +import textwrap |
| 4 | +import yaml |
| 5 | + |
| 6 | +with open(sys.argv[1]) as f: |
| 7 | + defs = yaml.safe_load(f) |
| 8 | + |
| 9 | +if len(sys.argv) > 2: |
| 10 | + modname = sys.argv[2] |
| 11 | +else: |
| 12 | + modname = pathlib.Path(sys.argv[1]).stem |
| 13 | + |
| 14 | +signed_integer_types = {'uint8_t', 'uint16_t', 'uint32_t', 'Fixed', 'GrafVerb', 'CharParameter'} |
| 15 | +unsigned_integer_types = {'int8_t', 'int16_t', 'int32_t'} |
| 16 | + |
| 17 | + |
| 18 | +class PointConverter: |
| 19 | + def emit_to_c(self, name_py, name_c): |
| 20 | + return f"Point {name_c} = Point_to_c({name_py});\n" |
| 21 | + |
| 22 | + def emit_to_py(self, name_c): |
| 23 | + return f"NEW_TUPLE({name_c}.x, {name_c}.y)" |
| 24 | + |
| 25 | + |
| 26 | +converters = { |
| 27 | + 'Point': PointConverter, |
| 28 | +} |
| 29 | + |
| 30 | + |
| 31 | +class SimpleConverter: |
| 32 | + def __init__(self, type_c, to_c, to_py): |
| 33 | + self.type_c = type_c |
| 34 | + self.to_c = to_c |
| 35 | + self.to_py = to_py |
| 36 | + |
| 37 | + def emit_decls(self): |
| 38 | + pass |
| 39 | + |
| 40 | + def emit_to_c(self, name_py, name_c): |
| 41 | + return f"{self.type_c} {name_c} = {self.to_c}({name_py});\n" |
| 42 | + |
| 43 | + def emit_to_py(self, name_c): |
| 44 | + return f"{self.to_py}({name_c})" |
| 45 | + |
| 46 | + |
| 47 | +class PtrConverter: |
| 48 | + def __init__(self, type_c): |
| 49 | + self.type_c = type_c |
| 50 | + |
| 51 | + def emit_to_c(self, name_py, name_c): |
| 52 | + is_const = +self.type_c.startswith("const ") # to get 0/1, not True/False |
| 53 | + return f"{self.type_c} {name_c} = to_c_helper({name_py}, sizeof(*{name_c}), {is_const});\n" |
| 54 | + |
| 55 | + def emit_to_py(self, name_c): |
| 56 | + is_signed_hint = not self.type_c.lower().startswith('u') |
| 57 | + return f"from_c_helper({name_c}, sizeof(*{name_c}), {+is_signed_hint})" |
| 58 | + |
| 59 | + |
| 60 | +def make_converter(type_c): |
| 61 | + if converter := converters.get(type_c): |
| 62 | + return converter() |
| 63 | + if type_c in signed_integer_types: |
| 64 | + return SimpleConverter(type_c, "mp_obj_get_int", "mp_obj_new_int") |
| 65 | + if type_c in unsigned_integer_types: |
| 66 | + return SimpleConverter(type_c, "mp_obj_get_int_truncated", "mp_obj_new_int_from_uint") |
| 67 | + if type_c.endswith("*") or type_c.endswith("Handle") or type_c.endswith("Ptr"): |
| 68 | + return PtrConverter(type_c) |
| 69 | + raise ValueError(f"no converter possible for {type_c}") |
| 70 | + |
| 71 | + |
| 72 | +class Processor: |
| 73 | + def __init__(self, modname): |
| 74 | + self.modname = modname |
| 75 | + self.decls = [] |
| 76 | + self.body = [] |
| 77 | + self.locals = [] |
| 78 | + self.info = [] |
| 79 | + self.unknowns = set() |
| 80 | + self.types = { |
| 81 | + 'Byte': 'uint8_t', |
| 82 | + 'Boolean': 'uint8_t', |
| 83 | + 'SignedByte': 'int8_t', |
| 84 | + 'Sint8': 'int8_t', |
| 85 | + 'Uint8': 'uint8_t', |
| 86 | + 'Sint16': 'int16_t', |
| 87 | + 'Uint16': 'uint16_t', |
| 88 | + 'Sint32': 'int32_t', |
| 89 | + 'Uint32': 'uint32_t', |
| 90 | + 'ULONGINT': 'uint32_t', |
| 91 | + 'INTEGER': 'int16_t', |
| 92 | + 'LONGINT': 'int32_t', |
| 93 | + } |
| 94 | + self.body_dedent(""" |
| 95 | + #include "py/obj.h" |
| 96 | + #include "py/runtime.h" |
| 97 | + #include <Multiverse.h> |
| 98 | +
|
| 99 | + // Relies on gcc Variadic Macros and Statement Expressions |
| 100 | + #define NEW_TUPLE(...) \ |
| 101 | + ({mp_obj_t _z[] = {__VA_ARGS__}; mp_obj_new_tuple(MP_ARRAY_SIZE(_z), _z); }) |
| 102 | +
|
| 103 | + void *to_c_helper(mp_obj_t obj, size_t objsize, bool is_const) { |
| 104 | + if (mp_obj_is_int(obj)) { |
| 105 | + return (void*)mp_obj_get_int_truncated(obj); |
| 106 | + } |
| 107 | + mp_buffer_info_t bufinfo = {0}; |
| 108 | + mp_get_buffer_raise(obj, &bufinfo, is_const ? MP_BUFFER_READ : MP_BUFFER_READ | MP_BUFFER_WRITE); |
| 109 | + if (bufinfo.len != objsize) { |
| 110 | + mp_raise_ValueError(MP_ERROR_TEXT("buffer has wrong length")); |
| 111 | + } |
| 112 | + return bufinfo.buf; |
| 113 | + } |
| 114 | +
|
| 115 | + Point Point_to_c(mp_obj_t obj) { |
| 116 | + Point result; |
| 117 | + if (mp_obj_len_maybe(obj) == MP_OBJ_NEW_SMALL_INT(2)) { |
| 118 | + result.h = mp_obj_get_int(mp_obj_subscr(obj, mp_obj_new_int(0), MP_OBJ_SENTINEL)); |
| 119 | + result.v = mp_obj_get_int(mp_obj_subscr(obj, mp_obj_new_int(1), MP_OBJ_SENTINEL)); |
| 120 | + } else { |
| 121 | + result = *(Point*)to_c_helper(obj, sizeof(Point), true); |
| 122 | + } |
| 123 | + return result; |
| 124 | + } |
| 125 | +
|
| 126 | + mp_obj_t from_c_helper(void *buf, size_t objsize, bool is_signed_hint) { |
| 127 | + return mp_obj_new_int_from_uint(*(unsigned long*)buf); |
| 128 | + } |
| 129 | +
|
| 130 | + mp_obj_t LMGet_common(long address, size_t objsize, mp_obj_t arg) { |
| 131 | + if (arg == mp_const_none) { |
| 132 | + return mp_obj_new_bytearray(objsize, (void*)address); |
| 133 | + } |
| 134 | + mp_buffer_info_t bufinfo = {0}; |
| 135 | + mp_get_buffer_raise(arg, &bufinfo, MP_BUFFER_WRITE); |
| 136 | + if (bufinfo.len != objsize) { |
| 137 | + mp_raise_ValueError(MP_ERROR_TEXT("buffer has wrong length")); |
| 138 | + } |
| 139 | + memcpy(bufinfo.buf, (void*)address, objsize); |
| 140 | + return arg; |
| 141 | + } |
| 142 | + void LMSet_common(long address, size_t objsize, mp_obj_t arg) { |
| 143 | + mp_buffer_info_t bufinfo = {0}; |
| 144 | + mp_get_buffer_raise(arg, &bufinfo, MP_BUFFER_READ); |
| 145 | + if (bufinfo.len != objsize) { |
| 146 | + mp_raise_ValueError(MP_ERROR_TEXT("buffer has wrong length")); |
| 147 | + } |
| 148 | + memcpy((void*)address, bufinfo.buf, objsize); |
| 149 | + } |
| 150 | + """) |
| 151 | + |
| 152 | + def resolve_type(self, typename): |
| 153 | + is_const = self.is_const(typename) |
| 154 | + typename = typename.removeprefix("const ") |
| 155 | + |
| 156 | + while typename in self.types: |
| 157 | + typename = self.types[typename] |
| 158 | + |
| 159 | + if is_const and self.is_ptr(typename): |
| 160 | + return f"const {typename}" |
| 161 | + |
| 162 | + return typename |
| 163 | + |
| 164 | + def is_ptr(self, typename): |
| 165 | + return self.resolve_type(typename).endswith("*") |
| 166 | + |
| 167 | + def is_const(self, typename): |
| 168 | + return typename.startswith("const ") |
| 169 | + |
| 170 | + def body_dedent(self, text): |
| 171 | + self.body.append(textwrap.dedent(text.rstrip())) |
| 172 | + |
| 173 | + def process(self, defs): |
| 174 | + for d in defs: |
| 175 | + self.only_for = d.pop('only-for', None) |
| 176 | + self.not_for = d.pop('not-for', None) |
| 177 | + self.api = d.pop('api', None) |
| 178 | + |
| 179 | + if len(d) != 1: |
| 180 | + raise ValueError(f"Definition with more than one key: {defs!r}") |
| 181 | + k, v = d.popitem() |
| 182 | + meth = getattr(self, 'handle_' + k, lambda v: self.handle_unknown(k, v)) |
| 183 | + meth(v) |
| 184 | + |
| 185 | + def handle_unknown(self, k, v): |
| 186 | + if k in self.unknowns: |
| 187 | + return |
| 188 | + self.unknowns.add(k) |
| 189 | + self.info.append(f"# Unknown {k} {v!r:.55s}...") |
| 190 | + |
| 191 | + def handle_enum(self, e): |
| 192 | + for v in e['values']: |
| 193 | + if 'value' in v: |
| 194 | + self.locals.append( |
| 195 | + f"{{ MP_ROM_QSTR(MP_QSTR_{v['name']}), MP_ROM_INT({v['value']}) }}," |
| 196 | + ) |
| 197 | + # else: |
| 198 | + # self.info.append(f"enumerant without value: {v['name']}") |
| 199 | + |
| 200 | + def handle_lowmem(self, lm): |
| 201 | + name = lm['name'] |
| 202 | + address = lm['address'] |
| 203 | + typename = lm['type'] |
| 204 | + self.body_dedent(f""" |
| 205 | + static mp_obj_t LMGet{name}_fn(size_t n_args, const mp_obj_t *args) {{ |
| 206 | + return LMGet_common({address}, sizeof({typename}), n_args == 0 ? mp_const_none : args[0]); |
| 207 | + }} |
| 208 | + MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(LMGet{name}_obj, 0, 1, LMGet{name}_fn); |
| 209 | +
|
| 210 | + static mp_obj_t LMSet{name}_fn(mp_obj_t value) {{ |
| 211 | + LMSet_common({address}, sizeof({typename}), value); |
| 212 | + return mp_const_none; |
| 213 | + }} |
| 214 | + MP_DEFINE_CONST_FUN_OBJ_1(LMSet{name}_obj, LMSet{name}_fn); |
| 215 | + """) |
| 216 | + self.locals.append( |
| 217 | + f"{{ MP_ROM_QSTR(MP_QSTR_LMGet{name}), MP_ROM_PTR(&LMGet{name}_obj) }}," |
| 218 | + ) |
| 219 | + self.locals.append( |
| 220 | + f"{{ MP_ROM_QSTR(MP_QSTR_LMSet{name}), MP_ROM_PTR(&LMSet{name}_obj) }}," |
| 221 | + ) |
| 222 | + |
| 223 | + def handle_typedef(self, v): |
| 224 | + pass # ignore typedefs for now ?? |
| 225 | + |
| 226 | + def handle_verbatim(self, v): |
| 227 | + pass # Ignore verbatim blocks |
| 228 | + |
| 229 | + # {'args': [{'name': 'src_bitmap', 'type': 'BitMap*'}, |
| 230 | + # {'name': 'dst_bitmap', 'type': 'BitMap*'}, |
| 231 | + # {'name': 'src_rect', 'type': 'const Rect*'}, |
| 232 | + # {'name': 'dst_rect', 'type': 'const Rect*'}, |
| 233 | + # {'name': 'mode', 'type': 'INTEGER'}, |
| 234 | + # {'name': 'mask', 'type': 'RgnHandle'}], |
| 235 | + # 'executor': 'C_', |
| 236 | + # 'name': 'CopyBits', |
| 237 | + # 'trap': 43244} |
| 238 | + |
| 239 | + def fun_declare_args_enum(self, args): |
| 240 | + if args: |
| 241 | + args = ", ".join(f"ARG_{arg['name']}" for arg in args) |
| 242 | + return f"enum {{ {args} }};" |
| 243 | + return "" |
| 244 | + |
| 245 | + @staticmethod |
| 246 | + def fun_declare_allowed_arg(arg): |
| 247 | + name = arg['name'] |
| 248 | + return f"{{ MP_QSTR_{name}, MP_ARG_OBJ | MP_ARG_REQUIRED, {{0}}, }}," |
| 249 | + |
| 250 | + def fun_parse_args(self, args): |
| 251 | + body = [ |
| 252 | + self.fun_declare_args_enum(args), |
| 253 | + "static const mp_arg_t allowed_args[] = {", |
| 254 | + *(self.fun_declare_allowed_arg(a) for a in args), |
| 255 | + "};", |
| 256 | + "mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];", |
| 257 | + "mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);", |
| 258 | + ] |
| 259 | + return "\n".join(f" {line}" for line in body) |
| 260 | + |
| 261 | + def make_converter(self, typename): |
| 262 | + print(f"make_converter {typename}", file=sys.stderr) |
| 263 | + if not typename.endswith('*'): |
| 264 | + typename = self.resolve_type(typename) |
| 265 | + print(f" -> {typename}", file=sys.stderr) |
| 266 | + return make_converter(typename) |
| 267 | + |
| 268 | + def fun_convert_arg(self, idx, arg): |
| 269 | + return self.make_converter(arg['type']).emit_to_c(f"args[{idx}].u_obj", arg['name']) |
| 270 | + |
| 271 | + def fun_convert_args(self, args): |
| 272 | + return "".join(" " + self.fun_convert_arg(i, a) for i, a in enumerate(args)) |
| 273 | + |
| 274 | + def fun_call_fun(self, fun): |
| 275 | + return_type = fun.get('return', None) |
| 276 | + args = fun.get('args', []) |
| 277 | + fun_args = ", ".join(arg['name'] for arg in args) |
| 278 | + funcall = f"{fun['name']}({fun_args});" |
| 279 | + if return_type: |
| 280 | + funcall = f"{return_type} retval = {funcall}" |
| 281 | + return " " + funcall |
| 282 | + |
| 283 | + def fun_convert_return(self, fun): |
| 284 | + return_type = fun.get('return', None) |
| 285 | + if return_type: |
| 286 | + converter = self.make_converter(return_type) |
| 287 | + return f" return {converter.emit_to_py('retval')};// TODO" |
| 288 | + else: |
| 289 | + return " return mp_const_none;" |
| 290 | + |
| 291 | + def handle_function(self, fun): |
| 292 | + name = fun['name'] |
| 293 | + args = fun.get('args', []) |
| 294 | + print(f"## API={self.api}", file=sys.stderr) |
| 295 | + if self.api == 'carbon': |
| 296 | + return |
| 297 | + self.body_dedent(f""" |
| 298 | + mp_obj_t {name}_fn(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {{ |
| 299 | + {self.fun_parse_args(args)} |
| 300 | + {self.fun_convert_args(args)} |
| 301 | + {self.fun_call_fun(fun)} |
| 302 | + {self.fun_convert_return(fun)} |
| 303 | + }} |
| 304 | + MP_DEFINE_CONST_FUN_OBJ_KW({name}_obj, {len(args)}, {name}_fn); |
| 305 | + """) |
| 306 | + self.locals.append(f"{{ MP_ROM_QSTR(MP_QSTR_{name}), MP_ROM_PTR(&{name}_obj) }},") |
| 307 | + |
| 308 | + def handle_funptr(self, fun): |
| 309 | + pass # Ignore function pointers for now |
| 310 | + |
| 311 | + def emit(self): |
| 312 | + for row in self.decls: |
| 313 | + print(row) |
| 314 | + for row in self.body: |
| 315 | + print(row) |
| 316 | + print() |
| 317 | + print("static const mp_rom_map_elem_t module_globals_table[] = {") |
| 318 | + print(f" {{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_{self.modname}) }},") |
| 319 | + for row in self.locals: |
| 320 | + print(f" {row}") |
| 321 | + print("};") |
| 322 | + print("static MP_DEFINE_CONST_DICT(module_globals, module_globals_table);") |
| 323 | + print( |
| 324 | + textwrap.dedent(f""" |
| 325 | + const mp_obj_module_t {self.modname}_module = {{ |
| 326 | + .base = {{ &mp_type_module }}, |
| 327 | + .globals = (mp_obj_dict_t *)&module_globals, |
| 328 | + }}; |
| 329 | +
|
| 330 | + MP_REGISTER_MODULE(MP_QSTR_{self.modname}, {self.modname}_module);""") |
| 331 | + ) |
| 332 | + |
| 333 | + for row in self.info: |
| 334 | + print(row, file=sys.stderr) |
| 335 | + |
| 336 | + |
| 337 | +p = Processor(modname) |
| 338 | +p.process(defs) |
| 339 | +p.emit() |
0 commit comments