Files
TransPyC/lib/StubGen/Converter.py
T
TermiNexus 0bded58999 refactor(type): 全面清理旧联合与 t/c 标记判断
审计并修复 23 个文件:类型注解中的 T1 | T2 一律 fail-fast;typedef/define/export/extern/static/const/volatile/attribute 优先读取 Markers/Attributes/ByteOrder/BitWidth;指针统一 CPtr/rawptr/CRef/CTempRef/CArrayPtr/CAutoPtr Subscript;stub 输出统一新语法。
2026-10-18 18:01:20 +08:00

1223 lines
61 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
# 提取 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 的报错/回退诊断才带得上模块名(否则记为 <unknown>)
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 末尾的参数,
# 对有默认值的参数附加 '= <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)
# 返回类型 - 保持原始类型(不添加 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 <cond>:`,含 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