mirror of
https://github.com/pocketpy/pocketpy
synced 2025-10-19 19:10:17 +00:00
add scripts/c_bind
This commit is contained in:
parent
0c1abd3b5c
commit
7ffd50bcf1
7
scripts/c_bind/c_bind/__init__.py
Normal file
7
scripts/c_bind/c_bind/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from .function import gen_function
|
||||||
|
from .converters import get_converter, set_linalg_converter, set_enum_converters
|
||||||
|
from .writer import Writer
|
||||||
|
from .struct import gen_struct
|
||||||
|
from .enum import gen_enum
|
||||||
|
|
||||||
|
from .library import Library
|
169
scripts/c_bind/c_bind/converters.py
Normal file
169
scripts/c_bind/c_bind/converters.py
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
from .writer import Writer
|
||||||
|
from .types import C_INT_TYPES, C_FLOAT_TYPES, C_BOOL_TYPES, C_STRING_TYPES, LINALG_TYPES
|
||||||
|
|
||||||
|
class Converter:
|
||||||
|
def __init__(self, T: str):
|
||||||
|
self.T = T
|
||||||
|
def c2py(self, w: Writer, out: str, expr: str):
|
||||||
|
raise NotImplementedError
|
||||||
|
def py2c(self, w: Writer, out: str, expr: str):
|
||||||
|
raise NotImplementedError
|
||||||
|
@property
|
||||||
|
def py_T(self) -> str:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def is_const(self):
|
||||||
|
return self.T.startswith('const ') or '[' in self.T
|
||||||
|
|
||||||
|
class _SimpleConverter(Converter):
|
||||||
|
def c2py(self, w: Writer, out: str, expr: str):
|
||||||
|
w.write(f'py_new{self.py_T}({out}, {expr});')
|
||||||
|
def py2c(self, w: Writer, out: str, expr: str):
|
||||||
|
w.write(f'if(!py_check{self.py_T}({expr})) return false;')
|
||||||
|
w.write(f'{out} = py_to{self.py_T}({expr});')
|
||||||
|
|
||||||
|
class IntConverter(_SimpleConverter):
|
||||||
|
@property
|
||||||
|
def py_T(self) -> str:
|
||||||
|
return 'int'
|
||||||
|
|
||||||
|
class BoolConverter(_SimpleConverter):
|
||||||
|
@property
|
||||||
|
def py_T(self) -> str:
|
||||||
|
return 'bool'
|
||||||
|
|
||||||
|
class StringConverter(_SimpleConverter):
|
||||||
|
@property
|
||||||
|
def py_T(self) -> str:
|
||||||
|
return 'str'
|
||||||
|
|
||||||
|
class FloatConverter(Converter):
|
||||||
|
def c2py(self, w: Writer, out: str, expr: str):
|
||||||
|
w.write(f'py_newfloat({out}, {expr});')
|
||||||
|
def py2c(self, w: Writer, out: str, expr: str):
|
||||||
|
if self.T == 'float':
|
||||||
|
w.write(f'if(!py_castfloat32({expr}, &{out})) return false;')
|
||||||
|
else:
|
||||||
|
w.write(f'if(!py_castfloat({expr}, &{out})) return false;')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def py_T(self) -> str:
|
||||||
|
return 'float'
|
||||||
|
|
||||||
|
class PointerConverter(Converter):
|
||||||
|
def c2py(self, w: Writer, out: str, expr: str):
|
||||||
|
w.write(f'py_newint({out}, (py_i64){expr});')
|
||||||
|
def py2c(self, w: Writer, out: str, expr: str):
|
||||||
|
w.write(f'if(!py_checkint({expr})) return false;')
|
||||||
|
w.write(f'{out} = ({self.T})py_toint({expr});')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def py_T(self) -> str:
|
||||||
|
return 'intptr'
|
||||||
|
|
||||||
|
class StructConverter(Converter):
|
||||||
|
def __init__(self, T: str, type_index: str | None):
|
||||||
|
super().__init__(T)
|
||||||
|
if type_index is None:
|
||||||
|
type_index = f'tp_user_{T}'
|
||||||
|
self.type_index = type_index
|
||||||
|
def c2py(self, w: Writer, out: str, expr: str):
|
||||||
|
w.write('do {')
|
||||||
|
w.indent()
|
||||||
|
w.write(f'{self.T}* ud = py_newobject({out}, {self.type_index}, 0, sizeof({self.T}));')
|
||||||
|
w.write(f'*ud = {expr};')
|
||||||
|
w.dedent()
|
||||||
|
w.write('} while(0);')
|
||||||
|
def py2c(self, w: Writer, out: str, expr: str):
|
||||||
|
w.write('do {')
|
||||||
|
w.indent()
|
||||||
|
w.write(f'if(!py_checktype({expr}, {self.type_index})) return false;')
|
||||||
|
w.write(f'{out} = *({self.T}*)py_touserdata({expr});')
|
||||||
|
w.dedent()
|
||||||
|
w.write('} while(0);')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def py_T(self) -> str:
|
||||||
|
return self.T
|
||||||
|
|
||||||
|
class EnumConverter(Converter):
|
||||||
|
def __init__(self, T: str):
|
||||||
|
super().__init__(T)
|
||||||
|
def c2py(self, w: Writer, out: str, expr: str):
|
||||||
|
w.write(f'py_newint({out}, (py_i64){expr});')
|
||||||
|
def py2c(self, w: Writer, out: str, expr: str):
|
||||||
|
w.write(f'if(!py_checkint({expr})) return false;')
|
||||||
|
w.write(f'{out} = ({self.T})py_toint({expr});')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def py_T(self) -> str:
|
||||||
|
return 'int'
|
||||||
|
|
||||||
|
class VoidConverter(Converter):
|
||||||
|
def c2py(self, w: Writer, out: str, expr: str):
|
||||||
|
w.write(f'py_newnone({out});')
|
||||||
|
def py2c(self, w: Writer, out: str, expr: str):
|
||||||
|
# raise NotImplementedError
|
||||||
|
w.write(f'? // VoidConverter.py2c is not implemented')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def py_T(self) -> str:
|
||||||
|
return 'None'
|
||||||
|
|
||||||
|
class BuiltinVectorConverter(Converter):
|
||||||
|
def __init__(self, T: str, py_builtin_T: str):
|
||||||
|
super().__init__(T)
|
||||||
|
self.py_builtin_T = py_builtin_T
|
||||||
|
|
||||||
|
def c2py(self, w: Writer, out: str, expr: str):
|
||||||
|
w.write(f'py_new{self.py_builtin_T}({out}, *(c11_{self.py_T}*)(&{expr}));')
|
||||||
|
|
||||||
|
def py2c(self, w: Writer, out: str, expr: str):
|
||||||
|
w.write('do {')
|
||||||
|
w.indent()
|
||||||
|
w.write(f'if(!py_checktype({expr}, tp_{self.py_T})) return false;')
|
||||||
|
w.write(f'c11_{self.py_T} tmp = py_to{self.py_builtin_T}({expr});')
|
||||||
|
# w.write(f'memcpy(&{out}, &tmp, sizeof(c11_{self.py_T}));')
|
||||||
|
w.write(f'{out} = *({self.T}*)(&tmp);')
|
||||||
|
w.dedent()
|
||||||
|
w.write('} while(0);')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def py_T(self) -> str:
|
||||||
|
return self.py_builtin_T
|
||||||
|
|
||||||
|
|
||||||
|
_CONVERTERS: dict[str, Converter] = {}
|
||||||
|
|
||||||
|
for t in C_INT_TYPES:
|
||||||
|
_CONVERTERS[t] = IntConverter(t)
|
||||||
|
for t in C_FLOAT_TYPES:
|
||||||
|
_CONVERTERS[t] = FloatConverter(t)
|
||||||
|
for t in C_BOOL_TYPES:
|
||||||
|
_CONVERTERS[t] = BoolConverter(t)
|
||||||
|
for t in C_STRING_TYPES:
|
||||||
|
_CONVERTERS[t] = StringConverter(t)
|
||||||
|
for t in LINALG_TYPES:
|
||||||
|
_CONVERTERS[t] = BuiltinVectorConverter(f'c11_{t}', t)
|
||||||
|
|
||||||
|
_CONVERTERS['void'] = VoidConverter('void')
|
||||||
|
_CONVERTERS['c11_array2d'] = StructConverter('c11_array2d', 'tp_array2d')
|
||||||
|
|
||||||
|
def set_linalg_converter(T: str, py_T: str):
|
||||||
|
assert py_T in LINALG_TYPES
|
||||||
|
_CONVERTERS[T] = BuiltinVectorConverter(T, py_T)
|
||||||
|
|
||||||
|
def set_enum_converters(enums: list[str]):
|
||||||
|
for T in enums:
|
||||||
|
_CONVERTERS[T] = EnumConverter(T)
|
||||||
|
|
||||||
|
def get_converter(T: str) -> Converter:
|
||||||
|
if T in _CONVERTERS:
|
||||||
|
return _CONVERTERS[T]
|
||||||
|
if T.endswith('*'):
|
||||||
|
return PointerConverter(T)
|
||||||
|
if '[' in T:
|
||||||
|
return PointerConverter(T)
|
||||||
|
cvt = _CONVERTERS.get(T)
|
||||||
|
if cvt is None:
|
||||||
|
return StructConverter(T, None)
|
12
scripts/c_bind/c_bind/enum.py
Normal file
12
scripts/c_bind/c_bind/enum.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from .writer import Writer
|
||||||
|
from .schema import Enum
|
||||||
|
|
||||||
|
def gen_enum(w: Writer, pyi_w: Writer, enum: Enum):
|
||||||
|
for value in enum.values:
|
||||||
|
w.write(f'ADD_ENUM({value.name});')
|
||||||
|
|
||||||
|
if value.value is not None:
|
||||||
|
pyi_w.write(f'{value.name}: int = {value.value}')
|
||||||
|
else:
|
||||||
|
pyi_w.write(f'{value.name}: int')
|
||||||
|
|
47
scripts/c_bind/c_bind/function.py
Normal file
47
scripts/c_bind/c_bind/function.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
from .writer import Writer
|
||||||
|
from .converters import get_converter
|
||||||
|
from .schema import Function
|
||||||
|
|
||||||
|
|
||||||
|
def gen_function(w: Writer, pyi_w: Writer, function: Function):
|
||||||
|
name = function.name
|
||||||
|
w.write(f'static bool cfunc__{name}(int argc, py_Ref argv) {{')
|
||||||
|
w.indent()
|
||||||
|
|
||||||
|
w.write(f'PY_CHECK_ARGC({len(function.params)});')
|
||||||
|
|
||||||
|
args_cvt = [get_converter(arg.type) for arg in function.params]
|
||||||
|
ret_cvt = get_converter(function.ret_type)
|
||||||
|
|
||||||
|
for i in range(len(args_cvt)):
|
||||||
|
w.write(f'{args_cvt[i].T} _{i};')
|
||||||
|
args_cvt[i].py2c(w, f'_{i}', f'py_arg({i})')
|
||||||
|
|
||||||
|
call_args = ', '.join([f'_{i}' for i in range(len(args_cvt))])
|
||||||
|
|
||||||
|
# gen retval
|
||||||
|
if function.ret_type == 'void':
|
||||||
|
w.write(f'{name}({call_args});')
|
||||||
|
w.write(f'py_newnone(py_retval());')
|
||||||
|
else:
|
||||||
|
w.write(f'{ret_cvt.T} res = {name}({call_args});')
|
||||||
|
ret_cvt.c2py(w, 'py_retval()', 'res')
|
||||||
|
|
||||||
|
w.write('return true;')
|
||||||
|
|
||||||
|
w.dedent()
|
||||||
|
w.write('}')
|
||||||
|
|
||||||
|
# pyi
|
||||||
|
py_args = []
|
||||||
|
# arg_names = [f'_{i}' for i in range(len(args_cvt))]
|
||||||
|
arg_names = [arg.name for arg in function.params]
|
||||||
|
for i in range(len(args_cvt)):
|
||||||
|
py_args.append(f'{arg_names[i]}: {args_cvt[i].py_T}')
|
||||||
|
|
||||||
|
pyi_w.write(f'def {name}({", ".join(py_args)}) -> {ret_cvt.py_T}:')
|
||||||
|
if function.desc:
|
||||||
|
pyi_w.write(f' """Wraps `{function.signature()}`\n\n {function.desc}"""')
|
||||||
|
else:
|
||||||
|
pyi_w.write(f' """Wraps `{function.signature()}`"""')
|
||||||
|
pyi_w.write('')
|
195
scripts/c_bind/c_bind/library.py
Normal file
195
scripts/c_bind/c_bind/library.py
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
from .schema import *
|
||||||
|
from .writer import Writer
|
||||||
|
from .enum import gen_enum
|
||||||
|
from .struct import gen_struct
|
||||||
|
from .function import gen_function
|
||||||
|
|
||||||
|
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: list[Alias]
|
||||||
|
self.enums = [] # type: list[Enum]
|
||||||
|
self.functions = [] # type: list[Function]
|
||||||
|
self.callbacks = set() # type: set[str]
|
||||||
|
|
||||||
|
def set_includes(self, includes: list[str]):
|
||||||
|
self.user_includes.extend(includes)
|
||||||
|
|
||||||
|
def build(self, *, glue_dir='.', stub_dir='.', includes: list[str] = None):
|
||||||
|
self.remove_unsupported()
|
||||||
|
|
||||||
|
w, pyi_w = Writer(), Writer()
|
||||||
|
|
||||||
|
pyi_w.write('from linalg import vec2, vec3, vec2i, vec3i, mat3x3')
|
||||||
|
pyi_w.write('from typing import overload')
|
||||||
|
pyi_w.write('intptr = int')
|
||||||
|
pyi_w.write('')
|
||||||
|
|
||||||
|
w.write('#include "pocketpy.h"')
|
||||||
|
w.write(f'#include "string.h"')
|
||||||
|
|
||||||
|
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('')
|
||||||
|
w.write('static bool struct__address__(int argc, py_Ref argv) {')
|
||||||
|
w.indent()
|
||||||
|
w.write('PY_CHECK_ARGC(1);')
|
||||||
|
w.write('void* ud = py_touserdata(argv);')
|
||||||
|
w.write('py_newint(py_retval(), (py_i64)ud);')
|
||||||
|
w.write('return true;')
|
||||||
|
w.dedent()
|
||||||
|
w.write('}')
|
||||||
|
w.write('')
|
||||||
|
|
||||||
|
for alias in self.aliases:
|
||||||
|
w.write(f'#define tp_user_{alias.name} tp_user_{alias.type}')
|
||||||
|
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 alias in self.aliases:
|
||||||
|
w.write(f'py_setdict(mod, py_name("{alias.name}"), py_getdict(mod, py_name("{alias.type}")));')
|
||||||
|
pyi_w.write(f'{alias.name} = {alias.type}')
|
||||||
|
|
||||||
|
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']:
|
||||||
|
self.structs.append(Struct(
|
||||||
|
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.append(Alias(
|
||||||
|
type=alias['type'],
|
||||||
|
name=alias['name'],
|
||||||
|
desc=alias['description']
|
||||||
|
))
|
||||||
|
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 c_bind.meta import schema
|
||||||
|
self = Library(name)
|
||||||
|
for type in header.types:
|
||||||
|
if isinstance(type, schema.NamedFields):
|
||||||
|
if type.is_opaque():
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
self.structs.append(Struct(
|
||||||
|
name=type.name,
|
||||||
|
fields=[StructField(
|
||||||
|
type=field_type,
|
||||||
|
name=field_name
|
||||||
|
) for field_name, field_type in type.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.append(Alias(
|
||||||
|
name=k,
|
||||||
|
type=v
|
||||||
|
))
|
||||||
|
|
||||||
|
for function in header.functions:
|
||||||
|
self.functions.append(Function(
|
||||||
|
name=function.name,
|
||||||
|
params=[FunctionParam(
|
||||||
|
type=param,
|
||||||
|
name=f'_{i}'
|
||||||
|
) for i, param in enumerate(function.args)],
|
||||||
|
ret_type=function.ret
|
||||||
|
))
|
||||||
|
return self
|
1
scripts/c_bind/c_bind/meta/__init__.py
Normal file
1
scripts/c_bind/c_bind/meta/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .parser import Header
|
159
scripts/c_bind/c_bind/meta/parser.py
Normal file
159
scripts/c_bind/c_bind/meta/parser.py
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
from .schema import *
|
||||||
|
|
||||||
|
class UnsupportedNode(Exception):
|
||||||
|
def __init__(self, node: c_ast.Node):
|
||||||
|
self.node = node
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"'{type(self.node)}' is not supported\n{self.node!r}"
|
||||||
|
|
||||||
|
|
||||||
|
class Header:
|
||||||
|
def __init__(self):
|
||||||
|
self.types = [] # type: list
|
||||||
|
self.type_aliases = {} # type: dict[str, str]
|
||||||
|
self.functions = [] # type: list[Function]
|
||||||
|
|
||||||
|
def remove_types(self, names: set):
|
||||||
|
self.types = [t for t in self.types if getattr(t, 'name', None) not in names]
|
||||||
|
|
||||||
|
def remove_functions(self, names: set):
|
||||||
|
self.functions = [f for f in self.functions if f.name not in names]
|
||||||
|
|
||||||
|
def build_enum(self, node: c_ast.Enum):
|
||||||
|
enum = Enum(node.name)
|
||||||
|
for item in node.values.enumerators:
|
||||||
|
enum.values.append(item.name)
|
||||||
|
self.types.append(enum)
|
||||||
|
|
||||||
|
def unalias(self, name: str):
|
||||||
|
while name in self.type_aliases:
|
||||||
|
name = self.type_aliases[name]
|
||||||
|
return name
|
||||||
|
|
||||||
|
def build_struct(self, node):
|
||||||
|
if isinstance(node, c_ast.Struct):
|
||||||
|
cls = Struct
|
||||||
|
elif isinstance(node, c_ast.Union):
|
||||||
|
cls = Union
|
||||||
|
else:
|
||||||
|
raise UnsupportedNode(node)
|
||||||
|
if node.decls is not None:
|
||||||
|
fields = {}
|
||||||
|
for decl in node.decls:
|
||||||
|
try:
|
||||||
|
type, name = self.build_param(decl)
|
||||||
|
assert name
|
||||||
|
fields[name] = type
|
||||||
|
except UnsupportedNode:
|
||||||
|
pass
|
||||||
|
self.types.append(cls(node.name, fields))
|
||||||
|
else:
|
||||||
|
self.types.append(cls(node.name, None))
|
||||||
|
|
||||||
|
def build_type(self, name, node):
|
||||||
|
if isinstance(node, c_ast.Enum):
|
||||||
|
self.build_enum(node)
|
||||||
|
elif isinstance(node, (c_ast.Struct, c_ast.Union)):
|
||||||
|
self.build_struct(node)
|
||||||
|
elif isinstance(node, c_ast.IdentifierType):
|
||||||
|
assert name
|
||||||
|
self.type_aliases[name] = node.names[0]
|
||||||
|
else:
|
||||||
|
raise UnsupportedNode(node)
|
||||||
|
|
||||||
|
def get_type_name(self, node):
|
||||||
|
# convert array to const T*
|
||||||
|
if isinstance(node, c_ast.ArrayDecl):
|
||||||
|
dims = []
|
||||||
|
while isinstance(node, c_ast.ArrayDecl):
|
||||||
|
dims.append(node.dim.value)
|
||||||
|
node = node.type
|
||||||
|
base_name = self.get_type_name(node)
|
||||||
|
# return base_name + ''.join(f'[{dim}]' for dim in dims)
|
||||||
|
return f'const {base_name}*'
|
||||||
|
|
||||||
|
node, level = self.eat_pointers(node)
|
||||||
|
|
||||||
|
if isinstance(node, c_ast.FuncDecl):
|
||||||
|
assert level == 1
|
||||||
|
return 'void (*)()'
|
||||||
|
|
||||||
|
if not isinstance(node, (c_ast.TypeDecl, c_ast.Typename)):
|
||||||
|
raise UnsupportedNode(node)
|
||||||
|
|
||||||
|
is_const = node.quals and 'const' in node.quals
|
||||||
|
node = node.type
|
||||||
|
base_name: str
|
||||||
|
if isinstance(node, c_ast.IdentifierType):
|
||||||
|
base_name = node.names[0]
|
||||||
|
elif isinstance(node, c_ast.Enum):
|
||||||
|
base_name = 'enum ' + node.name
|
||||||
|
elif isinstance(node, c_ast.Struct):
|
||||||
|
base_name = 'struct ' + node.name
|
||||||
|
elif isinstance(node, c_ast.Union):
|
||||||
|
base_name = 'union ' + node.name
|
||||||
|
else:
|
||||||
|
base_name = self.get_type_name(node)
|
||||||
|
|
||||||
|
if is_const:
|
||||||
|
base_name = 'const ' + base_name
|
||||||
|
return base_name + '*' * level
|
||||||
|
|
||||||
|
def build_param(self, node):
|
||||||
|
name = None
|
||||||
|
if isinstance(node, c_ast.Decl):
|
||||||
|
name = node.name
|
||||||
|
node = node.type
|
||||||
|
return self.get_type_name(node), name
|
||||||
|
|
||||||
|
def eat_pointers(self, node):
|
||||||
|
level = 0
|
||||||
|
while isinstance(node, c_ast.PtrDecl):
|
||||||
|
node = node.type
|
||||||
|
level += 1
|
||||||
|
return node, level
|
||||||
|
|
||||||
|
def build_function(self, node: c_ast.FuncDecl):
|
||||||
|
args = node.args
|
||||||
|
node = node.type
|
||||||
|
node, level = self.eat_pointers(node)
|
||||||
|
assert isinstance(node, c_ast.TypeDecl), type(node)
|
||||||
|
name = node.declname
|
||||||
|
ret = node.type.names[0]
|
||||||
|
func = Function(name, ret + '*' * level)
|
||||||
|
if args is not None:
|
||||||
|
for param in args.params:
|
||||||
|
if isinstance(param, c_ast.EllipsisParam):
|
||||||
|
func.args.append('...')
|
||||||
|
else:
|
||||||
|
T, name = self.build_param(param)
|
||||||
|
if T != 'void':
|
||||||
|
func.args.append(T)
|
||||||
|
self.functions.append(func)
|
||||||
|
|
||||||
|
def build(self, ast: c_ast.FileAST):
|
||||||
|
for _, node in ast.children():
|
||||||
|
if isinstance(node, c_ast.Typedef):
|
||||||
|
name, node = node.name, node.type
|
||||||
|
if isinstance(node, c_ast.TypeDecl):
|
||||||
|
self.build_type(name, node.type)
|
||||||
|
elif isinstance(node, c_ast.PtrDecl):
|
||||||
|
type_name = self.get_type_name(node)
|
||||||
|
self.type_aliases[name] = type_name
|
||||||
|
else:
|
||||||
|
# raise UnsupportedNode(node.type)
|
||||||
|
# print(f"Unsupported typedef: {type(node)}")
|
||||||
|
continue
|
||||||
|
elif isinstance(node, c_ast.Decl):
|
||||||
|
if isinstance(node.type, c_ast.FuncDecl):
|
||||||
|
self.build_function(node.type)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.build_type(None, node.type)
|
||||||
|
except UnsupportedNode:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# raise UnsupportedNode(node.type)
|
||||||
|
# print(f"Unsupported typedef: {type(node)}")
|
||||||
|
continue
|
39
scripts/c_bind/c_bind/meta/schema.py
Normal file
39
scripts/c_bind/c_bind/meta/schema.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
from pycparser import c_ast
|
||||||
|
|
||||||
|
class Pointer:
|
||||||
|
def __init__(self, base: str, level: int):
|
||||||
|
super().__init__(f'{base}' + '*' * level)
|
||||||
|
self.base = base
|
||||||
|
self.level = level
|
||||||
|
|
||||||
|
class NamedFields:
|
||||||
|
def __init__(self, name: str, fields: dict[str, str] | None):
|
||||||
|
self.name = name
|
||||||
|
self.fields = fields
|
||||||
|
|
||||||
|
def is_opaque(self):
|
||||||
|
return self.fields is None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
cls = type(self).__name__
|
||||||
|
return f"{cls}('{self.name}', {self.fields!r})"
|
||||||
|
|
||||||
|
class Struct(NamedFields): pass
|
||||||
|
class Union(NamedFields): pass
|
||||||
|
|
||||||
|
class Enum:
|
||||||
|
def __init__(self, name: str):
|
||||||
|
self.name = name
|
||||||
|
self.values = [] # type: list[str]
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Enum('{self.name}', values={self.values!r})"
|
||||||
|
|
||||||
|
class Function:
|
||||||
|
def __init__(self, name: str, ret: str):
|
||||||
|
self.name = name
|
||||||
|
self.args = [] # type: list[str]
|
||||||
|
self.ret = ret
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Function('{self.name}', args={self.args!r}, ret={self.ret!r})"
|
47
scripts/c_bind/c_bind/schema.py
Normal file
47
scripts/c_bind/c_bind/schema.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class StructField:
|
||||||
|
type: str
|
||||||
|
name: str
|
||||||
|
desc: str = None
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EnumValue:
|
||||||
|
name: str
|
||||||
|
value: int | None
|
||||||
|
desc: str = None
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Struct:
|
||||||
|
name: str
|
||||||
|
desc: str = None
|
||||||
|
fields: list[StructField] = None
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Alias:
|
||||||
|
type: str
|
||||||
|
name: str
|
||||||
|
desc: str = None
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Enum:
|
||||||
|
name: str
|
||||||
|
values: list[EnumValue]
|
||||||
|
desc: str = None
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FunctionParam:
|
||||||
|
type: str
|
||||||
|
name: str
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Function:
|
||||||
|
name: str
|
||||||
|
params: list[FunctionParam]
|
||||||
|
ret_type: str
|
||||||
|
desc: str = None
|
||||||
|
|
||||||
|
def signature(self) -> str:
|
||||||
|
return f'{self.ret_type} {self.name}({", ".join([f"{param.type} {param.name}" for param in self.params])})'
|
||||||
|
|
125
scripts/c_bind/c_bind/struct.py
Normal file
125
scripts/c_bind/c_bind/struct.py
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
from .writer import Writer
|
||||||
|
from .converters import get_converter, Converter
|
||||||
|
from .schema import Struct, StructField
|
||||||
|
|
||||||
|
def gen_getter(w: Writer, name: str, cvt: Converter, field: StructField):
|
||||||
|
w.write(f'static bool {name}__get_{field.name}(int argc, py_Ref argv) {{')
|
||||||
|
w.indent()
|
||||||
|
w.write(f'PY_CHECK_ARGC(1);')
|
||||||
|
w.write(f'{name}* self = py_touserdata(argv);')
|
||||||
|
cvt.c2py(w, 'py_retval()', f'self->{field.name}')
|
||||||
|
w.write('return true;')
|
||||||
|
w.dedent()
|
||||||
|
w.write('}')
|
||||||
|
|
||||||
|
def gen_setter(w: Writer, name: str, cvt: Converter, field: StructField):
|
||||||
|
w.write(f'static bool {name}__set_{field.name}(int argc, py_Ref argv) {{')
|
||||||
|
w.indent()
|
||||||
|
w.write(f'PY_CHECK_ARGC(2);')
|
||||||
|
w.write(f'{name}* self = py_touserdata(argv);')
|
||||||
|
cvt.py2c(w, f'self->{field.name}', 'py_arg(1)')
|
||||||
|
w.write('py_newnone(py_retval());')
|
||||||
|
w.write('return true;')
|
||||||
|
w.dedent()
|
||||||
|
w.write('}')
|
||||||
|
|
||||||
|
def gen_struct(w: Writer, pyi_w: Writer, struct: Struct):
|
||||||
|
name = struct.name
|
||||||
|
converters = [get_converter(field.type) for field in struct.fields]
|
||||||
|
# default __new__
|
||||||
|
w.write(f'static bool {name}__new__(int argc, py_Ref argv) {{')
|
||||||
|
w.indent()
|
||||||
|
w.write(f'py_Type cls = py_totype(argv);')
|
||||||
|
w.write(f'py_newobject(py_retval(), cls, 0, sizeof({name}));')
|
||||||
|
w.write('return true;')
|
||||||
|
w.dedent()
|
||||||
|
w.write('}')
|
||||||
|
|
||||||
|
# default __init__
|
||||||
|
w.write(f'static bool {name}__init__(int argc, py_Ref argv) {{')
|
||||||
|
w.indent()
|
||||||
|
w.write(f'{name}* self = py_touserdata(argv);')
|
||||||
|
w.write(f'if(argc == 1) {{')
|
||||||
|
w.indent()
|
||||||
|
w.write(f'memset(self, 0, sizeof({name}));')
|
||||||
|
w.dedent()
|
||||||
|
w.write(f'}} else if(argc == 1 + {len(struct.fields)}) {{')
|
||||||
|
w.indent()
|
||||||
|
for i, field in enumerate(struct.fields):
|
||||||
|
cvt = converters[i]
|
||||||
|
if not cvt.is_const():
|
||||||
|
cvt.py2c(w, f'self->{field.name}', f'py_arg({i+1})')
|
||||||
|
else:
|
||||||
|
w.write(f'// _{i} {field.name} is read-only')
|
||||||
|
w.dedent()
|
||||||
|
w.write('} else {')
|
||||||
|
w.indent()
|
||||||
|
w.write(f'return TypeError("expected 1 or {len(struct.fields)+1} arguments");')
|
||||||
|
w.dedent()
|
||||||
|
w.write('}')
|
||||||
|
w.write('py_newnone(py_retval());')
|
||||||
|
w.write('return true;')
|
||||||
|
w.dedent()
|
||||||
|
w.write('}')
|
||||||
|
|
||||||
|
# default __copy__
|
||||||
|
w.write(f'static bool {name}__copy__(int argc, py_Ref argv) {{')
|
||||||
|
w.indent()
|
||||||
|
w.write(f'PY_CHECK_ARGC(1);')
|
||||||
|
w.write(f'{name}* self = py_touserdata(argv);')
|
||||||
|
w.write(f'{name}* res = py_newobject(py_retval(), py_typeof(argv), 0, sizeof({name}));')
|
||||||
|
w.write(f'*res = *self;')
|
||||||
|
w.write('return true;')
|
||||||
|
w.dedent()
|
||||||
|
w.write('}')
|
||||||
|
|
||||||
|
for field in struct.fields:
|
||||||
|
cvt = get_converter(field.type)
|
||||||
|
gen_getter(w, name, cvt, field)
|
||||||
|
if not cvt.is_const():
|
||||||
|
gen_setter(w, name, cvt, field)
|
||||||
|
|
||||||
|
w.write(f'static py_Type register__{name}(py_GlobalRef mod) {{')
|
||||||
|
w.indent()
|
||||||
|
w.write(f'py_Type type = py_newtype("{name}", tp_object, mod, NULL);')
|
||||||
|
|
||||||
|
w.write(f'py_bindmagic(type, __new__, {name}__new__);')
|
||||||
|
w.write(f'py_bindmagic(type, __init__, {name}__init__);')
|
||||||
|
w.write(f'py_bindmethod(type, "__address__", struct__address__);')
|
||||||
|
w.write(f'py_bindmethod(type, "copy", {name}__copy__);')
|
||||||
|
|
||||||
|
for field in struct.fields:
|
||||||
|
cvt = get_converter(field.type)
|
||||||
|
if cvt.is_const():
|
||||||
|
setter = 'NULL'
|
||||||
|
else:
|
||||||
|
setter = f'{name}__set_{field.name}'
|
||||||
|
w.write(f'py_bindproperty(type, "{field.name}", {name}__get_{field.name}, {setter});')
|
||||||
|
|
||||||
|
w.write(f'return type;')
|
||||||
|
w.dedent()
|
||||||
|
w.write('}')
|
||||||
|
|
||||||
|
# pyi
|
||||||
|
pyi_w.write(f'class {name}:')
|
||||||
|
pyi_w.indent()
|
||||||
|
|
||||||
|
py_args = []
|
||||||
|
for field in struct.fields:
|
||||||
|
cvt = get_converter(field.type)
|
||||||
|
desc = (field.desc or '') + f' ({field.type})'
|
||||||
|
py_args.append(f"{field.name}: {cvt.py_T}")
|
||||||
|
pyi_w.write(f"{py_args[-1]} # {desc}")
|
||||||
|
pyi_w.write('')
|
||||||
|
|
||||||
|
pyi_w.write(f'@overload')
|
||||||
|
pyi_w.write(f'def __init__(self): ...')
|
||||||
|
pyi_w.write(f'@overload')
|
||||||
|
pyi_w.write(f'def __init__(self, {", ".join(py_args)}): ...')
|
||||||
|
pyi_w.write(f'def __address__(self) -> int: ...')
|
||||||
|
pyi_w.write(f"def copy(self) -> '{name}': ...")
|
||||||
|
pyi_w.write('')
|
||||||
|
pyi_w.dedent()
|
||||||
|
|
||||||
|
w.write(f'static py_Type tp_user_{name};')
|
||||||
|
return f'tp_user_{name} = register__{name}(mod);'
|
30
scripts/c_bind/c_bind/types.py
Normal file
30
scripts/c_bind/c_bind/types.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
C_INT_TYPES = [
|
||||||
|
'char', 'short', 'int', 'long', 'long long',
|
||||||
|
'signed char', 'signed short', 'signed int', 'signed long', 'signed long long',
|
||||||
|
'unsigned char', 'unsigned short', 'unsigned int', 'unsigned long', 'unsigned long long',
|
||||||
|
'int8_t', 'int16_t', 'int32_t', 'int64_t',
|
||||||
|
'uint8_t', 'uint16_t', 'uint32_t', 'uint64_t',
|
||||||
|
'intptr_t', 'uintptr_t',
|
||||||
|
'ptrdiff_t', 'size_t',
|
||||||
|
'unsigned', 'signed',
|
||||||
|
'py_i64',
|
||||||
|
]
|
||||||
|
|
||||||
|
C_FLOAT_TYPES = [
|
||||||
|
'float', 'double',
|
||||||
|
'py_f64',
|
||||||
|
]
|
||||||
|
|
||||||
|
C_BOOL_TYPES = [
|
||||||
|
'bool', '_Bool',
|
||||||
|
]
|
||||||
|
|
||||||
|
C_STRING_TYPES = [
|
||||||
|
'const char*',
|
||||||
|
'const char *',
|
||||||
|
]
|
||||||
|
|
||||||
|
LINALG_TYPES = [
|
||||||
|
'vec2', 'vec3', 'vec2i', 'vec3i',
|
||||||
|
'mat3x3'
|
||||||
|
]
|
16
scripts/c_bind/c_bind/writer.py
Normal file
16
scripts/c_bind/c_bind/writer.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
class Writer:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.buffer = []
|
||||||
|
self.indent_level = 0
|
||||||
|
|
||||||
|
def indent(self):
|
||||||
|
self.indent_level += 1
|
||||||
|
|
||||||
|
def dedent(self):
|
||||||
|
self.indent_level -= 1
|
||||||
|
|
||||||
|
def write(self, line: str):
|
||||||
|
self.buffer.append(' ' * self.indent_level + line)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return '\n'.join(self.buffer)
|
30
scripts/c_bind/gen_box2d.py
Normal file
30
scripts/c_bind/gen_box2d.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import pcpp
|
||||||
|
import pycparser
|
||||||
|
from c_bind import Library, set_linalg_converter, set_enum_converters
|
||||||
|
from c_bind.meta import Header
|
||||||
|
import os
|
||||||
|
|
||||||
|
path = '../3rd/box2d/include/box2d/box2d.h'
|
||||||
|
code = pcpp.CmdPreprocessor([None, path, '-o', 'tmp.h', '--line-directive', '-I', 'libc_include', '-I', '../3rd/box2d/include'])
|
||||||
|
|
||||||
|
ast = pycparser.parse_file('tmp.h')
|
||||||
|
os.remove('tmp.h')
|
||||||
|
|
||||||
|
header = Header()
|
||||||
|
header.build(ast)
|
||||||
|
|
||||||
|
header.remove_types({'b2Timer', 'b2DebugDraw'})
|
||||||
|
header.remove_functions({'b2CreateTimer', 'b2Hash', 'b2DefaultDebugDraw'})
|
||||||
|
|
||||||
|
lib = Library.from_header('box2d', header)
|
||||||
|
|
||||||
|
set_linalg_converter('b2Vec2', 'vec2')
|
||||||
|
set_linalg_converter('b2Vec3', 'vec3')
|
||||||
|
|
||||||
|
set_enum_converters([enum.name for enum in lib.enums])
|
||||||
|
|
||||||
|
lib.build(
|
||||||
|
includes=['box2d/box2d.h'],
|
||||||
|
glue_dir='../src',
|
||||||
|
stub_dir='../include/typings'
|
||||||
|
)
|
15
scripts/c_bind/gen_raylib.py
Normal file
15
scripts/c_bind/gen_raylib.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import json
|
||||||
|
from c_bind import Library, set_linalg_converter
|
||||||
|
|
||||||
|
with open('../3rd/raylib/parser/output/raylib_api.json') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
lib = Library.from_raylib(data)
|
||||||
|
set_linalg_converter('Vector2', 'vec2')
|
||||||
|
set_linalg_converter('Vector3', 'vec3')
|
||||||
|
|
||||||
|
lib.build(
|
||||||
|
includes=['raylib.h'],
|
||||||
|
glue_dir='../src',
|
||||||
|
stub_dir='../include/typings'
|
||||||
|
)
|
0
scripts/c_bind/libc_include/float.h
Normal file
0
scripts/c_bind/libc_include/float.h
Normal file
0
scripts/c_bind/libc_include/math.h
Normal file
0
scripts/c_bind/libc_include/math.h
Normal file
1
scripts/c_bind/libc_include/stdarg.h
Normal file
1
scripts/c_bind/libc_include/stdarg.h
Normal file
@ -0,0 +1 @@
|
|||||||
|
#define va_list int
|
1
scripts/c_bind/libc_include/stdbool.h
Normal file
1
scripts/c_bind/libc_include/stdbool.h
Normal file
@ -0,0 +1 @@
|
|||||||
|
#define bool _Bool
|
1
scripts/c_bind/libc_include/stddef.h
Normal file
1
scripts/c_bind/libc_include/stddef.h
Normal file
@ -0,0 +1 @@
|
|||||||
|
#define NULL ((void*)0)
|
11
scripts/c_bind/libc_include/stdint.h
Normal file
11
scripts/c_bind/libc_include/stdint.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#define size_t int
|
||||||
|
|
||||||
|
#define int8_t int
|
||||||
|
#define int16_t int
|
||||||
|
#define int32_t int
|
||||||
|
#define int64_t int
|
||||||
|
|
||||||
|
#define uint8_t unsigned
|
||||||
|
#define uint16_t unsigned
|
||||||
|
#define uint32_t unsigned
|
||||||
|
#define uint64_t unsigned
|
Loading…
x
Reference in New Issue
Block a user