Files
TransPyC/lib/core/stub_generator.py
T
TermiNexus b18d57fa38 perf(全仓库/正则): 字面量正则全部改模块级预编译常量(15 文件 / 115 个调用点)
背景(prof 基线 PROF/20260930_224857,主进程 433.69s):正则这一族合计 ≈45s ——
`re.match`(未预编译入口)被调 1970 万次 / 自身 10.70s、它内部 `Pattern.match` 占 8.06s,
`re._compile`(缓存查找)1975 万次 / 7.19s,`re._make`(真正编译)274 万次 / 7.62s
(说明 re 的 512 条缓存已被打穿,每 7 次调用就有 1 次重新编译)。
每次 `re.match(字面量, s)` 都要做"模式串哈希 → 缓存查找";预编译后这层全省掉。

改动:把 15 个文件里 97 处"字面量当模式"的 `re.match/search/sub/subn/fullmatch/
findall/finditer/split(...)` 提升为模块级 `_RE_*: re.Pattern[str] = re.compile(...)`,
共 115 个调用点(含同字面量跨处共用)。模式串与 flags 一字未改(flags 从调用处
搬进 re.compile,含原先以**位置参数**传入的 flags)。4 处拼接型动态模式按设计不动
(FuncGen 2 处、HandlesImports 2 处)。

核验(不靠肉眼):写了 AST 指纹对拍脚本,对每个文件取 HEAD 版与新工作区版,把
"旧 re.<m>(字面量, 其余实参, 关键字) + flags" 与 "新 _RE_X.<m>(实参, 关键字) +
常量 (模式, flags)" 做多重集比对 —— 15 个文件、115 个调用点全部逐一等价(0 失败),
另查无重名常量、无遗留字面量调用、GetDiagnostics 全空。
对拍过程中查出一处子代理抄写错误:`_RE_GLOBAL_DSOLOCAL_MULTILINE` 丢了行首锚 `^`
(原文注释明确"使用 ^ + MULTILINE 避免匹配字符串常量内容中的 define/declare"),已修正。

同轮经查证后**未做改动**的两项(避免把假热点当真事修):
- VLogger 的 `re.sub` 6.43s:profile 当时的 VLogger 版本里根本没有 `import re`
  (`git show 3658ab06:lib/core/VLogger.py` 已确认),属 C 帧错挂,不做。
- tokenize 274 万次:那是 `_generate_tokens_from_c_tokenizer` 的 **token 产出次数**
  (≈350 个整文件的词法量),SymbolUtils 的 4 个 tokenize 调用点 nc 仅数百,且已有
  `_EXPAND_CACHE` 去重;再加一层 token 缓存无收益,不做。
2026-09-30 23:47:36 +08:00

