"""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 # 提取 import 语句行尾注释(`_GetImportString` / `_GetImportFromString`)用的 # **预编译**正则(性能:字面量正则预编译避免 re 缓存查找与编译开销)。 _RE_LINE_COMMENT: re.Pattern[str] = re.compile(r'#.*$') _STUB_TYPE_ALIASES: dict[str, str] = { 'CPtr': 'rawptr', 'CArrayPtr': 'rawptr', 'CAutoPtr': 'rawptr', 'CRef': 'rawptr', 'CTempRef': 'rawptr', # 旧内建类型名 → 新语法内建名(输入兼容;输出不得再出现 C* 标记) 'CChar': 'char', 'CInt': 'int', 'CShort': 'short', 'CLong': 'long', 'CLongLong': 'longlong', 'CFloat': 'float', 'CDouble': 'double', 'CVoid': 'void', 'CUnsigned': 'uint', 'CUnsignedChar': 'uchar', 'CUnsignedInt': 'uint', 'CUnsignedShort': 'ushort', 'CUnsignedLong': 'ulong', 'CUnsignedLongLong': 'ulonglong', 'CSignedChar': 'char', 'CBool': 'bool', 'CSizeT': 'sizet', 'CInt8T': 'int8', 'CInt16T': 'int16', 'CInt32T': 'int32', 'CInt64T': 'int64', 'CUInt8T': 'uint8', 'CUInt16T': 'uint16', 'CUInt32T': 'uint32', 'CUInt64T': 'uint64', 'CIntPtrT': 'intptr', 'CUIntPtrT': 'uintptr', 'CPtrDiffT': 'ptrdifft', 'CWCharT': 'wchar', 'CChar8T': 'char8', 'CChar16T': 'char16', 'CChar32T': 'char32', 'CFloat8T': 'float8', 'CFloat16T': 'float16', 'CFloat32T': 'float32', 'CFloat64T': 'float64', 'CFloat128T': 'float128', } 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 代码,供下方主循环复用(避免重复 parse)。 # stub 头部不注入 import 行;内建标记按裸名使用,无需导入。 tree: ast.Module | None = None try: # 传来源名:解析失败时 vast 的报错/回退诊断才带得上模块名(否则记为 ) tree = ast.parse(PyContent, f'{ModuleName}.vp') except SyntaxError: # 语法错误:同一内容两次解析结果必然相同,与主循环 fail-fast 行为一致 raise except Exception: # 非语法类解析异常:保持原语义(第一遍吞掉,主循环重新解析后向上传播) tree = None # 解析 Python 代码,提取类型定义(复用上方的 parse 结果) try: if tree is None: # 第一遍解析失败(非语法类异常):重试一次,保持原"主循环重新解析"语义 tree = ast.parse(PyContent, f'{ModuleName}.vp') # 第一遍扫描:收集 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 '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 'Postdefinition' in TypeStr: continue # 检查是否带有 static 标志(优先读 Markers),如果有则不排除 _AnnMarkers: tuple[str, ...] = ( tuple(getattr(node, 'Markers', None) or ()) + tuple(getattr(node.annotation, 'Markers', None) or ())) HasStatic: bool = 'static' in _AnnMarkers or '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): # 处理模块级别的宏调用,如 CUndef(...) / CPragma(...) ExprStr: str = PythonToStubConverter._GetExprString(node.value) if ExprStr: CurrentType = 'macro' StubLines.append(ExprStr) StubLines.append('') # 宏后空行 elif isinstance(node, ast.If): # 处理模块级别的 cif 宏条件,如 `cif FF_MULTI_PARTITION:` IfStr: str = PythonToStubConverter._GetModuleIfMacroString(node) if IfStr: CurrentType = 'macro' StubLines.append(IfStr) StubLines.append('') # 宏后空行 if CurrentType: PrevType = CurrentType 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 # 处理类装饰器:virtual/novirtual 还原为关键字前缀 ClassPrefix: str = '' for decorator in node.decorator_list: DecoratorStr: str = PythonToStubConverter._GetDecoratorString(decorator) DecoratorShort: str = DecoratorStr.rsplit('.', 1)[-1] if DecoratorShort == 'NoVTable': ClassPrefix = 'novirtual ' elif DecoratorShort == 'CVTable': ClassPrefix = 'virtual ' elif 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}{ClassPrefix}class {node.name}{ClassTypeParamStr}({", ".join(BaseStrs)}):') else: lines.append(f'{IndentStr}{ClassPrefix}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}{ClassPrefix}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 # 检查是否是宏变量(Markers 标记 define;CDefine 仅兼容旧文本) _MemberMarkers: tuple[str, ...] = ( tuple(getattr(item, 'Markers', None) or ()) + tuple(getattr(item.annotation, 'Markers', None) or ())) if 'define' in _MemberMarkers or 'CDefine' in TypeStr: # 检查宏变量是否应该被排除 if isinstance(item.target, ast.Name): MacroName: str = item.target.id if PythonToStubConverter._ShouldExcludeMacro(MacroName): continue # 跳过该宏 # 统一输出新语法 `define NAME[: 类型] = 值` ImplicitType: bool = ( isinstance(item.annotation, ast.Name) and item.annotation.id == 'CDefine') TypePart: str = '' if ImplicitType else f': {TypeStr}' if item.value is not None: try: ValueStr: str = ast.unparse(item.value) except Exception: ValueStr = '' lines.append(f'{MemberIndent}define {item.target.id}{TypePart} = {ValueStr}') else: lines.append(f'{MemberIndent}define {item.target.id}{TypePart} = 0') 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'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'CArray[{elem_type_str}, {init_len}]' else: FinalTypeStr = f'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): # 处理宏调用,如 CUndef(...) / CPragma(...) ExprStr: str = PythonToStubConverter._GetExprString(item.value) if ExprStr: lines.append(f'{IndentStr} {ExprStr}') elif isinstance(item, ast.If): # 处理 cif 宏条件(`cif ...:` / `celif ...:` / `celse:`) 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('CIfndef(') or line.strip().startswith('CEndif()'): continue if ': 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 # 检查是否是宏函数:新语法由解析器写 node.Define;旧 `-> CDefine` # 返回注解仅作兼容回落。 ReturnMarkers: tuple[str, ...] = getattr(node.returns, 'Markers', None) or () if node.returns else () is_macro_func: bool = bool(getattr(node, 'Define', False)) or 'define' in ReturnMarkers if node.returns: ReturnType: str = PythonToStubConverter._GetTypeString(node.returns) if '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 Copied: list[str] = [SourceLines[i] for i in range(StartLine, min(EndLine, len(SourceLines)))] # 丢弃尾随空行:解析器的 end_lineno 会被紧随的空行 NL 顶到下一行 # (vast Parser._ParseSuite 单行套件分支返回前 `_SkipNL()` 吞掉空行 NL, # 调用方 _FixLoc 取 _Prev() 即该 NL)。若不丢弃,`define f(...): return x` # 这类内联体宏末尾会多出空白行,使下方"末行是否已有内联体"判据落空, # 凭空补一行 ` pass` → 存根缩进非法(hashlib.__sha512 第 29 行)。 while Copied and not Copied[-1].strip(): Copied.pop() lines.extend(Copied) if lines and lines[-1].strip() and not lines[-1].rstrip().endswith(':'): pass elif lines: lines.append(f'{IndentStr} pass') else: # 源区间为空(理论不可达):产出合法占位,绝不落出 ` pass` 这类非法缩进 lines.append(f'{IndentStr}pass') return lines # 处理函数装饰器:存储类/一次性初始化还原为关键字前缀。 # 新语法的 `extern def` / `export def` / `static def` 由解析器写入 # FunctionDef.Extern/Export/Static;旧装饰器形态仅作兼容。 IsExternFunc: bool = bool(getattr(node, 'Extern', False)) IsExportFunc: bool = bool(getattr(node, 'Export', False)) IsStaticFunc: bool = bool(getattr(node, 'Static', False)) IsTlsFunc: bool = False IsVirtualFunc: bool = False IsNoVirtualFunc: bool = False PendingDecorators: list[str] = [] for decorator in node.decorator_list: DecoratorStr: str = PythonToStubConverter._GetDecoratorString(decorator) DecoratorShort: str = DecoratorStr.rsplit('.', 1)[-1] if DecoratorShort == 'TLS': IsTlsFunc = True elif DecoratorShort == 'CStatic': IsStaticFunc = True elif DecoratorShort == 'CExport': IsExportFunc = True elif DecoratorShort == 'CExtern': IsExternFunc = True elif DecoratorShort == 'CVTable': IsVirtualFunc = True elif DecoratorShort == 'NoVTable': IsNoVirtualFunc = True elif DecoratorStr: PendingDecorators.append(DecoratorStr) FuncPrefix: str = '' if IsExternFunc: FuncPrefix = 'extern ' elif IsExportFunc: FuncPrefix = 'export ' elif IsStaticFunc: FuncPrefix = 'static ' elif IsTlsFunc: FuncPrefix = 'tls ' elif IsVirtualFunc: FuncPrefix = 'virtual ' elif IsNoVirtualFunc: FuncPrefix = 'novirtual ' for DecoratorStr in PendingDecorators: lines.append(f'{IndentStr}@{DecoratorStr}') # 构建参数列表 # 治本修复:原实现只遍历 node.args.args,完全忽略 node.args.defaults, # 导致带默认值的参数(如 category: str = "")在 .pyi 中丢失默认值。 # 下游 DeclarationGenerator 据此生成 stub.ll 时,会因缺少默认参数而 # 生成参数数量不足的 declare,进而造成调用方 ABI 不匹配崩溃。 # 修复:按 Python AST 语义,defaults 从右向左对应 args 末尾的参数, # 对有默认值的参数附加 '= '。 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) # 返回类型 - 保持原始类型(不添加 State) if node.returns: ReturnType = PythonToStubConverter._GetTypeString(node.returns) else: ReturnType = 'int' 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}{FuncPrefix}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) Markers: tuple[str, ...] = (tuple(getattr(node, 'Markers', None) or ()) + tuple(getattr(node.annotation, 'Markers', None) or ())) # 检查是否是宏变量(优先读 Markers 的 define;CDefine 仅兼容旧文本) if 'CDefine' in TypeStr or 'define' in Markers: # 检查宏变量是否应该被排除 if PythonToStubConverter._ShouldExcludeMacro(VarName): return lines # 返回空列表,排除该宏 # 统一输出新语法 `define NAME[: 类型] = 值`(旧写法在此归一化) ImplicitType: bool = ( isinstance(node.annotation, ast.Name) and node.annotation.id == 'CDefine') TypePart: str = '' if ImplicitType else f': {TypeStr}' if node.value is not None: try: ValueStr: str = ast.unparse(node.value) except Exception: ValueStr = '' lines.append(f'define {VarName}{TypePart} = {ValueStr}') else: # 无初值的宏(C 头 `#define FOO`)在新语法下补 0 占位 lines.append(f'define {VarName}{TypePart} = 0') return lines # 非宏变量:不加 State # 含有 typedef 的变量不加 extern,普通变量需要加 extern has_typedef: bool = 'CTypedef' in TypeStr or 'typedef' in Markers if has_typedef: ValueNode: ast.AST | None = node.value if node.value is not None else node.annotation ValueTypeStr: str = PythonToStubConverter._GetTypeString(ValueNode) lines.append(f'typedef {VarName} = {ValueTypeStr}') elif 'extern' in Markers: lines.append(f'extern {VarName}: {TypeStr}') elif 'static' in Markers: lines.append(f'static {VarName}: {TypeStr}') elif 'export' in Markers: lines.append(f'export {VarName}: {TypeStr}') else: 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}: int') return lines @staticmethod def _InferTypeFromValue(value: ast.AST) -> str | None: """从值推断内建类型名(新语法:int/float/char*/rawptr)""" if isinstance(value, ast.Constant): if isinstance(value.value, bool): return 'int' elif isinstance(value.value, int): return 'int' elif isinstance(value.value, float): return 'double' elif isinstance(value.value, str): return 'char*' elif isinstance(value, ast.List): return 'rawptr' elif isinstance(value, ast.Dict): return 'rawptr' elif isinstance(value, ast.Name): return 'int' elif isinstance(value, ast.BinOp): left_type: str | None = PythonToStubConverter._InferTypeFromValue(value.left) if left_type: return left_type return 'int' elif isinstance(value, ast.Call): # 函数调用返回值,假设为指针类型 if isinstance(value.func, ast.Name): func_name: str = value.func.id if func_name in ('malloc', 'cast'): return 'rawptr' return 'rawptr' return None @staticmethod def _GetTypeString(annotation: ast.AST) -> str: if annotation is None: return 'void' Core: str = PythonToStubConverter._GetTypeStringCore(annotation) # 语句级修饰(typedef/define/static/export/extern/state)由调用方在 # 声明前缀输出,不能混进类型文本;const/volatile/inline 等类型修饰 # 以及 Attributes/ByteOrder/BitWidth 按 Unparser 同一口径还原。 _StatementMarkers: frozenset[str] = frozenset( ('typedef', 'define', 'static', 'export', 'extern', 'state')) Markers: tuple[str, ...] = tuple(getattr(annotation, 'Markers', None) or ()) PrefixParts: list[str] = [ m for m in Markers if m not in _StatementMarkers] Attributes: tuple[str, ...] = tuple(getattr(annotation, 'Attributes', None) or ()) if Attributes: PrefixParts.append('__attribute__((' + ', '.join(Attributes) + '))') ByteOrder: str = getattr(annotation, 'ByteOrder', '') or '' if ByteOrder == 'big': PrefixParts.append('BigEndian') elif ByteOrder == 'little': PrefixParts.append('LittleEndian') BitWidth: int = getattr(annotation, 'BitWidth', 0) or 0 if BitWidth: PrefixParts.append(f'Bit({BitWidth})') Prefix: str = ''.join(f'{m} ' for m in PrefixParts) return Prefix + Core @staticmethod def _GetTypeStringCore(annotation: ast.AST) -> str: """获取类型字符串 支持集与 VPC(ViperC/App/lib/Stub/StubGen/Converter.vp 的 _PyiTypeStr) 等价:Name/Attribute/Subscript/BinOp/UnaryOp/Compare/BoolOp/Constant/ List/Call(VPC 另支持裸 Tuple,本侧仅在 Subscript.slice 位置接受 Tuple)。 未支持的节点种类与运算符在此 fail-fast(不自造 'CVoid' 回落)。 """ # 无注解(None):调用方未提供注解,按无返回类型处理(新语法 void); # 这不是"未知节点"——未知节点走函数尾 fail-fast。 if annotation is None: return 'void' if isinstance(annotation, ast.Name): return _STUB_TYPE_ALIASES.get(annotation.id, annotation.id) elif isinstance(annotation, ast.Attribute): if isinstance(annotation.value, ast.Name): if annotation.value.id in ('t', 'c'): return _STUB_TYPE_ALIASES.get(annotation.attr, annotation.attr) return f'{annotation.value.id}.{annotation.attr}' else: # 处理嵌套属性访问,如 attr.packed ParentStr: str = PythonToStubConverter._GetTypeString(annotation.value) return f'{ParentStr}.{annotation.attr}' elif isinstance(annotation, ast.Subscript): # 值引用标记按节点原名判定(不能被 _STUB_TYPE_ALIASES 提前归一成 # rawptr,否则 `&T` / `&temp T` 会被误写成 `T*`)。 ValueName: str = '' if isinstance(annotation.value, ast.Name): ValueName = annotation.value.id elif isinstance(annotation.value, ast.Attribute): ValueName = annotation.value.attr if ValueName == 'CRef': return '&' + PythonToStubConverter._GetTypeString(annotation.slice) if ValueName == 'CTempRef': return '&temp ' + PythonToStubConverter._GetTypeString(annotation.slice) 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) if base in ('CPtr', 'rawptr'): return PythonToStubConverter._GetTypeString(annotation.slice) + '*' return f'{base}[{SliceStr}]' elif isinstance(annotation, ast.BinOp): # 类型注解不再接受 `T1 | T2` 联合(包括旧的 CExtern | T / # CDefine | T / T | CRef):一律 fail-fast,不做任何合并。 if isinstance(annotation.op, ast.BitOr): Line: int = getattr(annotation, 'lineno', 0) or 0 raise TypeError( f"[TPC] 不支持类型联合 'T1 | T2'(第 {Line} 行);" f"类型注解只能是单一类型:指针请写 `T*` 或 `rawptr[T]`," f"修饰符请用关键字(const/volatile/static/export/extern)") LeftStr: str = PythonToStubConverter._GetTypeString(annotation.left) RightStr: str = PythonToStubConverter._GetTypeString(annotation.right) if 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}' # 位与/右移:条件编译实参(如 cif MASK_BYTE & 0x0F、 # cif (MAX_SIZE >> SHIFT_BITS) > 0)。两运算符必须与 VPC 的 # _PyiBinOpOpText 保持一致,否则同一份源码两侧写出的 vpi 文本不同。 elif isinstance(annotation.op, ast.BitAnd): return f'{LeftStr} & {RightStr}' elif isinstance(annotation.op, ast.RShift): return f'{LeftStr} >> {RightStr}' # 其余二元运算符(LShift/BitXor/FloorDiv/Pow/MatMult 等)不支持: # 落到函数尾 fail-fast(与 VPC 同语义) 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.BoolOp): # 布尔运算:条件编译实参(如 cif ENABLED and MAX_SIZE > 0)。 # 文本与 VPC 的 BoolOp 分支一致:and → ' and '、or → ' or '。 Joiner: str = ' or ' if isinstance(annotation.op, ast.Or) else ' and ' return Joiner.join([PythonToStubConverter._GetTypeString(v) for v in annotation.values]) 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): # 处理函数调用形式的类型注解,如 Postdefinition(xxa) 或 callable(CVoid, app_id = 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})' # 未知/未支持的注解节点 fail-fast(与 VPC StubGen 的 _PyiTypeStr 同语义、 # 同文案、同类型):静默回落 'CVoid' 会把错误类型写进 stub 签名——实例: # 源里的 Callable[[], int] 曾写成 Callable[int, int](见 # ViperApps/ViperOS/VKernel/__vpcbuild__/temp/0b/0bb176432facf634.vpi:23), # 下游按错误签名解析且无任何提示。 # 归因信息:节点 kind + 行号;文件归因由调用方在日志里带上 # (Generator.GenerateStub → "Failed to process <文件>", # Phase1Generator → "处理 include 文件失败 <相对路径>")。 KindName: str = type(annotation).__name__ LineNo: int = getattr(annotation, 'lineno', 0) raise TypeError(f'vpi 类型注解节点类型未知: {KindName} (line {LineNo})') @staticmethod def _ExtractPostdefClass(annotation: ast.AST) -> str | None: """从 Postdefinition 类型注解中提取类名 例如:Postdefinition(__buddy_system) -> '__buddy_system' Postdefinition(xxx) | CTypedef -> 'xxx' """ # 类型注解中的 BinOp(BitOr) 联合已废止:fail-fast,不做合并。 if isinstance(annotation, ast.BinOp): if isinstance(annotation.op, ast.BitOr): Line: int = getattr(annotation, 'lineno', 0) or 0 raise TypeError( f"[TPC] 不支持类型联合 'T1 | T2'(第 {Line} 行);" f"类型注解只能是单一类型") return None # 处理函数调用 (如: Postdefinition(__buddy_system)) if isinstance(annotation, ast.Call): FuncStr: str = PythonToStubConverter._GetTypeString(annotation.func) if FuncStr == '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_LINE_COMMENT.search(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_LINE_COMMENT.search(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): # 旧 `t.X` / `c.X` 前缀只作输入兼容:stub 输出按裸名书写。 if decorator.value.id in ('t', 'c'): return decorator.attr return f'{decorator.value.id}.{decorator.attr}' return decorator.attr elif isinstance(decorator, ast.Call): # 处理带参数的装饰器,如 @Attribute(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): # 处理函数调用,如 CUndef(...), CPragma(...) # (旧的 CIf/CElif/CElse 已删除:cif/celif/celse 由 _ProcessIfMacro 渲染) FuncStr: str = PythonToStubConverter._GetDecoratorString(expr.func) if FuncStr and ('CEndif' 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 _RestoreDefinedMarkers(node: ast.AST) -> ast.AST: """把内部标记 @CIfdef(N) 递归还原为 defined(N) 调用(供 stub 文本渲染)。 cif/celif 条件的 desugar 会把 defined(NAME) 变成 @CIfdef(NAME) 标记调用; 生成 .vpi 文本时必须还原为用户可写的 defined(NAME),否则存根不可再解析。 """ if isinstance(node, ast.Call): F: str = PythonToStubConverter._GetDecoratorString(node.func) short: str = F.rsplit('.', 1)[-1] if F else '' if short in ('@CIfdef', '@CIfndef') and len(node.args) == 1: inner: ast.AST = PythonToStubConverter._RestoreDefinedMarkers(node.args[0]) call: ast.Call = ast.Call( func=ast.Name(id='defined', ctx=ast.Load()), args=[inner], keywords=[]) if short == '@CIfndef': return ast.UnaryOp(op=ast.Not(), operand=call) return call for field, value in ast.iter_fields(node): if isinstance(value, list): for i, item in enumerate(value): if isinstance(item, ast.AST): value[i] = PythonToStubConverter._RestoreDefinedMarkers(item) elif isinstance(value, ast.AST): setattr(node, field, PythonToStubConverter._RestoreDefinedMarkers(value)) return node @staticmethod def _CifConditionText(test: ast.AST) -> str | None: """cif/celif 条件文本:内部标记 @CIf(EXPR) → EXPR;@CIfdef(N) → defined(N)。 返回 None 表示 test 不是 cif 族标记(普通 if 的运行时条件)。 """ if isinstance(test, ast.Call): F: str = PythonToStubConverter._GetDecoratorString(test.func) short: str = F.rsplit('.', 1)[-1] if F else '' if short == '@CIf' and len(test.args) == 1: inner: ast.AST = PythonToStubConverter._RestoreDefinedMarkers(test.args[0]) return PythonToStubConverter._GetTypeString(inner) if short in ('@CIfdef', '@CIfndef') and len(test.args) == 1: return PythonToStubConverter._GetTypeString( PythonToStubConverter._RestoreDefinedMarkers(test)) return None @staticmethod def _get_if_macro_string(node: ast.If, indent: int = 1) -> str: """获取类内部的 cif 宏字符串(如 `cif :`,含 celif/celse 链)。""" IndentStr: str = ' ' * indent InnerIndent: str = ' ' * (indent + 1) cond: str | None = PythonToStubConverter._CifConditionText(node.test) if cond is None: return '' def _body_lines(body: list, ind: str) -> list[str]: out: list[str] = [] for item in body: if isinstance(item, ast.AnnAssign): TypeStr: str = PythonToStubConverter._GetTypeString(item.annotation) out.append(f'{ind}{item.target.id}: {TypeStr}') elif isinstance(item, ast.Expr): ExprStr: str = PythonToStubConverter._GetExprString(item.value) if ExprStr: out.append(f'{ind}{ExprStr}') return out result: list[str] = [f'{IndentStr}cif {cond}:'] result.extend(_body_lines(node.body, InnerIndent)) current: ast.If = node while current.orelse: if len(current.orelse) == 1 and isinstance(current.orelse[0], ast.If): elif_node: ast.If = current.orelse[0] econd: str | None = PythonToStubConverter._CifConditionText(elif_node.test) if econd is None: break result.append(f'{IndentStr}celif {econd}:') result.extend(_body_lines(elif_node.body, InnerIndent)) current = elif_node continue result.append(f'{IndentStr}celse:') result.extend(_body_lines(current.orelse, InnerIndent)) break return '\n'.join(result) @staticmethod def _GetModuleIfMacroString(node: ast.If) -> str: """获取模块级别的 cif 宏字符串(如 `cif FF_MULTI_PARTITION:`)。""" return PythonToStubConverter._ProcessIfMacro(node, indent=0) @staticmethod def _ProcessIfMacro(node: ast.If, indent: int = 0) -> str: """处理 cif 宏节点,支持 celif / celse 链。""" IndentStr: str = ' ' * indent cond: str | None = PythonToStubConverter._CifConditionText(node.test) if cond is None: return '' result: list[str] = [f'{IndentStr}cif {cond}:'] for item in node.body: result.extend(PythonToStubConverter._ProcessMacroBodyItem(item, indent + 1)) current: ast.If = node while current.orelse: if len(current.orelse) == 1 and isinstance(current.orelse[0], ast.If): # celif 情况 elif_node: ast.If = current.orelse[0] econd: str | None = PythonToStubConverter._CifConditionText(elif_node.test) if econd is None: break result.append(f'{IndentStr}celif {econd}:') for item in elif_node.body: result.extend(PythonToStubConverter._ProcessMacroBodyItem(item, indent + 1)) current = elif_node continue # celse 情况 result.append(f'{IndentStr}celse:') for item in current.orelse: result.extend(PythonToStubConverter._ProcessMacroBodyItem(item, indent + 1)) break return '\n'.join(result) @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