Files
TransPyC/lib/StubGen/Converter.py
T
TermiNexus 5692e9b097 perf(stubgen): pyi 转换加缓存——convert 纯函数缓存 + Phase1 mtime 缓存
Converter.convert 147 次调用 45s、_process_file_pyi 98 次 50s。convert
按 (内容, 模块名) 纯函数缓存并合并内部两次 ast.parse;Phase1 按
(绝对路径, 模块名)+mtime+size 缓存 pyi 转换结果与 docstrings,未变化
跳过 open+read+parse。缓存上限 4096 条防内存膨胀,配置变更时清空。
2026-08-17 22:20:28 +08:00

1031 lines
51 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.
"""StubGen 转换器模块 - 将 Python 文件转换为存根格式"""
from __future__ import annotations
import vast as ast
import re
import fnmatch
from typing import List, Dict
from lib.core.SymbolUtils import IsListAnnotation, ExpandSoftKeywords
# 模块级转换结果缓存:{ (PyContent, ModuleName): str }
# convert 是纯函数(类级配置 IncludeAliasMap/VarExcludeList/MacroExcludeList 运行期不变),
# 相同输入必然产生相同输出。结果字符串不可变,调用方仅将其写入文件或拼接出新字符串,
# 缓存结果可安全复用(无需深拷贝)。Set* 配置方法会清空缓存以防配置变化导致结果过期。
_ConvertResultCache: dict[tuple[str, str], str] = {}
_ConvertCacheMaxSize: int = 4096
class PythonToStubConverter:
"""将 Python 文件转换为存根格式"""
# __include 别名映射表 {alias: ModuleName}
IncludeAliasMap: Dict[str, str] = {}
# 变量排除列表 - 匹配这些模式的变量不会被添加到存根文件
VarExcludeList: List[str] = []
# 宏排除列表 - 匹配这些模式的宏不会被添加到存根文件
MacroExcludeList: List[str] = []
@classmethod
def SetIncludeAliasMap(cls, alias_map: Dict[str, str]) -> None:
"""设置 __include 别名映射表"""
cls.IncludeAliasMap = alias_map
_ConvertResultCache.clear()
@classmethod
def SetVarExcludeList(cls, exclude_list: List[str]) -> None:
"""设置变量排除列表"""
cls.VarExcludeList = exclude_list
_ConvertResultCache.clear()
@classmethod
def SetMacroExcludeList(cls, exclude_list: List[str]) -> None:
"""设置宏排除列表"""
cls.MacroExcludeList = exclude_list
_ConvertResultCache.clear()
@classmethod
def _ShouldExcludeVar(cls, VarName: str) -> bool:
"""检查变量是否应该被排除"""
for pattern in cls.VarExcludeList:
if fnmatch.fnmatch(VarName, pattern):
return True
return False
@classmethod
def _ShouldExcludeMacro(cls, MacroName: str) -> bool:
"""检查宏是否应该被排除"""
for pattern in cls.MacroExcludeList:
if fnmatch.fnmatch(MacroName, pattern):
return True
return False
@staticmethod
def convert(PyContent: str, ModuleName: str) -> str:
"""将 Python 代码转换为存根格式"""
# 软关键字展开:fn/fun/func/function -> def(行内等宽/更短,行号不变),
# 保证后续 ast.parse 与 SourceLines 回读行号一致。
PyContent = ExpandSoftKeywords(PyContent)
# 模块级缓存:同一 (内容, 模块名) 直接复用上次转换结果(纯函数输出,不可变字符串)
CacheKey: tuple[str, str] = (PyContent, ModuleName)
CachedResult: str | None = _ConvertResultCache.get(CacheKey)
if CachedResult is not None:
return CachedResult
lines: list[str] = PyContent.split('\n')
StubLines: list[str] = []
# 添加文件头
StubLines.append('"""')
StubLines.append(f'Auto-generated Python stub file from {ModuleName}.vp')
StubLines.append(f'Module: {ModuleName}')
StubLines.append('"""')
StubLines.append('')
# 解析 Python 代码,检查是否已经导入了 t 和 c(AST 与下方主解析复用,避免重复 parse)
HasImportT: bool = False
HasImportC: bool = False
try:
tree: ast.Module = ast.parse(PyContent)
for node in tree.body:
if isinstance(node, ast.Import):
for alias in node.names:
if alias.name == 't':
HasImportT = True
elif alias.name == 'c':
HasImportC = True
except SyntaxError:
# 语法错误:同一内容两次解析结果必然相同,与主循环 fail-fast 行为一致
raise
except Exception:
# 非语法类解析异常:保持原语义(第一遍吞掉,主循环重新解析后向上传播)
tree = None
# 添加默认导入(如果源代码中没有)
if not HasImportT:
StubLines.append('import t')
if not HasImportC:
StubLines.append('import c')
if not HasImportT or not HasImportC:
StubLines.append('')
# 添加 c.CPragma("once")
# StubLines.append('c.CPragma("once")')
StubLines.append('')
# 生成文件级别宏守卫名称
#FileGuardName = f'__{ModuleName.upper()}_DEFINE__'
# 添加文件开头宏守卫
#StubLines.append(f'c.CIfndef({FileGuardName})')
#StubLines.append(f'{FileGuardName}: t.CDefine')
#StubLines.append('')
# 解析 Python 代码,提取类型定义(复用上方的 parse 结果)
try:
if tree is None:
# 第一遍解析失败(非语法类异常):重试一次,保持原"主循环重新解析"语义
tree = ast.parse(PyContent)
# 第一遍扫描:收集 Postdefinition 变量
PostdefVars: dict[str, list[tuple[str, str, ast.AnnAssign]]] = {} # {ClassName: [(VarName, TypeStr, node), ...]}
for node in tree.body:
if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
TypeStr: str = PythonToStubConverter._GetTypeString(node.annotation)
# 检查是否是 Postdefinition 类型
if 't.Postdefinition' in TypeStr:
# 提取引用的类名
ClassName: str | None = PythonToStubConverter._ExtractPostdefClass(node.annotation)
if ClassName:
if ClassName not in PostdefVars:
PostdefVars[ClassName] = []
PostdefVars[ClassName].append((node.target.id, TypeStr, node))
# 按原始顺序处理节点,保持代码的原始顺序
PrevType: str | None = None # 记录前一个节点的类型
for node in tree.body:
CurrentType: str | None = None
if isinstance(node, ast.ClassDef):
CurrentType = 'class'
# 如果前一个不是类,添加空行分隔
if PrevType is not None and PrevType != 'class':
StubLines.append('')
# 检查是否有对应的 Postdefinition 变量
ClassPostdefs: list[tuple[str, str, ast.AnnAssign]] = PostdefVars.get(node.name, [])
ClassLines: list[str] = PythonToStubConverter._ConvertClass(
node, SourceLines=lines, PostdefVars=ClassPostdefs
)
StubLines.extend(ClassLines)
elif isinstance(node, ast.FunctionDef):
CurrentType = 'function'
# 如果前一个是类或者是第一个函数,添加空行分隔
if PrevType == 'class' or (PrevType is not None and PrevType != 'function'):
StubLines.append('')
FuncLines: list[str] = PythonToStubConverter._ConvertFunction(node, SourceLines=lines)
StubLines.extend(FuncLines)
StubLines.append('') # 函数后空行
elif isinstance(node, ast.AnnAssign):
CurrentType = 'variable'
# 跳过 Postdefinition 变量(已经在类中处理)
TypeStr = PythonToStubConverter._GetTypeString(node.annotation)
if 't.Postdefinition' in TypeStr:
continue
# 检查是否带有 t.CStatic 标志,如果有则不排除
HasStatic: bool = 't.CStatic' in TypeStr
# 检查变量是否应该被排除
ShouldExclude: bool = False
if isinstance(node.target, ast.Name) and not HasStatic:
VarName: str = node.target.id
if PythonToStubConverter._ShouldExcludeVar(VarName):
ShouldExclude = True
if ShouldExclude:
continue
# 如果前一个不是变量,添加空行分隔
if PrevType is not None and PrevType != 'variable':
StubLines.append('')
VarLines: list[str] = PythonToStubConverter._ConvertVariable(node, SourceLines=lines)
# 移除多余的空行
VarLines = [line for line in VarLines if line.strip()]
StubLines.extend(VarLines)
elif isinstance(node, ast.Import):
# 处理导入语句
CurrentType = 'import'
ImportStr: str = PythonToStubConverter._GetImportString(node, SourceLines=lines)
if ImportStr:
if PrevType is not None and PrevType != 'import':
StubLines.append('')
StubLines.append(ImportStr)
elif isinstance(node, ast.Assign):
CurrentType = 'variable'
# 处理模块级别的全局变量(无类型标注)
if PrevType is not None and PrevType != 'variable':
StubLines.append('')
AssignLines: list[str] = PythonToStubConverter._ConvertGlobalAssign(node)
StubLines.extend(AssignLines)
elif isinstance(node, ast.ImportFrom):
# 处理 from ... import ... 语句
CurrentType = 'import'
ImportStr = PythonToStubConverter._GetImportFromString(node, SourceLines=lines)
if ImportStr:
if PrevType is not None and PrevType != 'import':
StubLines.append('')
StubLines.append(ImportStr)
elif isinstance(node, ast.Expr):
# 处理模块级别的宏调用,如 c.CIf(...), c.CEndif()
ExprStr: str = PythonToStubConverter._GetExprString(node.value)
if ExprStr:
CurrentType = 'macro'
StubLines.append(ExprStr)
StubLines.append('') # 宏后空行
elif isinstance(node, ast.If):
# 处理模块级别的 if 宏条件,如 if c.CIf(FF_MULTI_PARTITION):
IfStr: str = PythonToStubConverter._GetModuleIfMacroString(node)
if IfStr:
CurrentType = 'macro'
StubLines.append(IfStr)
StubLines.append('') # 宏后空行
if CurrentType:
PrevType = CurrentType
# 添加文件结尾宏守卫
#StubLines.append('')
#StubLines.append('c.CEndif()')
except SyntaxError as e:
# 语法错误直接终止(fail-fast),不允许生成残缺 stub 被全局缓存命中。
# 残缺 vpi 只含 import 头,函数/类型声明全缺,会在后续阶段以
# undefined symbol / mangle 错误形式爆发(如 ExpandKeywordsTest 链接失败)。
raise
Result: str = '\n'.join(StubLines)
# 缓存上限保护:条目过多时整体清空,避免内存无限增长(结果可随时重算)
if len(_ConvertResultCache) >= _ConvertCacheMaxSize:
_ConvertResultCache.clear()
_ConvertResultCache[CacheKey] = Result
return Result
@staticmethod
def _ConvertClass(node: ast.ClassDef, SourceLines: list[str] | None = None, PostdefVars: list[tuple[str, str, ast.AnnAssign]] | None = None, indent: int = 0) -> list[str]:
"""转换类定义
Args:
node: 类定义节点
SourceLines: 源代码行列表
PostdefVars: Postdefinition 变量列表 [(VarName, TypeStr, node), ...]
indent: 缩进级别(用于嵌套类)
"""
lines: list[str] = []
PostdefVars = PostdefVars or []
IndentStr: str = ' ' * indent
# 处理类装饰器
for decorator in node.decorator_list:
DecoratorStr: str = PythonToStubConverter._GetDecoratorString(decorator)
if DecoratorStr:
lines.append(f'{IndentStr}@{DecoratorStr}')
# 检查是否是结构体、联合体或枚举
BaseNames: list[str] = [base.attr if isinstance(base, ast.Attribute) else base.id
for base in node.bases
if isinstance(base, (ast.Name, ast.Attribute))]
# 保留原始基类
if node.bases:
BaseStrs: list[str] = []
for base in node.bases:
if isinstance(base, ast.Name):
BaseStrs.append(base.id)
elif isinstance(base, ast.Attribute):
BaseStrs.append(f'{base.value.id}.{base.attr}')
elif isinstance(base, ast.Subscript):
# 处理泛型类型,如 memory_block_t[MAX_ORDER + 1]
BaseStrs.append(PythonToStubConverter._GetTypeString(base))
ClassTypeParamStr: str = ''
if hasattr(node, 'type_params') and node.type_params:
param_names: list[str] = [tp.name for tp in node.type_params]
ClassTypeParamStr = f'[{", ".join(param_names)}]'
if BaseStrs:
lines.append(f'{IndentStr}class {node.name}{ClassTypeParamStr}({", ".join(BaseStrs)}):')
else:
lines.append(f'{IndentStr}class {node.name}{ClassTypeParamStr}:')
else:
ClassTypeParamStr = ''
if hasattr(node, 'type_params') and node.type_params:
param_names = [tp.name for tp in node.type_params]
ClassTypeParamStr = f'[{", ".join(param_names)}]'
lines.append(f'{IndentStr}class {node.name}{ClassTypeParamStr}:')
# 处理类成员
HasMembers: bool = False
seen_member_names: set[str] = set()
for item in node.body:
if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
seen_member_names.add(item.target.id)
init_members: list[ast.AnnAssign] = []
for item in node.body:
if isinstance(item, ast.FunctionDef) and item.name == '__init__':
for stmt in item.body:
if (isinstance(stmt, ast.AnnAssign)
and isinstance(stmt.target, ast.Attribute)
and isinstance(stmt.target.value, ast.Name)
and stmt.target.value.id == 'self'):
init_members.append(stmt)
untyped_self_members: list[ast.Assign] = []
for m_name in [m.target.attr for m in init_members if isinstance(m.target, ast.Attribute)]:
seen_member_names.add(m_name)
for item in node.body:
if isinstance(item, ast.FunctionDef):
for stmt in item.body:
if (isinstance(stmt, ast.Assign)
and len(stmt.targets) == 1
and isinstance(stmt.targets[0], ast.Attribute)
and isinstance(stmt.targets[0].value, ast.Name)
and stmt.targets[0].value.id == 'self'):
attr_name: str = stmt.targets[0].attr
if attr_name not in seen_member_names:
seen_member_names.add(attr_name)
untyped_self_members.append(stmt)
for item in list(node.body) + init_members + untyped_self_members:
if isinstance(item, ast.Assign):
if (len(item.targets) == 1
and isinstance(item.targets[0], ast.Attribute)
and isinstance(item.targets[0].value, ast.Name)
and item.targets[0].value.id == 'self'):
HasMembers = True
MemberIndent: str = IndentStr + ' '
attr_name = item.targets[0].attr
if attr_name in seen_member_names:
continue
InferredType: str = 'int'
if item.value and isinstance(item.value, ast.Constant):
if isinstance(item.value.value, float):
InferredType = 'float'
elif isinstance(item.value.value, bool):
InferredType = 'bool'
elif isinstance(item.value.value, str):
InferredType = 'str'
lines.append(f'{MemberIndent}{attr_name}: {InferredType}')
else:
HasMembers = True
MemberIndent = IndentStr + ' '
if SourceLines and hasattr(item, 'lineno'):
StartLine: int = item.lineno - 1
EndLine: int = item.end_lineno if hasattr(item, 'end_lineno') and item.end_lineno else StartLine + 1
for i in range(StartLine, min(EndLine, len(SourceLines))):
lines.append(MemberIndent + SourceLines[i].lstrip())
elif isinstance(item, ast.AnnAssign):
HasMembers = True
TypeStr: str = PythonToStubConverter._GetTypeString(item.annotation)
MemberIndent = IndentStr + ' '
# __provides__/__requires__/__require_must__ 元数据:保留值以便跨模块加载
if isinstance(item.target, ast.Name) and item.target.id in ('__provides__', '__requires__', '__require_must__'):
if item.value is not None:
try:
value_str: str = ast.unparse(item.value)
except Exception:
value_str = ''
lines.append(f'{MemberIndent}{item.target.id}: {TypeStr} = {value_str}')
else:
lines.append(f'{MemberIndent}{item.target.id}: {TypeStr}')
continue
# 检查是否是宏变量(类型包含 t.CDefine)
if 't.CDefine' in TypeStr:
# 检查宏变量是否应该被排除
if isinstance(item.target, ast.Name):
MacroName: str = item.target.id
if PythonToStubConverter._ShouldExcludeMacro(MacroName):
continue # 跳过该宏
# 宏变量保持原样(包括赋值)
if SourceLines and hasattr(item, 'lineno'):
StartLine = item.lineno - 1
EndLine = item.end_lineno if hasattr(item, 'end_lineno') and item.end_lineno else StartLine + 1
for i in range(StartLine, min(EndLine, len(SourceLines))):
lines.append(MemberIndent + SourceLines[i].lstrip())
else:
lines.append(f'{MemberIndent}{item.target.id}: {TypeStr}')
else:
if isinstance(item.target, ast.Attribute):
MemberName: str = item.target.attr
else:
MemberName = item.target.id
FinalTypeStr: str = TypeStr
if IsListAnnotation(item.annotation):
slice_node: ast.AST = item.annotation.slice
has_size: bool = False
if isinstance(slice_node, ast.Tuple) and len(slice_node.elts) == 2:
count_node: ast.AST = slice_node.elts[1]
if isinstance(count_node, ast.Constant) and isinstance(count_node.value, int) and count_node.value > 0:
has_size = True
else:
try:
count_expr: str = ast.unparse(count_node)
except Exception:
count_expr = ''
if count_expr and count_expr != 'None':
has_size = True
elem_type_str: str = PythonToStubConverter._GetTypeString(slice_node.elts[0])
FinalTypeStr = f't.CArray[{elem_type_str}, {count_expr}]'
if not has_size:
elem_type_str = PythonToStubConverter._GetTypeString(slice_node if not isinstance(slice_node, ast.Tuple) else slice_node.elts[0])
init_len: int = 0
if item.value is not None:
if isinstance(item.value, ast.List):
init_len = len(item.value.elts)
elif isinstance(item.value, ast.Constant) and isinstance(item.value.value, str):
init_len = len(item.value.value) + 1
if init_len > 0:
FinalTypeStr = f't.CArray[{elem_type_str}, {init_len}]'
else:
FinalTypeStr = f't.CArray[{elem_type_str}, None]'
if item.value is not None and isinstance(item.value, ast.Constant):
try:
value_repr: str = ast.unparse(item.value)
lines.append(f'{MemberIndent}{MemberName}: {FinalTypeStr} = {value_repr}')
except Exception:
lines.append(f'{MemberIndent}{MemberName}: {FinalTypeStr}')
else:
lines.append(f'{MemberIndent}{MemberName}: {FinalTypeStr}')
elif isinstance(item, ast.FunctionDef):
HasMembers = True
FuncStub: list[str] = PythonToStubConverter._ConvertFunction(item, indent=indent+1, SourceLines=SourceLines, class_name=node.name)
lines.extend(FuncStub)
elif isinstance(item, ast.Expr):
# 处理宏调用,如 c.CIf(...), c.CEndif(), c.CElif(), c.CElse()
ExprStr: str = PythonToStubConverter._GetExprString(item.value)
if ExprStr:
lines.append(f'{IndentStr} {ExprStr}')
elif isinstance(item, ast.If):
# 处理 if c.CIf(...): 形式的宏条件
IfStr: str = PythonToStubConverter._get_if_macro_string(item, indent=indent+1)
if IfStr:
lines.append(IfStr)
elif isinstance(item, ast.ClassDef):
# 处理嵌套类
HasMembers = True
NestedClassLines: list[str] = PythonToStubConverter._ConvertClass(item, SourceLines=SourceLines, PostdefVars=[], indent=indent+1)
# 移除嵌套类的宏守卫(因为已经在父类的宏守卫内)
FilteredLines: list[str] = []
for line in NestedClassLines:
# 跳过宏守卫相关行
if line.strip().startswith('c.CIfndef(') or line.strip().startswith('c.CEndif()'):
continue
if ': t.CDefine' in line and '__' in line:
continue
FilteredLines.append(line)
lines.extend(FilteredLines)
if not HasMembers:
lines.append(f'{IndentStr} pass')
# 添加 Postdefinition 变量
if PostdefVars:
lines.append('')
for VarName, TypeStr, _ in PostdefVars:
lines.append(f'{VarName}: {TypeStr}')
return lines
@staticmethod
def _ConvertFunction(node: ast.FunctionDef, indent: int = 0, SourceLines: list[str] | None = None, class_name: str | None = None) -> list[str]:
"""转换函数定义"""
lines: list[str] = []
IndentStr: str = ' ' * indent
# 检查是否是宏函数(返回类型包含 t.CDefine)
is_macro_func: bool = False
if node.returns:
ReturnType: str = PythonToStubConverter._GetTypeString(node.returns)
if 't.CDefine' in ReturnType:
is_macro_func = True
# 如果是宏函数,检查是否应该被排除
if is_macro_func:
if PythonToStubConverter._ShouldExcludeMacro(node.name):
return lines # 返回空列表,排除该宏
# 保持原样(包括代码块)
if SourceLines:
StartLine: int = node.lineno - 1
EndLine: int = node.end_lineno if hasattr(node, 'end_lineno') and node.end_lineno else StartLine + 1
for i in range(StartLine, min(EndLine, len(SourceLines))):
lines.append(SourceLines[i])
if lines and lines[-1].strip() and not lines[-1].rstrip().endswith(':'):
pass
else:
lines.append(f'{IndentStr} pass')
return lines
# 处理函数装饰器
for decorator in node.decorator_list:
DecoratorStr: str = PythonToStubConverter._GetDecoratorString(decorator)
if DecoratorStr:
lines.append(f'{IndentStr}@{DecoratorStr}')
# 构建参数列表
# 治本修复:原实现只遍历 node.args.args,完全忽略 node.args.defaults,
# 导致带默认值的参数(如 category: str = "")在 .pyi 中丢失默认值。
# 下游 DeclarationGenerator 据此生成 stub.ll 时,会因缺少默认参数而
# 生成参数数量不足的 declare,进而造成调用方 ABI 不匹配崩溃。
# 修复:按 Python AST 语义,defaults 从右向左对应 args 末尾的参数,
# 对有默认值的参数附加 '= <default_value>'。
params: list[str] = []
NumDefaults: int = len(node.args.defaults) if node.args.defaults else 0
DefaultsStart: int = len(node.args.args) - NumDefaults
for arg_idx, arg in enumerate(node.args.args):
ArgName: str = arg.arg
if arg.annotation:
TypeStr: str = PythonToStubConverter._GetTypeString(arg.annotation)
ParamPart: str = f'{ArgName}: {TypeStr}'
elif class_name and arg_idx == 0 and ArgName == 'self':
ParamPart = f'self: {class_name}'
else:
ParamPart = ArgName
# 附加默认值:defaults[i] 对应 args[DefaultsStart + i]
if NumDefaults > 0 and arg_idx >= DefaultsStart:
DefaultIdx: int = arg_idx - DefaultsStart
try:
DefaultStr: str = ast.unparse(node.args.defaults[DefaultIdx])
ParamPart = f'{ParamPart} = {DefaultStr}'
except Exception:
# unparse 失败时保留无默认值形式,避免阻塞生成
pass
params.append(ParamPart)
# 处理 *args
if node.args.vararg:
ArgName = node.args.vararg.arg
if node.args.vararg.annotation:
TypeStr = PythonToStubConverter._GetTypeString(node.args.vararg.annotation)
params.append(f'*{ArgName}: {TypeStr}')
else:
params.append(f'*{ArgName}')
# 处理 **kwargs
if node.args.kwarg:
ArgName = node.args.kwarg.arg
if node.args.kwarg.annotation:
TypeStr = PythonToStubConverter._GetTypeString(node.args.kwarg.annotation)
params.append(f'**{ArgName}: {TypeStr}')
else:
params.append(f'**{ArgName}')
ParamStr: str = ', '.join(params)
# 返回类型 - 保持原始类型(不添加t.State)
if node.returns:
ReturnType = PythonToStubConverter._GetTypeString(node.returns)
else:
ReturnType = 't.CInt'
TypeParamStr: str = ''
if hasattr(node, 'type_params') and node.type_params:
param_names: list[str] = [tp.name for tp in node.type_params]
TypeParamStr = f'[{", ".join(param_names)}]'
lines.append(f'{IndentStr}def {node.name}{TypeParamStr}({ParamStr}) -> {ReturnType}: pass')
return lines
@staticmethod
def _ConvertVariable(node: ast.AnnAssign, SourceLines: list[str] | None = None) -> list[str]:
"""转换变量定义"""
lines: list[str] = []
if isinstance(node.target, ast.Name):
VarName: str = node.target.id
TypeStr: str = PythonToStubConverter._GetTypeString(node.annotation)
# 检查是否是宏变量(类型包含 t.CDefine)
if 't.CDefine' in TypeStr:
# 检查宏变量是否应该被排除
if PythonToStubConverter._ShouldExcludeMacro(VarName):
return lines # 返回空列表,排除该宏
# 宏变量保持原样(包括赋值)
if SourceLines and hasattr(node, 'lineno'):
StartLine: int = node.lineno - 1
EndLine: int = node.end_lineno if hasattr(node, 'end_lineno') and node.end_lineno else StartLine + 1
for i in range(StartLine, min(EndLine, len(SourceLines))):
lines.append(SourceLines[i])
else:
lines.append(f'{VarName}: {TypeStr}')
return lines
# 非宏变量:不加 t.State
# 含有 typedef 的变量不加 extern,普通变量需要加 extern
has_typedef: bool = 't.CTypedef' in TypeStr or 'CTypedef' in TypeStr
if has_typedef and node.value is not None:
ValueTypeStr: str = PythonToStubConverter._GetTypeString(node.value)
lines.append(f'{VarName}: {TypeStr} = {ValueTypeStr}')
else:
if not has_typedef and 't.CExtern' not in TypeStr and 't.CStatic' not in TypeStr:
TypeStr = f't.CExtern | {TypeStr}'
lines.append(f'{VarName}: {TypeStr}')
return lines
@staticmethod
def _ConvertGlobalAssign(node: ast.Assign) -> list[str]:
"""转换模块级别的全局变量(无类型标注)"""
lines: list[str] = []
if not node.targets or not isinstance(node.targets[0], ast.Name):
return lines
VarName: str = node.targets[0].id
# 跳过私有变量(以下划线开头)
if VarName.startswith('_'):
return lines
# 从值推断类型
inferred_type: str | None = PythonToStubConverter._InferTypeFromValue(node.value)
if inferred_type:
lines.append(f'{VarName}: {inferred_type}')
else:
lines.append(f'{VarName}: t.CInt')
return lines
@staticmethod
def _InferTypeFromValue(value: ast.AST) -> str | None:
"""从值推断 C 类型"""
if isinstance(value, ast.Constant):
if isinstance(value.value, int):
return 't.CInt'
elif isinstance(value.value, float):
return 't.CDouble'
elif isinstance(value.value, str):
return 't.CCharPtr'
elif isinstance(value.value, bool):
return 't.CInt'
elif isinstance(value, ast.List):
return 't.CVoidPtr'
elif isinstance(value, ast.Dict):
return 't.CVoidPtr'
elif isinstance(value, ast.Name):
return 't.CInt'
elif isinstance(value, ast.BinOp):
left_type: str | None = PythonToStubConverter._InferTypeFromValue(value.left)
if left_type:
return left_type
return 't.CInt'
elif isinstance(value, ast.Call):
# 函数调用返回值,假设为指针类型
if isinstance(value.func, ast.Name):
func_name: str = value.func.id
if func_name == 'malloc':
return 't.CVoidPtr'
elif func_name in ('ctypes.cast', 'cast'):
return 't.CVoidPtr'
return 't.CVoidPtr'
return None
@staticmethod
def _GetTypeString(annotation: ast.AST) -> str:
"""获取类型字符串"""
if isinstance(annotation, ast.Name):
return annotation.id
elif isinstance(annotation, ast.Attribute):
if isinstance(annotation.value, ast.Name):
return f'{annotation.value.id}.{annotation.attr}'
else:
# 处理嵌套属性访问,如 t.attr.packed
ParentStr: str = PythonToStubConverter._GetTypeString(annotation.value)
return f'{ParentStr}.{annotation.attr}'
elif isinstance(annotation, ast.Subscript):
base: str = PythonToStubConverter._GetTypeString(annotation.value)
if isinstance(annotation.slice, ast.Tuple):
slice_strs: list[str] = [PythonToStubConverter._GetTypeString(e) for e in annotation.slice.elts]
SliceStr: str = ', '.join(slice_strs)
else:
SliceStr = PythonToStubConverter._GetTypeString(annotation.slice)
return f'{base}[{SliceStr}]'
elif isinstance(annotation, ast.BinOp):
# 处理 BinOp,如 t.CExtern | fd_table.__set_default__(...)
left: ast.AST = annotation.left
right: ast.AST = annotation.right
# 检查右侧是否是 __set_default__ 调用
if isinstance(right, ast.Call) and isinstance(right.func, ast.Attribute) and right.func.attr in ('__set_default__', '__set_default'):
# 去除 __set_default__,保留左侧,右侧替换为函数名部分
LeftStr: str = PythonToStubConverter._GetTypeString(left)
RightStr: str = PythonToStubConverter._GetTypeString(right.func.value)
if isinstance(annotation.op, ast.BitOr):
return f'{LeftStr} | {RightStr}'
LeftStr = PythonToStubConverter._GetTypeString(left)
RightStr = PythonToStubConverter._GetTypeString(right)
if isinstance(annotation.op, ast.BitOr):
return f'{LeftStr} | {RightStr}'
elif isinstance(annotation.op, ast.Add):
return f'{LeftStr} + {RightStr}'
elif isinstance(annotation.op, ast.Sub):
return f'{LeftStr} - {RightStr}'
elif isinstance(annotation.op, ast.Mult):
return f'{LeftStr} * {RightStr}'
elif isinstance(annotation.op, ast.Div):
return f'{LeftStr} / {RightStr}'
elif isinstance(annotation.op, ast.Mod):
return f'{LeftStr} % {RightStr}'
elif isinstance(annotation, ast.UnaryOp):
if isinstance(annotation.op, ast.Not):
operand: str = PythonToStubConverter._GetTypeString(annotation.operand)
return f'not {operand}'
elif isinstance(annotation.op, ast.Invert):
operand = PythonToStubConverter._GetTypeString(annotation.operand)
return f'~{operand}'
elif isinstance(annotation.op, ast.USub):
operand = PythonToStubConverter._GetTypeString(annotation.operand)
return f'-{operand}'
elif isinstance(annotation, ast.Compare):
# 处理比较操作,如 FF_MAX_SS != FF_MIN_SS
left: str = PythonToStubConverter._GetTypeString(annotation.left)
comparators: list[str] = [PythonToStubConverter._GetTypeString(c) for c in annotation.comparators]
ops: list[str] = []
for op in annotation.ops:
if isinstance(op, ast.Eq):
ops.append('==')
elif isinstance(op, ast.NotEq):
ops.append('!=')
elif isinstance(op, ast.Lt):
ops.append('<')
elif isinstance(op, ast.LtE):
ops.append('<=')
elif isinstance(op, ast.Gt):
ops.append('>')
elif isinstance(op, ast.GtE):
ops.append('>=')
else:
ops.append('?')
result: str = left
for i, op in enumerate(ops):
result += f' {op} {comparators[i]}'
return result
elif isinstance(annotation, ast.Constant):
return repr(annotation.value)
elif isinstance(annotation, ast.Index):
return PythonToStubConverter._GetTypeString(annotation.value)
elif isinstance(annotation, ast.List):
# 处理列表,如 [MAX_FILE_DESCRIPTORS]
elements: list[str] = [PythonToStubConverter._GetTypeString(elem) for elem in annotation.elts]
return f'[{", ".join(elements)}]'
elif isinstance(annotation, ast.Call):
# 处理函数调用形式的类型注解,如 t.Postdefinition(xxa) 或 callable(t.CVoid, app_id = t.CInt)
# 如果是 __set_default__ 调用,只返回函数名部分
if isinstance(annotation.func, ast.Attribute) and annotation.func.attr in ('__set_default__', '__set_default'):
return PythonToStubConverter._GetTypeString(annotation.func.value)
FuncStr: str = PythonToStubConverter._GetTypeString(annotation.func)
ArgsStr: str = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in annotation.args])
# 处理关键字参数
keywords_str: str = ', '.join([f'{kw.arg} = {PythonToStubConverter._GetTypeString(kw.value)}' for kw in annotation.keywords])
# 合并位置参数和关键字参数
if ArgsStr and keywords_str:
return f'{FuncStr}({ArgsStr}, {keywords_str})'
elif keywords_str:
return f'{FuncStr}({keywords_str})'
else:
return f'{FuncStr}({ArgsStr})'
return 't.CVoid'
@staticmethod
def _ExtractPostdefClass(annotation: ast.AST) -> str | None:
"""从 Postdefinition 类型注解中提取类名
例如:t.Postdefinition(__buddy_system) -> '__buddy_system'
t.Postdefinition(xxx) | t.CTypedef -> 'xxx'
"""
# 处理 BinOp (如: t.Postdefinition(xxx) | t.CTypedef)
if isinstance(annotation, ast.BinOp):
# 递归检查左操作数
left_result: str | None = PythonToStubConverter._ExtractPostdefClass(annotation.left)
if left_result:
return left_result
# 递归检查右操作数
right_result: str | None = PythonToStubConverter._ExtractPostdefClass(annotation.right)
if right_result:
return right_result
return None
# 处理函数调用 (如: t.Postdefinition(__buddy_system))
if isinstance(annotation, ast.Call):
FuncStr: str = PythonToStubConverter._GetTypeString(annotation.func)
if FuncStr == 't.Postdefinition' and annotation.args:
# 提取第一个参数
first_arg: ast.AST = annotation.args[0]
if isinstance(first_arg, ast.Name):
return first_arg.id
elif isinstance(first_arg, ast.Attribute):
return f'{first_arg.value.id}.{first_arg.attr}'
return None
@staticmethod
def _GetImportString(node: ast.Import, SourceLines: list[str] | None = None) -> str:
"""获取导入语句字符串"""
parts: list[str] = []
IncludeAliasMap: dict[str, str] = PythonToStubConverter.IncludeAliasMap
for alias in node.names:
ModuleName: str = alias.name
# 检查是否是 __include.xxx 格式的导入
if ModuleName.startswith('__include.'):
# 提取别名(xxx 部分)
SubModule: str = ModuleName[len('__include.'):]
if SubModule in IncludeAliasMap:
# 替换为实际模块路径
RealModule: str = IncludeAliasMap[SubModule]
if alias.asname:
parts.append(f'{RealModule} as {alias.asname}')
else:
parts.append(f'{RealModule} as {SubModule}')
else:
# 不在映射表中,保持原样
if alias.asname:
parts.append(f'{ModuleName} as {alias.asname}')
else:
parts.append(ModuleName)
else:
if alias.asname:
parts.append(f'{alias.name} as {alias.asname}')
else:
parts.append(alias.name)
result: str = f'import {", ".join(parts)}'
# 提取并保留注释
if SourceLines and hasattr(node, 'lineno'):
LineIdx: int = node.lineno - 1
if 0 <= LineIdx < len(SourceLines):
line: str = SourceLines[LineIdx]
CommentMatch: re.Match | None = re.search(r'#.*$', line)
if CommentMatch:
result += ' ' + CommentMatch.group(0)
return result
@staticmethod
def _GetImportFromString(node: ast.ImportFrom, SourceLines: list[str] | None = None) -> str:
"""获取 from ... import ... 语句字符串"""
module: str = node.module or ''
parts: list[str] = []
IncludeAliasMap: dict[str, str] = PythonToStubConverter.IncludeAliasMap
# 处理相对导入 (from . import xxx 或 from ..package import xxx)
if node.level > 0:
if module:
if node.level == 1:
full_module: str = f'.{module}'
else:
full_module = '.' * node.level + module
else:
full_module = '.' * node.level
for alias in node.names:
if alias.asname:
parts.append(f'{alias.name} as {alias.asname}')
else:
parts.append(alias.name)
return f'from {full_module} import {", ".join(parts)}'
# 提取注释(在生成结果前获取)
comment: str = ''
if SourceLines and hasattr(node, 'lineno'):
LineIdx = node.lineno - 1
if 0 <= LineIdx < len(SourceLines):
line = SourceLines[LineIdx]
CommentMatch: re.Match | None = re.search(r'#.*$', line)
if CommentMatch:
comment = ' ' + CommentMatch.group(0)
# 检查是否是 from __include import xxx 格式
if module == '__include':
for alias in node.names:
name: str = alias.name
if name in IncludeAliasMap:
# 替换为实际模块路径
RealModule: str = IncludeAliasMap[name]
if alias.asname:
parts.append(f'import {RealModule} as {alias.asname}{comment}')
else:
parts.append(f'import {RealModule} as {name}{comment}')
else:
# 不在映射表中,保持原样
if alias.asname:
parts.append(f'from {module} import {name} as {alias.asname}{comment}')
else:
parts.append(f'from {module} import {name}{comment}')
return '\n'.join(parts)
# 普通 from ... import ... 语句
for alias in node.names:
if alias.asname:
parts.append(f'{alias.name} as {alias.asname}')
else:
parts.append(alias.name)
return f'from {module} import {", ".join(parts)}{comment}'
@staticmethod
def _GetDecoratorString(decorator: ast.AST) -> str:
"""获取装饰器字符串"""
if isinstance(decorator, ast.Name):
return decorator.id
elif isinstance(decorator, ast.Attribute):
if isinstance(decorator.value, ast.Name):
return f'{decorator.value.id}.{decorator.attr}'
return decorator.attr
elif isinstance(decorator, ast.Call):
# 处理带参数的装饰器,如 @c.Attribute(t.attr.packed)
FuncStr: str = PythonToStubConverter._GetDecoratorString(decorator.func)
ArgsStr: str = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in decorator.args])
return f'{FuncStr}({ArgsStr})'
return ''
@staticmethod
def _GetExprString(expr: ast.AST) -> str:
"""获取表达式字符串(用于宏调用)"""
if isinstance(expr, ast.Call):
# 处理函数调用,如 c.CIf(...), c.CEndif(), c.CUndef(...), c.CPragma(...)
FuncStr: str = PythonToStubConverter._GetDecoratorString(expr.func)
if FuncStr and ('CIf' in FuncStr or 'CEndif' in FuncStr or 'CElif' in FuncStr or 'CElse' in FuncStr or 'CUndef' in FuncStr or 'CPragma' in FuncStr):
ArgsStr: str = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in expr.args])
return f'{FuncStr}({ArgsStr})' if ArgsStr else f'{FuncStr}()'
return ''
@staticmethod
def _get_if_macro_string(node: ast.If, indent: int = 1) -> str:
"""获取类内部的 if 宏字符串(如 if c.CIf(...):)"""
IndentStr: str = ' ' * indent
InnerIndent: str = ' ' * (indent + 1)
# 检查条件是否是宏调用
if isinstance(node.test, ast.Call):
FuncStr: str = PythonToStubConverter._GetDecoratorString(node.test.func)
if FuncStr and 'CIf' in FuncStr:
ArgsStr: str = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in node.test.args])
result: list[str] = [f'{IndentStr}if {FuncStr}({ArgsStr}):' if ArgsStr else f'{IndentStr}if {FuncStr}():']
# 处理 if 体内的语句
for item in node.body:
if isinstance(item, ast.AnnAssign):
TypeStr: str = PythonToStubConverter._GetTypeString(item.annotation)
result.append(f'{InnerIndent}{item.target.id}: {TypeStr}')
elif isinstance(item, ast.Expr):
ExprStr: str = PythonToStubConverter._GetExprString(item.value)
if ExprStr:
result.append(f'{InnerIndent}{ExprStr}')
return '\n'.join(result)
return ''
@staticmethod
def _GetModuleIfMacroString(node: ast.If) -> str:
"""获取模块级别的 if 宏字符串(如 if c.CIf(FF_MULTI_PARTITION):)"""
return PythonToStubConverter._ProcessIfMacro(node, indent=0)
@staticmethod
def _ProcessIfMacro(node: ast.If, indent: int = 0) -> str:
"""处理 if 宏节点,支持 elif 和 else"""
IndentStr: str = ' ' * indent
# 检查条件是否是宏调用
if isinstance(node.test, ast.Call):
FuncStr: str = PythonToStubConverter._GetDecoratorString(node.test.func)
if FuncStr and 'CIf' in FuncStr:
ArgsStr: str = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in node.test.args])
result: list[str] = [f'{IndentStr}if {FuncStr}({ArgsStr}):' if ArgsStr else f'{IndentStr}if {FuncStr}():']
# 处理 if 体内的语句
for item in node.body:
result.extend(PythonToStubConverter._ProcessMacroBodyItem(item, indent + 1))
# 处理 elif 和 else
current: ast.If = node
while current.orelse:
if len(current.orelse) == 1 and isinstance(current.orelse[0], ast.If):
# elif 情况
elif_node: ast.If = current.orelse[0]
if isinstance(elif_node.test, ast.Call):
ElifFuncStr: str = PythonToStubConverter._GetDecoratorString(elif_node.test.func)
if ElifFuncStr and 'CIf' in ElifFuncStr:
ElifArgsStr: str = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in elif_node.test.args])
result.append(f'{IndentStr}elif {ElifFuncStr}({ElifArgsStr}):' if ElifArgsStr else f'{IndentStr}elif {ElifFuncStr}():')
for item in elif_node.body:
result.extend(PythonToStubConverter._ProcessMacroBodyItem(item, indent + 1))
current = elif_node
continue
break
else:
# else 情况
result.append(f'{IndentStr}else:')
for item in current.orelse:
result.extend(PythonToStubConverter._ProcessMacroBodyItem(item, indent + 1))
break
return '\n'.join(result)
return ''
@staticmethod
def _ProcessMacroBodyItem(item: ast.AST, indent: int = 0) -> list[str]:
"""处理宏体内的单个语句"""
IndentStr: str = ' ' * indent
result: list[str] = []
if isinstance(item, ast.ClassDef):
class_stub: list[str] = PythonToStubConverter._ConvertClass(item)
for line in class_stub:
if line.strip():
result.append(f'{IndentStr}{line}')
else:
result.append(line)
elif isinstance(item, ast.FunctionDef):
FuncStub: list[str] = PythonToStubConverter._ConvertFunction(item, indent=indent)
result.extend(FuncStub)
elif isinstance(item, ast.AnnAssign):
TypeStr: str = PythonToStubConverter._GetTypeString(item.annotation)
result.append(f'{IndentStr}{item.target.id}: {TypeStr}')
elif isinstance(item, ast.Expr):
ExprStr: str = PythonToStubConverter._GetExprString(item.value)
if ExprStr:
result.append(f'{IndentStr}{ExprStr}')
elif isinstance(item, ast.If):
# 处理嵌套的 if 宏
NestedIf: str = PythonToStubConverter._ProcessIfMacro(item, indent=indent)
if NestedIf:
result.append(NestedIf)
return result