pocketpy/ffigen/ffigen/library.py
2026-01-08 15:26:03 +08:00

208 lines
7.2 KiB
Python

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