789 lines
33 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
C/H 文件到 Python 存根文件生成器
将 C 头文件 (.h) 或 C 源文件 (.c) 转换为 Python 存根文件 (.vpi)
生成的 Python 代码使用 TransPyC 语法,包含类型注解
"""
from __future__ import annotations
import os
import re
import sys
from typing import Any
from lib.core.VLogger import get_logger as _vlog
# PROF 热点:C 头文件解析阶段对每一行 / 每个成员反复调用未预编译的 re.match /
# re.finditer(模块级入口),每次都要「模式串哈希 → re 缓存查找 → 未命中就重新编译」。
# 这些模式全是字面量 → 提升为模块级预编译常量(模式串与调用点字面量逐字一致)。
# _ParseDefines 用:#define 指令(含可选值),模式带 re.MULTILINE
_RE_DEFINE: re.Pattern[str] = re.compile(r'#define\s+(\w+)(?:\s+(.+?))?\s*$', re.MULTILINE)
# _ParseTypedefStructs / _ParseTypedefEnums / _ParseTypedefUnions 用:typedef 结尾的 `别名;`
_RE_ALIAS: re.Pattern[str] = re.compile(r'(\w+)\s*;')
# _ParseFunctions 用:返回类型是否以数字开头
_RE_DIGIT_START: re.Pattern[str] = re.compile(r'^\d')
# _ParseFunctions / _ParseVariables 用:标识符是否以字母或下划线开头
_RE_IDENT_START: re.Pattern[str] = re.compile(r'^[A-Za-z_]')
# _ParseVariables 用:排除 C 关键字作为变量名
_RE_C_KEYWORD: re.Pattern[str] = re.compile(r'^(if|else|while|for|return|switch|case|break|continue|do|goto)$')
# _ParseStructMembers 用:结构体数组成员
_RE_STRUCT_ARRAY_MEMBER: re.Pattern[str] = re.compile(r'([\w\s\*]+?)\s+(\w+)\s*\[(\d*)\]')
# _ParseStructMembers 用:结构体指针成员
_RE_STRUCT_PTR_MEMBER: re.Pattern[str] = re.compile(r'([\w\s\*]+?)\s*\*\s*(\w+)')
# _ParseStructMembers 用:结构体普通成员
_RE_STRUCT_SIMPLE_MEMBER: re.Pattern[str] = re.compile(r'([\w\s]+?)\s+(\w+)')
# _ParseEnumValues 用:枚举项名是否合法
_RE_ENUM_NAME: re.Pattern[str] = re.compile(r'^[A-Za-z_]\w*$')
# _ParseFuncParams 用:函数指针参数
_RE_FUNC_PTR_PARAM: re.Pattern[str] = re.compile(r'([\w\s\*]+?)\s*\(\s*\*\s*(\w*)\s*\)\s*\([^)]*\)')
# _ParseFuncParams 用:数组参数
_RE_ARRAY_PARAM: re.Pattern[str] = re.compile(r'([\w\s\*]+?)\s+(\w+)\s*\[\d*\]')
class CTypeMapper:
# 硬编码 C 类型名 → Python 类型映射表
# 这是 stub 生成器(C 头文件 → Python stub)专用的固定映射,
# 不再依赖运行时 CTypeRegistry._cname_to_class(该映射已删除)。
_CNAME_TO_PY: dict[str, str] = {
# 基本类型
'char': 't.CChar', 'int': 't.CInt', 'short': 't.CShort',
'long': 't.CLong', 'long long': 't.CLongLong',
'float': 't.CFloat', 'double': 't.CDouble', 'void': 't.CVoid',
# unsigned / signed 变体
'unsigned': 't.CUnsigned', 'unsigned char': 't.CUnsignedChar',
'unsigned int': 't.CUnsignedInt', 'unsigned short': 't.CUnsignedShort',
'unsigned long': 't.CUnsignedLong',
'unsigned long long': 't.CUnsignedLongLong',
'signed char': 't.CSignedChar',
# bool / size_t
'bool': 't.CBool', '_Bool': 't.CBool', 'size_t': 't.CSizeT',
'ssize_t': 't.CLong',
# stdint 固定宽度类型
'int8_t': 't.CInt8T', 'int16_t': 't.CInt16T',
'int32_t': 't.CInt32T', 'int64_t': 't.CInt64T',
'uint8_t': 't.CUInt8T', 'uint16_t': 't.CUInt16T',
'uint32_t': 't.CUInt32T', 'uint64_t': 't.CUInt64T',
'intptr_t': 't.CIntPtrT', 'uintptr_t': 't.CUIntPtrT',
'ptrdiff_t': 't.CPtrDiffT',
# 宽字符 / 字符类型
'wchar_t': 't.CWCharT', 'char8_t': 't.CChar8T',
'char16_t': 't.CChar16T', 'char32_t': 't.CChar32T',
# 浮点特化类型
'float8_t': 't.CFloat8T', 'float16_t': 't.CFloat16T',
'float32_t': 't.CFloat32T', 'float64_t': 't.CFloat64T',
'float128_t': 't.CFloat128T',
# signed 速记
'signed': 't.CInt', 'signed int': 't.CInt',
'signed short': 't.CShort', 'signed long': 't.CLong',
}
@classmethod
def _ResolveCNameToPyType(cls, c_type: str) -> str:
"""将 C 类型名解析为 Python 类型字符串(如 'int' → 't.CInt')
使用硬编码映射表,不再依赖 CTypeRegistry._cname_to_class。
未识别的类型原样返回(用于 struct/union/enum/typedef 用户自定义名)。
"""
py: str | None = cls._CNAME_TO_PY.get(c_type)
if py is not None:
return py
return c_type
@classmethod
def MapType(cls, c_type: str, IsPtr: bool = False, IsArray: bool = False) -> str:
c_type = c_type.strip()
IsConst: bool = 'const ' in c_type
c_type = c_type.replace('const ', '').replace('const', '').strip()
IsVolatile: bool = 'volatile ' in c_type
c_type = c_type.replace('volatile ', '').replace('volatile', '').strip()
IsStatic: bool = 'static ' in c_type
c_type = c_type.replace('static ', '').replace('static', '').strip()
IsExtern: bool = 'extern ' in c_type
c_type = c_type.replace('extern ', '').replace('extern', '').strip()
IsRegister: bool = 'register ' in c_type
c_type = c_type.replace('register ', '').replace('register', '').strip()
IsInline: bool = 'inline ' in c_type
c_type = c_type.replace('inline ', '').replace('inline', '').strip()
PtrSuffix: str = ''
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: str = cls._ResolveCNameToPyType(c_type)
result: str = PyType + PtrSuffix
if IsArray:
result += ' | t.CArrayPtr'
return result
def _find_matching_brace(text: str, start: int) -> int:
depth: int = 0
i: int = 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: str = 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: list[str] = []
i: int = 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: str = 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: list[str] = content.split('\n')
filtered: list[str] = []
for line in lines:
stripped: str = 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) -> None:
self.defines: list[dict[str, str | None]] = []
self.structs: dict[str, dict[str, Any]] = {}
self.enums: dict[str, dict[str, Any]] = {}
self.typedefs: list[dict[str, str]] = []
self.functions: dict[str, dict[str, Any]] = {}
self.variables: dict[str, dict[str, Any]] = {}
self.unions: dict[str, dict[str, Any]] = {}
self.func_ptr_typedefs: dict[str, dict[str, Any]] = {}
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: str = 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: str = self._StripBodies(content)
self._ParseFunctions(stripped)
self._ParseVariables(stripped)
def _StripBodies(self, content: str) -> str:
result: str = content
while True:
pos: int = result.find('{')
if pos < 0:
break
end: int = _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_DEFINE.finditer(content):
Name: str = match.group(1)
Value: str | None = 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: str = r'typedef\s+struct\s*(\w*)\s*\{'
for match in re.finditer(pattern, content):
brace_start: int = content.index('{', match.start())
brace_end: int = _find_matching_brace(content, brace_start)
if brace_end < 0:
continue
body: str = content[brace_start + 1:brace_end]
rest: str = content[brace_end + 1:].lstrip()
AliasMatch: re.Match[str] | None = _RE_ALIAS.match(rest)
if not AliasMatch:
continue
AliasName: str = AliasMatch.group(1)
TagName: str | None = match.group(1) if match.group(1) else None
members: list[dict[str, Any]] = 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: str = r'typedef\s+enum\s*(\w*)\s*\{'
for match in re.finditer(pattern, content):
brace_start: int = content.index('{', match.start())
brace_end: int = _find_matching_brace(content, brace_start)
if brace_end < 0:
continue
body: str = content[brace_start + 1:brace_end]
rest: str = content[brace_end + 1:].lstrip()
AliasMatch: re.Match[str] | None = _RE_ALIAS.match(rest)
if not AliasMatch:
continue
AliasName: str = AliasMatch.group(1)
TagName: str | None = match.group(1) if match.group(1) else None
values: list[dict[str, str | None]] = 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: str = r'typedef\s+union\s*(\w*)\s*\{'
for match in re.finditer(pattern, content):
brace_start: int = content.index('{', match.start())
brace_end: int = _find_matching_brace(content, brace_start)
if brace_end < 0:
continue
body: str = content[brace_start + 1:brace_end]
rest: str = content[brace_end + 1:].lstrip()
AliasMatch: re.Match[str] | None = _RE_ALIAS.match(rest)
if not AliasMatch:
continue
AliasName: str = AliasMatch.group(1)
TagName: str | None = match.group(1) if match.group(1) else None
members: list[dict[str, Any]] = 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: str = r'typedef\s+([\w\s\*]+?)\s*\(\s*\*\s*(\w+)\s*\)\s*\(([^)]*)\)\s*;'
for match in re.finditer(pattern, content):
ReturnType: str = match.group(1).strip()
Name: str = match.group(2)
ParamsStr: str = match.group(3).strip()
params: list[dict[str, Any]] = self._ParseFuncParams(ParamsStr)
self.func_ptr_typedefs[Name] = {
'name': Name,
'return_type': ReturnType,
'params': params,
}
def _ParseSimpleTypedefs(self, content: str) -> None:
stripped: str = self._StripBodies(content)
pattern: str = r'typedef\s+([\w\s\*]+?)\s+(\w+)\s*;'
for match in re.finditer(pattern, stripped):
BaseType: str = match.group(1).strip()
AliasName: str = 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: str = r'(?<!typedef\s)struct\s+(\w+)\s*\{'
for match in re.finditer(pattern, content):
TagName: str = match.group(1)
if TagName in self._typedef_struct_map:
continue
if TagName in self.structs:
continue
brace_start: int = content.index('{', match.start())
brace_end: int = _find_matching_brace(content, brace_start)
if brace_end < 0:
continue
body: str = content[brace_start + 1:brace_end]
members: list[dict[str, Any]] = self._ParseStructMembers(body)
self.structs[TagName] = {
'name': TagName,
'tag': None,
'members': members,
}
def _ParseStandaloneEnums(self, content: str) -> None:
pattern: str = r'(?<!typedef\s)enum\s+(\w+)\s*\{'
for match in re.finditer(pattern, content):
TagName: str = match.group(1)
if TagName in self._typedef_enum_map:
continue
if TagName in self.enums:
continue
brace_start: int = content.index('{', match.start())
brace_end: int = _find_matching_brace(content, brace_start)
if brace_end < 0:
continue
body: str = content[brace_start + 1:brace_end]
values: list[dict[str, str | None]] = self._ParseEnumValues(body)
self.enums[TagName] = {
'name': TagName,
'tag': None,
'values': values,
}
def _ParseStandaloneUnions(self, content: str) -> None:
pattern: str = r'(?<!typedef\s)union\s+(\w+)\s*\{'
for match in re.finditer(pattern, content):
TagName: str = match.group(1)
if TagName in self._typedef_union_map:
continue
if TagName in self.unions:
continue
brace_start: int = content.index('{', match.start())
brace_end: int = _find_matching_brace(content, brace_start)
if brace_end < 0:
continue
body: str = content[brace_start + 1:brace_end]
members: list[dict[str, Any]] = self._ParseStructMembers(body)
self.unions[TagName] = {
'name': TagName,
'tag': None,
'members': members,
}
def _ParseFunctions(self, content: str) -> None:
pattern: str = r'(?:(?:static|extern|inline)\s+)*([\w][\w\s\*]*?)\b(\w+)\s*\(([^)]*)\)\s*;'
for match in re.finditer(pattern, content):
ReturnType: str = match.group(1).strip()
FuncName: str = match.group(2)
ParamsStr: str = 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_DIGIT_START.match(ReturnType):
continue
if not _RE_IDENT_START.match(FuncName):
continue
params: list[dict[str, Any]] = self._ParseFuncParams(ParamsStr)
self.functions[FuncName] = {
'name': FuncName,
'return_type': ReturnType,
'params': params,
}
def _ParseVariables(self, content: str) -> None:
pattern: str = r'(?:(?:static|extern|volatile|const)\s+)*([\w][\w\s\*]*?)\s+(\w+)\s*(?:\[(\d*)\])?\s*;'
for match in re.finditer(pattern, content):
TypeStr: str = match.group(1).strip()
VarName: str = match.group(2)
ArraySize: str | None = 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_C_KEYWORD.match(VarName):
continue
if not _RE_IDENT_START.match(VarName):
continue
IsPtr: bool = '*' in TypeStr
IsArray: bool = 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[str, Any]]:
members: list[dict[str, Any]] = []
for line in body.split(';'):
line = line.strip()
if not line:
continue
arr_match: re.Match[str] | None = _RE_STRUCT_ARRAY_MEMBER.match(line)
if arr_match:
MemberType: str = arr_match.group(1).strip()
MemberName: str = arr_match.group(2)
ArraySize: str = 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[str] | None = _RE_STRUCT_PTR_MEMBER.match(line)
if ptr_match:
MemberType: str = ptr_match.group(1).strip() + ' *'
MemberName: str = ptr_match.group(2)
members.append({
'name': MemberName,
'type': MemberType,
'IsPtr': True,
})
continue
simple_match: re.Match[str] | None = _RE_STRUCT_SIMPLE_MEMBER.match(line)
if simple_match:
MemberType: str = simple_match.group(1).strip()
MemberName: str = simple_match.group(2)
members.append({
'name': MemberName,
'type': MemberType,
})
return members
def _ParseEnumValues(self, body: str) -> list[dict[str, str | None]]:
values: list[dict[str, str | None]] = []
for item in body.split(','):
item = item.strip()
if not item:
continue
eq_pos: int = item.find('=')
if eq_pos >= 0:
Name: str = item[:eq_pos].strip()
Value: str | None = item[eq_pos + 1:].strip()
else:
Name = item.strip()
Value = None
if Name and _RE_ENUM_NAME.match(Name):
values.append({'name': Name, 'value': Value})
return values
def _ParseFuncParams(self, params_str: str) -> list[dict[str, Any]]:
params: list[dict[str, Any]] = []
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[str] | None = _RE_FUNC_PTR_PARAM.match(param)
if func_ptr_match:
RetType: str = func_ptr_match.group(1).strip()
ParamName: str = func_ptr_match.group(2) or 'callback'
params.append({
'name': ParamName,
'type': RetType + ' (*)',
'is_func_ptr': True,
})
continue
arr_match: re.Match[str] | None = _RE_ARRAY_PARAM.match(param)
if arr_match:
ParamType: str = arr_match.group(1).strip()
ParamName: str = arr_match.group(2)
params.append({
'name': ParamName,
'type': ParamType,
'is_array': True,
})
continue
tokens: list[str] = param.split()
if len(tokens) >= 2:
LastToken: str = tokens[-1]
PtrCount: int = 0
while LastToken.startswith('*'):
PtrCount += 1
LastToken = LastToken[1:]
TypeTokens: list[str] = tokens[:-1]
for _ in range(PtrCount):
TypeTokens.append('*')
ParamName: str = LastToken
ParamType: str = ' '.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) -> None:
self.parser: CHeaderParser = parser
self.OutputLines: list[str] = []
def generate(self, ModuleName: str | None = 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: str | None = 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: str = define['name']
Value: str | None = 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: str = CTypeMapper.MapType(Info['return_type'])
ParamTypes: list[str] = []
for p in Info['params']:
ParamTypes.append(CTypeMapper.MapType(p['type']))
ParamStr: str = ', '.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: str = td['name']
BaseType: str = td['base']
PyType: str = 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: str = member['name']
IsPtr: bool = member.get('IsPtr', False)
IsArray: bool = member.get('is_array', False)
ArraySize: int | None = member.get('array_size')
PyType: str = 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: str = 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: str = 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: list[str] = []
for param in FuncInfo['params']:
if param.get('is_func_ptr'):
PyType: str = CTypeMapper.MapType(param['type'])
elif param.get('is_array'):
PyType: str = CTypeMapper.MapType(param['type'], IsPtr=True)
else:
PyType: str = CTypeMapper.MapType(param['type'])
params.append(f'{param["name"]}: {PyType}')
ParamStr: str = ', '.join(params) if params else ''
ReturnType: str = CTypeMapper.MapType(FuncInfo['return_type'])
self.OutputLines.append(f'def {FuncName}({ParamStr}) -> {ReturnType}: pass')
self.OutputLines.append('')
def GenerateStubFromC(InputFile: str, OutputFile: str | None = None) -> str:
parser: CHeaderParser = CHeaderParser()
parser.ParseFile(InputFile)
generator: PythonStubGenerator = PythonStubGenerator(parser)
ModuleName: str = os.path.splitext(os.path.basename(InputFile))[0]
content: str = generator.generate(ModuleName)
if OutputFile:
with open(OutputFile, 'w', encoding='utf-8') as f:
f.write(content)
_vlog().success(f"生成: {OutputFile}")
return content
if __name__ == '__main__':
if len(sys.argv) >= 3:
GenerateStubFromC(sys.argv[1], sys.argv[2])
elif len(sys.argv) == 2:
_vlog().info(GenerateStubFromC(sys.argv[1]))
else:
test_code: str = '''
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 = CHeaderParser()
parser.ParseContent(test_code)
generator: PythonStubGenerator = PythonStubGenerator(parser)
_vlog().info(generator.generate("test_header"))