Files
TransPyC/lib/core/stub_generator.py

734 lines
28 KiB
Python

#!/usr/bin/env python3
"""
C/H 文件到 Python 存根文件生成器
将 C 头文件 (.h) 或 C 源文件 (.c) 转换为 Python 存根文件 (.pyi)
生成的 Python 代码使用 TransPyC 语法,包含类型注解
"""
import os
import re
from typing import List, Dict, Optional
class CTypeMapper:
@classmethod
def _ResolveCNameToPyType(cls, c_type: str) -> str:
from lib.includes.t import CTypeRegistry
ctype_cls = CTypeRegistry.CNameToClass(c_type)
if ctype_cls is not None:
name = ctype_cls.__name__
return f't.{name}'
resolved = CTypeRegistry.ResolveName(c_type)
if resolved is not None:
ctype_cls, _ = resolved
name = ctype_cls.__name__
return f't.{name}'
_SPECIAL = {
'signed': 't.CInt', 'unsigned': 't.CUnsigned',
'signed char': 't.CChar', 'signed short': 't.CShort',
'signed int': 't.CInt', 'signed long': 't.CLong',
'_Bool': 't.CBool', 'ssize_t': 't.CLong',
}
if c_type in _SPECIAL:
return _SPECIAL[c_type]
return c_type
@classmethod
def MapType(cls, c_type: str, IsPtr: bool = False, IsArray: bool = False) -> str:
c_type = c_type.strip()
IsConst = 'const ' in c_type
c_type = c_type.replace('const ', '').replace('const', '').strip()
IsVolatile = 'volatile ' in c_type
c_type = c_type.replace('volatile ', '').replace('volatile', '').strip()
IsStatic = 'static ' in c_type
c_type = c_type.replace('static ', '').replace('static', '').strip()
IsExtern = 'extern ' in c_type
c_type = c_type.replace('extern ', '').replace('extern', '').strip()
IsRegister = 'register ' in c_type
c_type = c_type.replace('register ', '').replace('register', '').strip()
IsInline = 'inline ' in c_type
c_type = c_type.replace('inline ', '').replace('inline', '').strip()
PtrSuffix = ''
if IsPtr or c_type.endswith('*'):
PtrSuffix = ' | t.CPtr'
c_type = c_type.rstrip('*').strip()
if c_type.startswith('struct '):
c_type = c_type[7:].strip()
elif c_type.startswith('union '):
c_type = c_type[6:].strip()
elif c_type.startswith('enum '):
c_type = c_type[5:].strip()
PyType = cls._ResolveCNameToPyType(c_type)
result = PyType + PtrSuffix
if IsArray:
result += ' | t.CArrayPtr'
return result
def _find_matching_brace(text: str, start: int) -> int:
depth = 0
i = start
while i < len(text):
if text[i] == '{':
depth += 1
elif text[i] == '}':
depth -= 1
if depth == 0:
return i
elif text[i] == '"' or text[i] == "'":
quote = text[i]
i += 1
while i < len(text) and text[i] != quote:
if text[i] == '\\':
i += 1
i += 1
elif text[i] == '/' and i + 1 < len(text) and text[i + 1] == '/':
while i < len(text) and text[i] != '\n':
i += 1
continue
elif text[i] == '/' and i + 1 < len(text) and text[i + 1] == '*':
i += 2
while i + 1 < len(text) and not (text[i] == '*' and text[i + 1] == '/'):
i += 1
i += 1
i += 1
return -1
def _strip_comments(content: str) -> str:
result = []
i = 0
while i < len(content):
if content[i] == '/' and i + 1 < len(content) and content[i + 1] == '/':
while i < len(content) and content[i] != '\n':
i += 1
elif content[i] == '/' and i + 1 < len(content) and content[i + 1] == '*':
i += 2
while i + 1 < len(content) and not (content[i] == '*' and content[i + 1] == '/'):
i += 1
i += 2
elif content[i] == '"' or content[i] == "'":
quote = content[i]
result.append(content[i])
i += 1
while i < len(content) and content[i] != quote:
if content[i] == '\\' and i + 1 < len(content):
result.append(content[i])
i += 1
result.append(content[i])
i += 1
if i < len(content):
result.append(content[i])
i += 1
else:
result.append(content[i])
i += 1
return ''.join(result)
def _strip_preprocessor_guards(content: str) -> str:
lines = content.split('\n')
filtered = []
for line in lines:
stripped = line.strip()
if stripped.startswith('#ifndef') and '_H' in stripped.upper():
continue
if stripped.startswith('#define') and '_H' in stripped.upper():
continue
if stripped == '#endif':
continue
filtered.append(line)
return '\n'.join(filtered)
class CHeaderParser:
"""C 头文件解析器"""
def __init__(self):
self.defines: List[Dict] = []
self.structs: Dict[str, Dict] = {}
self.enums: Dict[str, Dict] = {}
self.typedefs: Dict[str, str] = []
self.functions: Dict[str, Dict] = {}
self.variables: Dict[str, Dict] = {}
self.unions: Dict[str, Dict] = {}
self.func_ptr_typedefs: Dict[str, Dict] = {}
self._typedef_struct_map: Dict[str, str] = {}
self._typedef_enum_map: Dict[str, str] = {}
self._typedef_union_map: Dict[str, str] = {}
def ParseFile(self, FilePath: str) -> None:
with open(FilePath, 'r', encoding='utf-8') as f:
content = f.read()
self.ParseContent(content)
def ParseContent(self, content: str) -> None:
content = _strip_comments(content)
content = _strip_preprocessor_guards(content)
self._ParseDefines(content)
self._ParseTypedefStructs(content)
self._ParseTypedefEnums(content)
self._ParseTypedefUnions(content)
self._ParseFuncPtrTypedefs(content)
self._ParseSimpleTypedefs(content)
self._ParseStandaloneStructs(content)
self._ParseStandaloneEnums(content)
self._ParseStandaloneUnions(content)
stripped = self._StripBodies(content)
self._ParseFunctions(stripped)
self._ParseVariables(stripped)
def _StripBodies(self, content: str) -> str:
result = content
while True:
pos = result.find('{')
if pos < 0:
break
end = _find_matching_brace(result, pos)
if end < 0:
break
result = result[:pos] + result[end + 1:]
return result
def _ParseDefines(self, content: str) -> None:
for match in re.finditer(r'#define\s+(\w+)(?:\s+(.+?))?\s*$', content, re.MULTILINE):
Name = match.group(1)
Value = match.group(2)
if Name.startswith('_') and Name.endswith('_'):
continue
if Name.startswith('__'):
continue
self.defines.append({
'name': Name,
'value': Value.strip() if Value else None,
})
def _ParseTypedefStructs(self, content: str) -> None:
pattern = r'typedef\s+struct\s*(\w*)\s*\{'
for match in re.finditer(pattern, content):
brace_start = content.index('{', match.start())
brace_end = _find_matching_brace(content, brace_start)
if brace_end < 0:
continue
body = content[brace_start + 1:brace_end]
rest = content[brace_end + 1:].lstrip()
AliasMatch = re.match(r'(\w+)\s*;', rest)
if not AliasMatch:
continue
AliasName = AliasMatch.group(1)
TagName = match.group(1) if match.group(1) else None
members = self._ParseStructMembers(body)
self.structs[AliasName] = {
'name': AliasName,
'tag': TagName,
'members': members,
}
if TagName:
self._typedef_struct_map[TagName] = AliasName
def _ParseTypedefEnums(self, content: str) -> None:
pattern = r'typedef\s+enum\s*(\w*)\s*\{'
for match in re.finditer(pattern, content):
brace_start = content.index('{', match.start())
brace_end = _find_matching_brace(content, brace_start)
if brace_end < 0:
continue
body = content[brace_start + 1:brace_end]
rest = content[brace_end + 1:].lstrip()
AliasMatch = re.match(r'(\w+)\s*;', rest)
if not AliasMatch:
continue
AliasName = AliasMatch.group(1)
TagName = match.group(1) if match.group(1) else None
values = self._ParseEnumValues(body)
self.enums[AliasName] = {
'name': AliasName,
'tag': TagName,
'values': values,
}
if TagName:
self._typedef_enum_map[TagName] = AliasName
def _ParseTypedefUnions(self, content: str) -> None:
pattern = r'typedef\s+union\s*(\w*)\s*\{'
for match in re.finditer(pattern, content):
brace_start = content.index('{', match.start())
brace_end = _find_matching_brace(content, brace_start)
if brace_end < 0:
continue
body = content[brace_start + 1:brace_end]
rest = content[brace_end + 1:].lstrip()
AliasMatch = re.match(r'(\w+)\s*;', rest)
if not AliasMatch:
continue
AliasName = AliasMatch.group(1)
TagName = match.group(1) if match.group(1) else None
members = self._ParseStructMembers(body)
self.unions[AliasName] = {
'name': AliasName,
'tag': TagName,
'members': members,
}
if TagName:
self._typedef_union_map[TagName] = AliasName
def _ParseFuncPtrTypedefs(self, content: str) -> None:
pattern = r'typedef\s+([\w\s\*]+?)\s*\(\s*\*\s*(\w+)\s*\)\s*\(([^)]*)\)\s*;'
for match in re.finditer(pattern, content):
ReturnType = match.group(1).strip()
Name = match.group(2)
ParamsStr = match.group(3).strip()
params = self._ParseFuncParams(ParamsStr)
self.func_ptr_typedefs[Name] = {
'name': Name,
'return_type': ReturnType,
'params': params,
}
def _ParseSimpleTypedefs(self, content: str) -> None:
stripped = self._StripBodies(content)
pattern = r'typedef\s+([\w\s\*]+?)\s+(\w+)\s*;'
for match in re.finditer(pattern, stripped):
BaseType = match.group(1).strip()
AliasName = match.group(2)
if AliasName in self.structs:
continue
if AliasName in self.enums:
continue
if AliasName in self.unions:
continue
if AliasName in self.func_ptr_typedefs:
continue
if BaseType.startswith('typedef'):
continue
self.typedefs.append({'name': AliasName, 'base': BaseType})
def _ParseStandaloneStructs(self, content: str) -> None:
pattern = r'(?<!typedef\s)struct\s+(\w+)\s*\{'
for match in re.finditer(pattern, content):
TagName = match.group(1)
if TagName in self._typedef_struct_map:
continue
if TagName in self.structs:
continue
brace_start = content.index('{', match.start())
brace_end = _find_matching_brace(content, brace_start)
if brace_end < 0:
continue
body = content[brace_start + 1:brace_end]
members = self._ParseStructMembers(body)
self.structs[TagName] = {
'name': TagName,
'tag': None,
'members': members,
}
def _ParseStandaloneEnums(self, content: str) -> None:
pattern = r'(?<!typedef\s)enum\s+(\w+)\s*\{'
for match in re.finditer(pattern, content):
TagName = match.group(1)
if TagName in self._typedef_enum_map:
continue
if TagName in self.enums:
continue
brace_start = content.index('{', match.start())
brace_end = _find_matching_brace(content, brace_start)
if brace_end < 0:
continue
body = content[brace_start + 1:brace_end]
values = self._ParseEnumValues(body)
self.enums[TagName] = {
'name': TagName,
'tag': None,
'values': values,
}
def _ParseStandaloneUnions(self, content: str) -> None:
pattern = r'(?<!typedef\s)union\s+(\w+)\s*\{'
for match in re.finditer(pattern, content):
TagName = match.group(1)
if TagName in self._typedef_union_map:
continue
if TagName in self.unions:
continue
brace_start = content.index('{', match.start())
brace_end = _find_matching_brace(content, brace_start)
if brace_end < 0:
continue
body = content[brace_start + 1:brace_end]
members = self._ParseStructMembers(body)
self.unions[TagName] = {
'name': TagName,
'tag': None,
'members': members,
}
def _ParseFunctions(self, content: str) -> None:
pattern = r'(?:(?:static|extern|inline)\s+)*([\w][\w\s\*]*?)\b(\w+)\s*\(([^)]*)\)\s*;'
for match in re.finditer(pattern, content):
ReturnType = match.group(1).strip()
FuncName = match.group(2)
ParamsStr = match.group(3).strip()
if FuncName in self.structs or FuncName in self.enums or FuncName in self.unions:
continue
if FuncName in self.func_ptr_typedefs:
continue
if FuncName in self.defines:
continue
if ReturnType in ('struct', 'union', 'enum', 'typedef'):
continue
if re.match(r'^\d', ReturnType):
continue
if not re.match(r'^[A-Za-z_]', FuncName):
continue
params = self._ParseFuncParams(ParamsStr)
self.functions[FuncName] = {
'name': FuncName,
'return_type': ReturnType,
'params': params,
}
def _ParseVariables(self, content: str) -> None:
pattern = r'(?:(?:static|extern|volatile|const)\s+)*([\w][\w\s\*]*?)\s+(\w+)\s*(?:\[(\d*)\])?\s*;'
for match in re.finditer(pattern, content):
TypeStr = match.group(1).strip()
VarName = match.group(2)
ArraySize = match.group(3)
if VarName in self.functions or VarName in self.structs or VarName in self.enums:
continue
if VarName in self.unions or VarName in self.func_ptr_typedefs:
continue
if VarName in self.defines:
continue
if TypeStr.startswith('typedef'):
continue
if TypeStr in ('struct', 'union', 'enum', 'typedef'):
continue
if re.match(r'^(if|else|while|for|return|switch|case|break|continue|do|goto)$', VarName):
continue
if not re.match(r'^[A-Za-z_]', VarName):
continue
IsPtr = '*' in TypeStr
IsArray = ArraySize is not None
self.variables[VarName] = {
'name': VarName,
'type': TypeStr,
'IsPtr': IsPtr,
'is_array': IsArray,
'array_size': int(ArraySize) if ArraySize and ArraySize.isdigit() else None,
}
def _ParseStructMembers(self, body: str) -> List[Dict]:
members = []
for line in body.split(';'):
line = line.strip()
if not line:
continue
arr_match = re.match(r'([\w\s\*]+?)\s+(\w+)\s*\[(\d*)\]', line)
if arr_match:
MemberType = arr_match.group(1).strip()
MemberName = arr_match.group(2)
ArraySize = arr_match.group(3)
members.append({
'name': MemberName,
'type': MemberType,
'is_array': True,
'array_size': int(ArraySize) if ArraySize and ArraySize.isdigit() else None,
})
continue
ptr_match = re.match(r'([\w\s\*]+?)\s*\*\s*(\w+)', line)
if ptr_match:
MemberType = ptr_match.group(1).strip() + ' *'
MemberName = ptr_match.group(2)
members.append({
'name': MemberName,
'type': MemberType,
'IsPtr': True,
})
continue
simple_match = re.match(r'([\w\s]+?)\s+(\w+)', line)
if simple_match:
MemberType = simple_match.group(1).strip()
MemberName = simple_match.group(2)
members.append({
'name': MemberName,
'type': MemberType,
})
return members
def _ParseEnumValues(self, body: str) -> List[Dict]:
values = []
for item in body.split(','):
item = item.strip()
if not item:
continue
eq_pos = item.find('=')
if eq_pos >= 0:
Name = item[:eq_pos].strip()
Value = item[eq_pos + 1:].strip()
else:
Name = item.strip()
Value = None
if Name and re.match(r'^[A-Za-z_]\w*$', Name):
values.append({'name': Name, 'value': Value})
return values
def _ParseFuncParams(self, params_str: str) -> List[Dict]:
params = []
if not params_str or params_str.strip() == 'void' or params_str.strip() == '':
return params
for param in params_str.split(','):
param = param.strip()
if not param:
continue
func_ptr_match = re.match(r'([\w\s\*]+?)\s*\(\s*\*\s*(\w*)\s*\)\s*\([^)]*\)', param)
if func_ptr_match:
RetType = func_ptr_match.group(1).strip()
ParamName = func_ptr_match.group(2) or 'callback'
params.append({
'name': ParamName,
'type': RetType + ' (*)',
'is_func_ptr': True,
})
continue
arr_match = re.match(r'([\w\s\*]+?)\s+(\w+)\s*\[\d*\]', param)
if arr_match:
ParamType = arr_match.group(1).strip()
ParamName = arr_match.group(2)
params.append({
'name': ParamName,
'type': ParamType,
'is_array': True,
})
continue
tokens = param.split()
if len(tokens) >= 2:
LastToken = tokens[-1]
PtrCount = 0
while LastToken.startswith('*'):
PtrCount += 1
LastToken = LastToken[1:]
TypeTokens = tokens[:-1]
for _ in range(PtrCount):
TypeTokens.append('*')
ParamName = LastToken
ParamType = ' '.join(TypeTokens)
if not ParamName:
ParamName = 'arg'
ParamType = param
params.append({
'name': ParamName,
'type': ParamType,
})
elif len(tokens) == 1:
params.append({
'name': 'arg',
'type': tokens[0],
})
return params
class PythonStubGenerator:
"""Python 存根文件生成器"""
def __init__(self, parser: CHeaderParser):
self.parser = parser
self.OutputLines: List[str] = []
def generate(self, ModuleName: Optional[str] = None) -> str:
self.OutputLines = []
self._AddHeader(ModuleName)
self._GenerateImports()
self._GenerateDefines()
self._GenerateFuncPtrTypedefs()
self._GenerateSimpleTypedefs()
self._GenerateStructs()
self._GenerateUnions()
self._GenerateEnums()
self._GenerateVariables()
self._GenerateFunctions()
return '\n'.join(self.OutputLines)
def _AddHeader(self, ModuleName: Optional[str] = None) -> None:
self.OutputLines.append('"""')
self.OutputLines.append('Auto-generated Python stub file from C header')
if ModuleName:
self.OutputLines.append(f'Module: {ModuleName}')
self.OutputLines.append('"""')
self.OutputLines.append('')
def _GenerateImports(self) -> None:
self.OutputLines.append('import t')
self.OutputLines.append('import c')
self.OutputLines.append('')
def _GenerateDefines(self) -> None:
if not self.parser.defines:
return
for define in self.parser.defines:
Name = define['name']
Value = define['value']
if Value is not None:
self.OutputLines.append(f'{Name}: t.CDefine = {Value}')
else:
self.OutputLines.append(f'{Name}: t.CDefine')
self.OutputLines.append('')
def _GenerateFuncPtrTypedefs(self) -> None:
if not self.parser.func_ptr_typedefs:
return
for Name, Info in self.parser.func_ptr_typedefs.items():
RetType = CTypeMapper.MapType(Info['return_type'])
ParamTypes = []
for p in Info['params']:
ParamTypes.append(CTypeMapper.MapType(p['type']))
ParamStr = ', '.join(ParamTypes) if ParamTypes else ''
self.OutputLines.append(f'{Name}: t.CTypedef = t.Callable[[{ParamStr}], {RetType}]')
self.OutputLines.append('')
def _GenerateSimpleTypedefs(self) -> None:
if not self.parser.typedefs:
return
for td in self.parser.typedefs:
AliasName = td['name']
BaseType = td['base']
PyType = CTypeMapper.MapType(BaseType)
self.OutputLines.append(f'{AliasName}: t.CTypedef | {PyType}')
self.OutputLines.append('')
def _GenerateStructs(self) -> None:
if not self.parser.structs:
return
for StructName, StructInfo in self.parser.structs.items():
self.OutputLines.append(f'@c.Attribute(t.attr.packed)')
self.OutputLines.append(f'class {StructName}(t.CStruct):')
if StructInfo['members']:
for member in StructInfo['members']:
MemberName = member['name']
IsPtr = member.get('IsPtr', False)
IsArray = member.get('is_array', False)
ArraySize = member.get('array_size')
PyType = CTypeMapper.MapType(member['type'], IsPtr=IsPtr)
if IsArray and ArraySize is not None:
self.OutputLines.append(f' {MemberName}: list[{PyType}, {ArraySize}]')
elif IsArray:
self.OutputLines.append(f' {MemberName}: {PyType} | t.CArrayPtr')
else:
self.OutputLines.append(f' {MemberName}: {PyType}')
else:
self.OutputLines.append(' pass')
self.OutputLines.append('')
def _GenerateUnions(self) -> None:
if not self.parser.unions:
return
for UnionName, UnionInfo in self.parser.unions.items():
self.OutputLines.append(f'@c.Attribute(t.attr.packed)')
self.OutputLines.append(f'class {UnionName}(t.CUnion):')
if UnionInfo['members']:
for member in UnionInfo['members']:
PyType = CTypeMapper.MapType(member['type'], IsPtr=member.get('IsPtr', False))
self.OutputLines.append(f' {member["name"]}: {PyType}')
else:
self.OutputLines.append(' pass')
self.OutputLines.append('')
def _GenerateEnums(self) -> None:
if not self.parser.enums:
return
for EnumName, EnumInfo in self.parser.enums.items():
self.OutputLines.append(f'class __{EnumName.lower()}(t.CEnum):')
if EnumInfo['values']:
for value in EnumInfo['values']:
if value['value'] is not None:
self.OutputLines.append(f' {value["name"]}: t.CDefine = {value["value"]}')
else:
self.OutputLines.append(f' {value["name"]}: t.CDefine')
else:
self.OutputLines.append(' pass')
self.OutputLines.append(f'{EnumName}: t.CTypedef = __{EnumName.lower()}')
self.OutputLines.append('')
def _GenerateVariables(self) -> None:
if not self.parser.variables:
return
for VarName, VarInfo in self.parser.variables.items():
PyType = CTypeMapper.MapType(VarInfo['type'], IsPtr=VarInfo['IsPtr'], IsArray=VarInfo['is_array'])
self.OutputLines.append(f'{VarName}: {PyType}')
self.OutputLines.append('')
def _GenerateFunctions(self) -> None:
if not self.parser.functions:
return
for FuncName, FuncInfo in self.parser.functions.items():
params = []
for param in FuncInfo['params']:
if param.get('is_func_ptr'):
PyType = CTypeMapper.MapType(param['type'])
elif param.get('is_array'):
PyType = CTypeMapper.MapType(param['type'], IsPtr=True)
else:
PyType = CTypeMapper.MapType(param['type'])
params.append(f'{param["name"]}: {PyType}')
ParamStr = ', '.join(params) if params else ''
ReturnType = CTypeMapper.MapType(FuncInfo['return_type'])
self.OutputLines.append(f'def {FuncName}({ParamStr}) -> {ReturnType}: pass')
self.OutputLines.append('')
def GenerateStubFromC(InputFile: str, OutputFile: Optional[str] = None) -> str:
parser = CHeaderParser()
parser.ParseFile(InputFile)
generator = PythonStubGenerator(parser)
ModuleName = os.path.splitext(os.path.basename(InputFile))[0]
content = generator.generate(ModuleName)
if OutputFile:
with open(OutputFile, 'w', encoding='utf-8') as f:
f.write(content)
print(f"Generated: {OutputFile}")
return content
if __name__ == '__main__':
import sys
if len(sys.argv) >= 3:
GenerateStubFromC(sys.argv[1], sys.argv[2])
elif len(sys.argv) == 2:
print(GenerateStubFromC(sys.argv[1]))
else:
test_code = '''
struct memory_block {
uint64_t size;
uint8_t state;
uint8_t order;
struct memory_block *next;
struct memory_block *prev;
};
typedef struct memory_block memory_block_t;
enum color_format {
COLOR_FORMAT_BGRA,
COLOR_FORMAT_RGBA,
COLOR_FORMAT_BGR,
COLOR_FORMAT_RGB
};
void *malloc(size_t size);
void free(void *ptr);
void *memcpy(void *dest, const void *src, size_t n);
'''
parser = CHeaderParser()
parser.ParseContent(test_code)
generator = PythonStubGenerator(parser)
print(generator.generate("test_header"))