修复了大量存在的问题,增加了假鸭子类型等等机制

This commit is contained in:
2026-06-25 14:49:46 +08:00
parent 19f2787db0
commit d88d11b646
827 changed files with 32617 additions and 18316 deletions

View File

@@ -1,214 +1,240 @@
import os
import sys
import shutil
from lib.Projectrans.Phase1Generator import Phase1Generator
from lib.Projectrans.Phase2Translator import Phase2Translator
def save_sha1_map(temp_dir: str, sha1_map: dict[str, str]):
"""将 SHA1 映射表保存到文件"""
map_path = os.path.join(temp_dir, '_sha1_map.txt')
with open(map_path, 'w', encoding='utf-8') as f:
for sha1, rel in sorted(sha1_map.items()):
f.write(f"{sha1}:{rel}\n")
def Load_sha1_map(temp_dir: str) -> dict[str, str]:
"""从文件加载 SHA1 映射表"""
map_path = os.path.join(temp_dir, '_sha1_map.txt')
result = {}
if os.path.exists(map_path):
with open(map_path, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if ':' in line:
sha1, rel = line.split(':', 1)
result[sha1] = rel
return result
def Load_project_config(project_file: str) -> dict:
"""从 project.json 加载配置"""
import json
with open(project_file, 'r', encoding='utf-8') as f:
return json.load(f)
def resolve_paths(config: dict, project_file: str) -> dict:
"""将配置中的相对路径转换为绝对路径"""
project_dir = os.path.dirname(os.path.abspath(project_file))
result = dict(config)
def resolve(p):
if p is None:
return None
if os.path.isabs(p):
return p
return os.path.normpath(os.path.join(project_dir, p))
if 'source_dir' in result:
result['source_dir'] = resolve(result.get('source_dir'))
elif 'sources' in result:
resolved_sources = []
for src in result['sources']:
if '*' in src or '?' in src:
import glob as glob_module
for matched in glob_module.glob(os.path.join(project_dir, src), recursive=True):
if os.path.isfile(matched):
resolved_sources.append(matched)
else:
resolved_sources.append(resolve(src))
result['sources'] = [s for s in resolved_sources if os.path.isfile(s)]
result['temp_dir'] = resolve(config.get('temp_dir'))
result['output_dir'] = resolve(config.get('output_dir'))
if 'includes' in result:
result['includes'] = [resolve(inc) for inc in result['includes']]
result['_project_dir'] = project_dir
return result
def main():
import argparse
parser = argparse.ArgumentParser(
description='TransPyC 工程翻译器 - 基于 project.json 的两阶段翻译',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
示例:
# 从 project.json 读取配置并执行(默认查找当前目录)
python ProjectTrans.py
# 指定 project.json 文件
python ProjectTrans.py --project ./TestProject/project.json
# 使用命令行参数覆盖 project.json 配置
python ProjectTrans.py --src ./src --temp ./decl --output ./out --phase all
# 仅运行阶段一
python ProjectTrans.py --phase 1
# 仅运行阶段二
python ProjectTrans.py --phase 2
# 清理临时目录
python ProjectTrans.py --clean
'''
)
parser.add_argument('--project', help='project.json 路径(默认查找当前目录)')
parser.add_argument('--src', help='源文件目录(覆盖 project.json')
parser.add_argument('--temp', help='声明接口临时目录(覆盖 project.json')
parser.add_argument('--output', help='输出目录(覆盖 project.json')
parser.add_argument('--phase', choices=['1', '2', 'all'], help='阶段: 1=生成声明, 2=翻译+编译, all=全部')
parser.add_argument('--cc', help='LLVM 编译器命令(覆盖 project.json')
parser.add_argument('--clean', action='store_true', help='清理 output 和 temp 目录')
args = parser.parse_args()
project_file = args.project
if not project_file:
candidates = ['project.json', './project.json']
for c in candidates:
if os.path.exists(c):
project_file = c
break
if project_file and os.path.exists(project_file):
print(f"[配置] 读取: {project_file}")
config = Load_project_config(project_file)
project_dir = os.path.dirname(os.path.abspath(project_file))
print(f"[配置] 项目目录: {project_dir}")
else:
print("[错误] 未找到 project.json请使用 --project 指定")
sys.exit(1)
resolved = resolve_paths(config, project_file)
src = args.src or resolved.get('source_dir') or resolved.get('sources')
temp_dir = args.temp or resolved.get('temp_dir')
output_dir = args.output or resolved.get('output_dir')
phase = args.phase or 'all'
cc_cmd = args.cc or resolved.get('compiler', {}).get('cmd', 'llc')
cc_flags = resolved.get('compiler', {}).get('flags', ['-filetype=obj'])
slice_level = resolved.get('options', {}).get('slice_level', 3)
project_include_dirs = resolved.get('includes', [])
target_triple = resolved.get('target', {}).get('triple')
target_datalayout = resolved.get('target', {}).get('datalayout')
if isinstance(src, list):
if len(src) == 1 and os.path.isdir(src[0]):
src = src[0]
else:
src = src[0] if len(src) == 1 else os.path.dirname(os.path.commonpath(src)) if all(os.path.exists(p) for p in src) else src[0]
print(f"[配置] 源目录: {src}")
print(f"[配置] 临时目录: {temp_dir}")
print(f"[配置] 输出目录: {output_dir}")
print(f"[配置] 阶段: {phase}")
print(f"[配置] 编译器: {cc_cmd} {' '.join(cc_flags)}")
if target_triple:
print(f"[配置] 目标三元组: {target_triple}")
if target_datalayout:
print(f"[配置] 数据布局: {target_datalayout}")
if args.clean:
if output_dir and os.path.exists(output_dir):
for f in os.listdir(output_dir):
fpath = os.path.join(output_dir, f)
try:
if os.path.isfile(fpath) or os.path.islink(fpath):
os.remove(fpath)
elif os.path.isdir(fpath):
shutil.rmtree(fpath, ignore_errors=True)
except:
pass
print(f"[清理] output: 已清空 {output_dir}")
if temp_dir and os.path.exists(temp_dir):
for f in os.listdir(temp_dir):
fpath = os.path.join(temp_dir, f)
try:
if os.path.isfile(fpath) or os.path.islink(fpath):
os.remove(fpath)
elif os.path.isdir(fpath):
shutil.rmtree(fpath, ignore_errors=True)
except:
pass
print(f"[清理] temp: 已清空 {temp_dir}")
print("[清理] 完成")
if (not phase or phase == 'all') and not args.project:
return
if phase in ('1', 'all'):
if isinstance(src, list):
print("[错误] 阶段一需要指定源目录,不支持文件列表")
sys.exit(1)
all_include_dirs = list(project_include_dirs) if project_include_dirs else []
for d in Phase2Translator.INCLUDE_DIRS:
if os.path.isdir(d):
d = os.path.abspath(d)
if d not in [os.path.abspath(x) for x in all_include_dirs]:
all_include_dirs.append(d)
gen = Phase1Generator(src, temp_dir, include_dirs=all_include_dirs, target_triple=target_triple, target_datalayout=target_datalayout)
gen.run()
save_sha1_map(temp_dir, gen.sha1_map)
if phase in ('2', 'all'):
if not output_dir:
print("[错误] 阶段二需要指定 --output 目录")
sys.exit(1)
if not os.path.exists(temp_dir):
print(f"[错误] 声明目录不存在,请先运行阶段一: {temp_dir}")
sys.exit(1)
if isinstance(src, list):
print("[错误] 阶段二需要指定源目录,不支持文件列表")
sys.exit(1)
linker_cmd = resolved.get('linker', {}).get('cmd')
linker_flags = resolved.get('linker', {}).get('flags', [])
linker_output = resolved.get('linker', {}).get('output')
target_triple = resolved.get('target', {}).get('triple')
target_datalayout = resolved.get('target', {}).get('datalayout')
startup = resolved.get('options', {}).get('startup')
trans = Phase2Translator(src, temp_dir, output_dir, compile_cmd=cc_cmd, compile_flags=cc_flags, linker_cmd=linker_cmd, linker_flags=linker_flags, linker_output=linker_output, include_dirs=project_include_dirs, target_triple=target_triple, target_datalayout=target_datalayout, startup=startup)
trans.sha1_map = Load_sha1_map(temp_dir)
trans.slice_level = slice_level
trans.run()
from __future__ import annotations
import os
import sys
import json
import glob as glob_module
import argparse
import shutil
import subprocess
from typing import Any
from lib.core.VLogger import get_logger as _vlog
from lib.Projectrans.Phase1Generator import Phase1Generator
from lib.Projectrans.Phase2Translator import Phase2Translator
def save_sha1_map(temp_dir: str, sha1_map: dict[str, str]) -> None:
"""将 SHA1 映射表保存到文件"""
map_path: str = os.path.join(temp_dir, '_sha1_map.txt')
with open(map_path, 'w', encoding='utf-8') as f:
for sha1, rel in sorted(sha1_map.items()):
f.write(f"{sha1}:{rel}\n")
def Load_sha1_map(temp_dir: str) -> dict[str, str]:
"""从文件加载 SHA1 映射表"""
map_path: str = os.path.join(temp_dir, '_sha1_map.txt')
result: dict[str, str] = {}
if os.path.exists(map_path):
with open(map_path, 'r', encoding='utf-8') as f:
for line in f:
line: str = line.strip()
if ':' in line:
sha1: str
rel: str
sha1, rel = line.split(':', 1)
result[sha1] = rel
return result
def Load_project_config(project_file: str) -> dict[str, Any]:
"""从 project.json 加载配置"""
with open(project_file, 'r', encoding='utf-8') as f:
return json.load(f)
def resolve_paths(config: dict[str, Any], project_file: str) -> dict[str, Any]:
"""将配置中的相对路径转换为绝对路径"""
project_dir: str = os.path.dirname(os.path.abspath(project_file))
result: dict[str, Any] = dict(config)
def resolve(p: str | None) -> str | None:
if p is None:
return None
if os.path.isabs(p):
return p
return os.path.normpath(os.path.join(project_dir, p))
if 'source_dir' in result:
result['source_dir'] = resolve(result.get('source_dir'))
elif 'sources' in result:
resolved_sources: list[str] = []
for src in result['sources']:
if '*' in src or '?' in src:
for matched in glob_module.glob(os.path.join(project_dir, src), recursive=True):
if os.path.isfile(matched):
resolved_sources.append(matched)
else:
resolved_sources.append(resolve(src))
result['sources'] = [s for s in resolved_sources if os.path.isfile(s)]
result['temp_dir'] = resolve(config.get('temp_dir'))
result['output_dir'] = resolve(config.get('output_dir'))
if 'includes' in result:
result['includes'] = [resolve(inc) for inc in result['includes']]
result['_project_dir'] = project_dir
return result
def main() -> None:
parser: argparse.ArgumentParser = argparse.ArgumentParser(
description='TransPyC 工程翻译器 - 基于 project.json 的两阶段翻译',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
示例:
# 从 project.json 读取配置并执行(默认查找当前目录)
python ProjectTrans.py
# 指定 project.json 文件
python ProjectTrans.py --project ./TestProject/project.json
# 使用命令行参数覆盖 project.json 配置
python ProjectTrans.py --src ./src --temp ./decl --output ./out --phase all
# 仅运行阶段一
python ProjectTrans.py --phase 1
# 仅运行阶段二
python ProjectTrans.py --phase 2
# 清理临时目录
python ProjectTrans.py --clean
# 编译并立即执行
python Projectrans.py --project ./TestProject/project.json --clean --run
'''
)
parser.add_argument('--project', help='project.json 路径(默认查找当前目录)')
parser.add_argument('--src', help='源文件目录(覆盖 project.json')
parser.add_argument('--temp', help='声明接口临时目录(覆盖 project.json')
parser.add_argument('--output', help='输出目录(覆盖 project.json')
parser.add_argument('--phase', choices=['1', '2', 'all'], help='阶段: 1=生成声明, 2=翻译+编译, all=全部')
parser.add_argument('--cc', help='LLVM 编译器命令(覆盖 project.json')
parser.add_argument('--clean', action='store_true', help='清理 output 和 temp 目录')
parser.add_argument('--run', action='store_true', help='编译成功后立即执行生成的可执行文件')
args: argparse.Namespace = parser.parse_args()
project_file: str | None = args.project
if not project_file:
candidates: list[str] = ['project.json', './project.json']
for c in candidates:
if os.path.exists(c):
project_file = c
break
if project_file and os.path.exists(project_file):
_vlog().info(f"读取配置文件: {project_file}")
config: dict[str, Any] = Load_project_config(project_file)
project_dir: str = os.path.dirname(os.path.abspath(project_file))
_vlog().info(f"项目目录: {project_dir}")
else:
_vlog().error("未找到 project.json请使用 --project 指定")
sys.exit(1)
resolved: dict[str, Any] = resolve_paths(config, project_file)
src: str | list[str] | None = args.src or resolved.get('source_dir') or resolved.get('sources')
temp_dir: str | None = args.temp or resolved.get('temp_dir')
output_dir: str | None = args.output or resolved.get('output_dir')
phase: str = args.phase or 'all'
cc_cmd: str = args.cc or resolved.get('compiler', {}).get('cmd', 'llc')
cc_flags: list[str] = resolved.get('compiler', {}).get('flags', ['-filetype=obj'])
slice_level: int = resolved.get('options', {}).get('slice_level', 3)
project_include_dirs: list[str] = resolved.get('includes', [])
target_triple: str | None = resolved.get('target', {}).get('triple')
target_datalayout: str | None = resolved.get('target', {}).get('datalayout')
if isinstance(src, list):
if len(src) == 1 and os.path.isdir(src[0]):
src = src[0]
else:
src = src[0] if len(src) == 1 else os.path.dirname(os.path.commonpath(src)) if all(os.path.exists(p) for p in src) else src[0]
_vlog().info(f"源目录: {src}")
_vlog().info(f"临时目录: {temp_dir}")
_vlog().info(f"输出目录: {output_dir}")
_vlog().info(f"阶段: {phase}")
_vlog().info(f"编译器: {cc_cmd} {' '.join(cc_flags)}")
if target_triple:
_vlog().info(f"目标三元组: {target_triple}")
if target_datalayout:
_vlog().info(f"数据布局: {target_datalayout}")
if args.clean:
if output_dir and os.path.exists(output_dir):
for f in os.listdir(output_dir):
fpath: str = os.path.join(output_dir, f)
try:
if os.path.isfile(fpath) or os.path.islink(fpath):
os.remove(fpath)
elif os.path.isdir(fpath):
shutil.rmtree(fpath, ignore_errors=True)
except:
pass
_vlog().info(f"output: 已清空 {output_dir}")
if temp_dir and os.path.exists(temp_dir):
for f in os.listdir(temp_dir):
fpath: str = os.path.join(temp_dir, f)
try:
if os.path.isfile(fpath) or os.path.islink(fpath):
os.remove(fpath)
elif os.path.isdir(fpath):
shutil.rmtree(fpath, ignore_errors=True)
except:
pass
_vlog().info(f"temp: 已清空 {temp_dir}")
_vlog().info("清理完成")
if (not phase or phase == 'all') and not args.project:
return
if phase in ('1', 'all'):
if isinstance(src, list):
_vlog().error("阶段一需要指定源目录,不支持文件列表")
sys.exit(1)
all_include_dirs: list[str] = list(project_include_dirs) if project_include_dirs else []
for d in Phase2Translator.INCLUDE_DIRS:
if os.path.isdir(d):
d = os.path.abspath(d)
if d not in [os.path.abspath(x) for x in all_include_dirs]:
all_include_dirs.append(d)
gen: Phase1Generator = Phase1Generator(src, temp_dir, include_dirs=all_include_dirs, target_triple=target_triple, target_datalayout=target_datalayout)
gen.run()
save_sha1_map(temp_dir, gen.sha1_map)
if phase in ('2', 'all'):
if not output_dir:
_vlog().error("阶段二需要指定 --output 目录")
sys.exit(1)
if not os.path.exists(temp_dir):
_vlog().error(f"声明目录不存在,请先运行阶段一: {temp_dir}")
sys.exit(1)
if isinstance(src, list):
_vlog().error("阶段二需要指定源目录,不支持文件列表")
sys.exit(1)
linker_cmd: str | None = resolved.get('linker', {}).get('cmd')
linker_flags: list[str] = resolved.get('linker', {}).get('flags', [])
linker_output: str | None = resolved.get('linker', {}).get('output')
target_triple = resolved.get('target', {}).get('triple')
target_datalayout = resolved.get('target', {}).get('datalayout')
startup: str | None = resolved.get('options', {}).get('startup')
trans: Phase2Translator = Phase2Translator(src, temp_dir, output_dir, compile_cmd=cc_cmd, compile_flags=cc_flags, linker_cmd=linker_cmd, linker_flags=linker_flags, linker_output=linker_output, include_dirs=project_include_dirs, target_triple=target_triple, target_datalayout=target_datalayout, startup=startup)
trans.sha1_map = Load_sha1_map(temp_dir)
trans.slice_level = slice_level
trans.run()
if args.run and linker_output and not linker_output.endswith('.bin'):
exe_path: str = os.path.join(output_dir, linker_output)
if os.path.isfile(exe_path):
_vlog().info(f"\n[运行] 执行: {exe_path}")
try:
result: subprocess.CompletedProcess = subprocess.run([exe_path], cwd=output_dir)
_vlog().info(f"[运行] 退出码: {result.returncode}")
except Exception as e:
_vlog().error(f"[运行] 执行失败: {e}")
sys.exit(1)
else:
_vlog().error(f"[运行] 找不到可执行文件: {exe_path}")
sys.exit(1)

View File

@@ -1,45 +1,78 @@
from __future__ import annotations
import ast
import re
from typing import List
import llvmlite.ir as ir
from lib.includes import t
from lib.includes.t import CTypeRegistry
from lib.core.SymbolUtils import IsListAnnotation, AnnotationContainsName
class DeclarationGenerator:
"""从 .pyi AST 生成纯 LLVM IR 声明(不使用字符串操作)"""
def __init__(self, struct_names=None, enum_names=None, module_sha1=None, target_triple=None, target_datalayout=None, struct_sha1_map=None, exception_names=None, typedef_map=None):
import llvmlite.ir as ir
self.ir = ir
self.module = None
self.builder = None
self._DefineConstants = {}
self.struct_names = struct_names or set()
self.enum_names = enum_names or set()
self.module_sha1 = module_sha1
self.struct_sha1_map = struct_sha1_map or {}
self.exception_names = exception_names or set()
self.target_triple = target_triple or "x86_64-none-elf"
self.target_datalayout = target_datalayout or "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
self.typedef_map = typedef_map or {}
# 匹配 LLVM 数组类型:[N x elem_type]
_ARRAY_TYPE_RE: re.Pattern = re.compile(r'^\[(\d+)\s+x\s+(.+)\]$')
@staticmethod
def _decay_array_to_ptr(type_str: str) -> str:
"""C 语言中数组参数退化为指针:[N x elem_type] → elem_type*"""
m = DeclarationGenerator._ARRAY_TYPE_RE.match(type_str)
if m:
return f'{m.group(2)}*'
return type_str
def __init__(self, struct_names: set[str] | None = None, enum_names: set[str] | None = None, module_sha1: str | None = None, target_triple: str | None = None, target_datalayout: str | None = None, struct_sha1_map: dict[str, str] | None = None, exception_names: set[str] | None = None, typedef_map: dict[str, ast.AST] | None = None) -> None:
self.ir: ir.Module = ir
self.module: ir.Module | None = None
self.builder: ir.IRBuilder | None = None
self._DefineConstants: dict[str, int | str] = {}
self.struct_names: set[str] = struct_names or set()
self.enum_names: set[str] = enum_names or set()
self.module_sha1: str | None = module_sha1
self.struct_sha1_map: dict[str, str] = struct_sha1_map or {}
self.exception_names: set[str] = exception_names or set()
self.target_triple: str = target_triple or "x86_64-none-elf"
self.target_datalayout: str = target_datalayout or "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
self.typedef_map: dict[str, ast.AST] = typedef_map or {}
self._pyi_tree: ast.Module | None = None
self._t_alias_map: dict[str, type] = {}
t.configure_platform(self.target_triple)
def generate(self, pyi_content: str, src_path: str) -> str:
import ast
tree = ast.parse(pyi_content)
tree: ast.Module = ast.parse(pyi_content)
self._DefineConstants = {}
self._DefineConstants: dict[str, int | str] = {}
self._pyi_tree = tree
global_typedef_map = self.typedef_map
self.typedef_map = {}
global_typedef_map: dict[str, ast.AST] = self.typedef_map
self.typedef_map: dict[str, ast.AST] = {}
if global_typedef_map:
self.typedef_map.update(global_typedef_map)
# 构建 t/c 类型别名映射from t import CInt as myint
self._t_alias_map: dict[str, type] = {}
for node in ast.iter_child_nodes(tree):
if isinstance(node, ast.ImportFrom):
if node.module in ('t', 'c'):
for alias in node.names:
name: str = alias.name
asname: str = alias.asname or name
if node.module == 't':
t_cls: type | None = getattr(t, name, None)
if isinstance(t_cls, type) and issubclass(t_cls, t.CType):
self._t_alias_map[asname] = t_cls
for node in ast.iter_child_nodes(tree):
if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
var_name = node.target.id
var_name: str = node.target.id
if node.value and isinstance(node.value, ast.Constant):
self._DefineConstants[var_name] = node.value.value
elif node.value and isinstance(node.value, ast.Name):
self._DefineConstants[var_name] = node.value.id
is_typedef = False
is_typedef: bool = False
if isinstance(node.annotation, ast.Attribute) and hasattr(node.annotation, 'attr') and node.annotation.attr == 'CTypedef':
is_typedef = True
elif isinstance(node.annotation, ast.Name) and node.annotation.id == 'CTypedef':
@@ -52,7 +85,7 @@ class DeclarationGenerator:
elif isinstance(node.annotation, ast.BinOp) and node.annotation.right:
self.typedef_map[var_name] = node.annotation.right
lines = []
lines: list[str] = []
lines.append('; ModuleID = "transpyc_decl"')
lines.append(f'target triple = "{self.target_triple}"')
lines.append(f'target datalayout = "{self.target_datalayout}"')
@@ -62,32 +95,32 @@ class DeclarationGenerator:
if isinstance(node, ast.FunctionDef):
if hasattr(node, 'type_params') and node.type_params:
continue
decl = self._generate_func_decl(node)
decl: str | None = self._generate_func_decl(node)
if decl:
lines.append(decl)
elif isinstance(node, ast.AnnAssign):
decl = self._generate_global_decl(node)
decl: str | None = self._generate_global_decl(node)
if decl:
lines.append(decl)
elif isinstance(node, ast.Assign):
decl = self._generate_global_assign_decl(node)
decl: str | None = self._generate_global_assign_decl(node)
if decl:
lines.append(decl)
elif isinstance(node, ast.ClassDef):
if hasattr(node, 'type_params') and node.type_params:
continue
decls = self._generate_class_decl(node)
decls: list[str] = self._generate_class_decl(node)
lines.extend(decls)
return '\n'.join(lines)
def _generate_func_decl(self, node: ast.FunctionDef) -> str:
def _generate_func_decl(self, node: ast.FunctionDef) -> str | None:
"""生成函数声明"""
func_name = node.name
is_export = self._is_export_func(node)
func_name: str = node.name
is_export: bool = self._is_export_func(node)
if self.module_sha1 and not is_export:
func_name = f"{self.module_sha1}.{func_name}"
CReturnTypes = []
CReturnTypes: list[ast.AST] = []
if node.decorator_list:
for decorator in node.decorator_list:
if isinstance(decorator, ast.Call) and isinstance(decorator.func, ast.Attribute):
@@ -96,14 +129,15 @@ class DeclarationGenerator:
CReturnTypes.append(arg)
if node.returns:
if isinstance(node.returns, ast.Subscript) and isinstance(node.returns.value, ast.Name) and node.returns.value.id == 'tuple':
slice_node = node.returns.slice
slice_node: ast.AST = node.returns.slice
if isinstance(slice_node, ast.Tuple):
for elt in slice_node.elts:
CReturnTypes.append(elt)
else:
CReturnTypes.append(slice_node)
ret_type: str
if CReturnTypes:
elem_types = [self._get_type_str(rt, embedded=True) for rt in CReturnTypes]
elem_types: list[str] = [self._get_type_str(rt, embedded=True) for rt in CReturnTypes]
ret_type = '{ ' + ', '.join(elem_types) + ' }'
else:
ret_type = self._get_type_str(node.returns)
@@ -113,16 +147,19 @@ class DeclarationGenerator:
if node.returns is None:
ret_type = 'void'
params = []
params: list[str] = []
for arg in node.args.args:
arg_type: str
if arg.annotation:
arg_type = self._get_type_str(arg.annotation)
else:
arg_type = 'i8*'
# C 语言中数组参数退化为指针:[N x elem_type] → elem_type*
arg_type = self._decay_array_to_ptr(arg_type)
if arg_type and arg_type != 'void':
params.append(arg_type)
param_str = ', '.join(params) if params else ''
param_str: str = ', '.join(params) if params else ''
if node.args.vararg:
param_str = param_str + ', ...' if param_str else '...'
if func_name[0].isdigit():
@@ -135,91 +172,125 @@ class DeclarationGenerator:
return False
return self._check_annotation_for_export(node.returns)
def _check_annotation_for_export(self, annotation) -> bool:
def _check_annotation_for_export(self, annotation: ast.AST | None) -> bool:
"""递归检查类型注解中是否包含 CExport 或 t.State"""
if isinstance(annotation, ast.Attribute):
if hasattr(annotation, 'attr') and annotation.attr in ('CExport', 'State'):
return True
elif isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
return self._check_annotation_for_export(annotation.left) or self._check_annotation_for_export(annotation.right)
elif isinstance(annotation, ast.Name):
return annotation.id in ('CExport', 'State')
return False
def _generate_global_decl(self, node: ast.AnnAssign) -> str:
"""生成全局变量声明"""
if not isinstance(node.target, ast.Name):
return None
var_name = node.target.id
if isinstance(node.annotation, ast.Attribute) and hasattr(node.annotation, 'attr') and node.annotation.attr == 'CDefine':
return None
if isinstance(node.annotation, ast.Attribute) and hasattr(node.annotation, 'attr') and node.annotation.attr == 'CTypedef':
return None
if isinstance(node.annotation, ast.Name) and node.annotation.id == 'CTypedef':
return None
if isinstance(node.annotation, ast.BinOp) and isinstance(node.annotation.left, ast.Attribute) and hasattr(node.annotation.left, 'attr') and node.annotation.left.attr == 'CTypedef':
return None
if isinstance(node.annotation, ast.List):
for elt in node.annotation.elts:
if isinstance(elt, ast.Attribute) and hasattr(elt, 'attr') and elt.attr in ('CTypedef', 'Callable'):
return None
if isinstance(elt, ast.Subscript) and isinstance(elt.value, ast.Attribute) and isinstance(elt.value.value, ast.Name) and elt.value.value.id == 't' and elt.value.attr == 'Callable':
return None
VarType = self._get_type_str(node.annotation)
if VarType.startswith('[0 x ') and node.value and isinstance(node.value, ast.List):
elem_type = VarType[5:-1]
actual_count = len(node.value.elts)
if actual_count > 0:
VarType = f'[{actual_count} x {elem_type}]'
return f'@{var_name} = external global {VarType}'
def _generate_global_assign_decl(self, node: ast.Assign) -> str:
"""从无类型标注赋值推断并生成全局变量声明"""
if not node.targets or not isinstance(node.targets[0], ast.Name):
return None
var_name = node.targets[0].id
VarType = self._infer_type(node.value)
if VarType:
return f'@{var_name} = external global {VarType}'
return None
return bool(annotation) and (AnnotationContainsName(annotation, 'CExport') or AnnotationContainsName(annotation, 'State'))
def _generate_global_decl(self, node: ast.AnnAssign) -> str | None:
"""生成全局变量声明"""
if not isinstance(node.target, ast.Name):
return None
var_name: str = node.target.id
if isinstance(node.annotation, ast.Attribute) and hasattr(node.annotation, 'attr') and node.annotation.attr == 'CDefine':
return None
if isinstance(node.annotation, ast.Attribute) and hasattr(node.annotation, 'attr') and node.annotation.attr == 'CTypedef':
return None
if isinstance(node.annotation, ast.Name) and node.annotation.id == 'CTypedef':
return None
if isinstance(node.annotation, ast.BinOp) and isinstance(node.annotation.left, ast.Attribute) and hasattr(node.annotation.left, 'attr') and node.annotation.left.attr == 'CTypedef':
return None
if isinstance(node.annotation, ast.List):
for elt in node.annotation.elts:
if isinstance(elt, ast.Attribute) and hasattr(elt, 'attr') and elt.attr in ('CTypedef', 'Callable'):
return None
if isinstance(elt, ast.Subscript) and isinstance(elt.value, ast.Attribute) and isinstance(elt.value.value, ast.Name) and elt.value.value.id == 't' and elt.value.attr == 'Callable':
return None
VarType: str = self._get_type_str(node.annotation)
if VarType.startswith('[0 x ') and node.value and isinstance(node.value, ast.List):
elem_type: str = VarType[5:-1]
actual_count: int = len(node.value.elts)
if actual_count > 0:
VarType = f'[{actual_count} x {elem_type}]'
return f'@{var_name} = external global {VarType}'
def _generate_global_assign_decl(self, node: ast.Assign) -> str | None:
"""从无类型标注赋值推断并生成全局变量声明"""
if not node.targets or not isinstance(node.targets[0], ast.Name):
return None
var_name: str = node.targets[0].id
VarType: str | None = self._infer_type(node.value)
if VarType:
return f'@{var_name} = external global {VarType}'
return None
def _resolve_base_kind(self, base_name: str) -> str | None:
"""解析基类名称的类型种类(支持别名)
Returns:
'enum' | 'union' | 'renum' | 'struct' | None
"""
# 1) 查 t 别名映射from t import CEnum as en
if base_name in self._t_alias_map:
t_cls: type = self._t_alias_map[base_name]
if issubclass(t_cls, (t.CEnum, t.REnum)):
return 'renum' if issubclass(t_cls, t.REnum) else 'enum'
if issubclass(t_cls, t.CUnion):
return 'union'
if issubclass(t_cls, t.CStruct):
return 'struct'
# 2) 直接查 t 模块属性
t_cls: type | None = getattr(t, base_name, None)
if isinstance(t_cls, type) and issubclass(t_cls, t.CType):
if issubclass(t_cls, (t.CEnum, t.REnum)):
return 'renum' if issubclass(t_cls, t.REnum) else 'enum'
if issubclass(t_cls, t.CUnion):
return 'union'
if issubclass(t_cls, t.CStruct):
return 'struct'
return None
def _is_marker_base(self, base_name: str) -> bool:
"""检查是否为标记基类Object/CVTable/Exception/CEnum/CUnion/CStruct/REnum 及其别名)"""
if base_name in ('Object', 'CVTable', 'Exception', 'CEnum', 'Enum', 'CStruct', 'CUnion', 'REnum'):
return True
if self._resolve_base_kind(base_name) is not None:
return True
return False
def _generate_class_decl(self, node: ast.ClassDef) -> List[str]:
"""生成结构体/类声明"""
decls = []
class_name = node.name
is_enum = False
is_exception = False
decls: list[str] = []
class_name: str = node.name
is_enum: bool = False
is_renum: bool = False
is_union: bool = False
is_exception: bool = False
if node.bases:
for base in node.bases:
base_name: str | None = None
if isinstance(base, ast.Attribute) and hasattr(base, 'attr'):
if base.attr == 'CEnum' or base.attr == 'Enum':
is_enum = True
break
elif base.attr == 'Exception' or base.attr in self.exception_names:
is_exception = True
break
base_name = base.attr
elif isinstance(base, ast.Name) and hasattr(base, 'id'):
if base.id == 'CEnum' or base.id == 'Enum':
is_enum = True
break
elif base.id == 'Exception' or base.id in self.exception_names:
base_name = base.id
if base_name:
if base_name in self.exception_names or base_name == 'Exception':
is_exception = True
break
kind: str | None = self._resolve_base_kind(base_name)
if kind == 'enum':
is_enum = True
break
elif kind == 'union':
is_union = True
elif kind == 'renum':
is_renum = True
if is_exception:
return decls
if is_enum:
enum_values = {}
next_value = 0
enum_values: dict[str, int] = {}
next_value: int = 0
for item in node.body:
if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
var_name = item.target.id
var_name: str = item.target.id
if item.value and isinstance(item.value, ast.Constant) and isinstance(item.value.value, int):
enum_values[var_name] = item.value.value
else:
enum_values[var_name] = next_value
next_value += 1
elif isinstance(item, ast.Assign) and len(item.targets) == 1 and isinstance(item.targets[0], ast.Name):
var_name = item.targets[0].id
var_name: str = item.targets[0].id
if item.value and isinstance(item.value, ast.Constant) and isinstance(item.value.value, int):
enum_values[var_name] = item.value.value
next_value = item.value.value + 1
@@ -229,12 +300,12 @@ class DeclarationGenerator:
for var_name, value in enum_values.items():
decls.append(f'@__config_{class_name}_{var_name} = external global i32')
return decls
member_types = []
has_bitfield = False
total_bits = 0
has_methods = any(isinstance(item, ast.FunctionDef) for item in node.body)
is_cvtable = False
is_cpython_object = False
member_types: list[str] = []
has_bitfield: bool = False
total_bits: int = 0
has_methods: bool = any(isinstance(item, ast.FunctionDef) for item in node.body)
is_cvtable: bool = False
is_cpython_object: bool = False
if hasattr(node, 'decorator_list') and node.decorator_list:
for decorator in node.decorator_list:
if isinstance(decorator, ast.Attribute):
@@ -250,9 +321,9 @@ class DeclarationGenerator:
is_cpython_object = True
if has_methods and not is_cpython_object:
is_cpython_object = True
has_parent_class = False
has_parent_class: bool = False
for base in node.bases:
base_name = None
base_name: str | None = None
if isinstance(base, ast.Attribute):
base_name = base.attr
elif isinstance(base, ast.Name):
@@ -262,14 +333,14 @@ class DeclarationGenerator:
base_name = base.value.attr
elif isinstance(base.value, ast.Name):
base_name = base.value.id
if base_name and base_name not in ('Object', 'CVTable', 'CEnum', 'Enum', 'CStruct', 'CUnion'):
if base_name and not self._is_marker_base(base_name):
has_parent_class = True
break
if has_parent_class and not is_cvtable:
is_cvtable = True
base_has_vtable = False
base_has_vtable: bool = False
for base in node.bases:
base_name = None
base_name: str | None = None
if isinstance(base, ast.Attribute):
base_name = base.attr
elif isinstance(base, ast.Name):
@@ -279,7 +350,7 @@ class DeclarationGenerator:
base_name = base.value.attr
elif isinstance(base.value, ast.Name):
base_name = base.value.id
if base_name and base_name not in ('Object', 'CVTable', 'CEnum', 'Enum', 'CStruct', 'CUnion'):
if base_name and not self._is_marker_base(base_name):
for n in ast.iter_child_nodes(self._pyi_tree):
if isinstance(n, ast.ClassDef) and n.name == base_name:
if hasattr(n, 'decorator_list') and n.decorator_list:
@@ -293,9 +364,9 @@ class DeclarationGenerator:
break
if has_methods and is_cvtable and not base_has_vtable:
member_types.append('i8*')
seen_member_names = set()
seen_member_names: set[str] = set()
for base in node.bases:
base_name = None
base_name: str | None = None
if isinstance(base, ast.Attribute):
base_name = base.attr
elif isinstance(base, ast.Name):
@@ -305,7 +376,9 @@ class DeclarationGenerator:
base_name = base.value.attr
elif isinstance(base.value, ast.Name):
base_name = base.value.id
if base_name and base_name not in ('Object', 'CVTable', 'CEnum', 'Enum', 'CStruct', 'CUnion'):
if base_name and not self._is_marker_base(base_name):
base_member_types: list[str]
base_seen: set[str]
base_member_types, base_seen = self._get_inherited_members(base_name)
for mt in base_member_types:
member_types.append(mt)
@@ -313,7 +386,7 @@ class DeclarationGenerator:
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 = []
init_members: list[ast.AnnAssign] = []
for item in node.body:
if isinstance(item, ast.FunctionDef) and item.name == '__init__':
for stmt in item.body:
@@ -322,7 +395,7 @@ class DeclarationGenerator:
and isinstance(stmt.target.value, ast.Name)
and stmt.target.value.id == 'self'):
init_members.append(stmt)
untyped_self_members = []
untyped_self_members: list[ast.Assign] = []
for m_name in [m.attr for m in init_members if isinstance(m.target, ast.Attribute)]:
seen_member_names.add(m_name)
for item in node.body:
@@ -335,25 +408,26 @@ class DeclarationGenerator:
and isinstance(stmt.targets[0], ast.Attribute)
and isinstance(stmt.targets[0].value, ast.Name)
and stmt.targets[0].value.id == 'self'):
attr_name = stmt.targets[0].attr
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.AnnAssign):
bit_width = self._get_bitfield_width(item.annotation)
# 跳过编译期元数据字段__provides__/__requires__/__require_must__
if isinstance(item.target, ast.Name) and item.target.id in ('__provides__', '__requires__', '__require_must__'):
continue
bit_width: int | None = self._get_bitfield_width(item.annotation)
if bit_width is not None:
has_bitfield = True
total_bits += bit_width
else:
VarType = self._get_type_str(item.annotation, embedded=True)
if (isinstance(item.annotation, ast.Subscript)
and isinstance(item.annotation.value, ast.Name)
and item.annotation.value.id == 'list'):
slice_node = item.annotation.slice
is_dynamic = False
VarType: str = self._get_type_str(item.annotation, embedded=True)
if IsListAnnotation(item.annotation):
slice_node: ast.AST = item.annotation.slice
is_dynamic: bool = False
if isinstance(slice_node, ast.Tuple) and len(slice_node.elts) == 2:
count_node = slice_node.elts[1]
count_node: ast.AST = slice_node.elts[1]
if isinstance(count_node, ast.Constant) and count_node.value is None:
is_dynamic = True
elif not (isinstance(count_node, ast.Constant) and isinstance(count_node.value, int) and count_node.value > 0):
@@ -362,23 +436,34 @@ class DeclarationGenerator:
elif not isinstance(slice_node, ast.Tuple):
is_dynamic = True
if is_dynamic:
elem_type = self._get_type_str(slice_node if not isinstance(slice_node, ast.Tuple) else slice_node.elts[0], embedded=True)
init_len = 0
elem_type: str = self._get_type_str(slice_node if not isinstance(slice_node, ast.Tuple) else slice_node.elts[0], embedded=True)
init_len: int = 0
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
VarType = f'[{init_len} x {elem_type}]'
else:
# 固定大小数组: t.CArray[elem_type, count] → [count x elem_type]
if isinstance(slice_node, ast.Tuple) and len(slice_node.elts) == 2:
elem_type: str = self._get_type_str(slice_node.elts[0], embedded=True)
count_val: int = self._get_const_int(slice_node.elts[1])
if count_val > 0:
VarType = f'[{count_val} x {elem_type}]'
elif isinstance(slice_node, (ast.Attribute, ast.Name, ast.Subscript)):
# 单参数 t.CArray[elem_type] → 指针模式
elem_type: str = self._get_type_str(slice_node, embedded=True)
VarType = f'{elem_type}*'
member_types.append(VarType)
elif 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'):
attr_name = item.targets[0].attr
attr_name: str = item.targets[0].attr
if attr_name in seen_member_names:
continue
InferredType = 'i32'
InferredType: str = 'i32'
if item.value and isinstance(item.value, ast.Constant):
if isinstance(item.value.value, float):
InferredType = 'float'
@@ -398,13 +483,14 @@ class DeclarationGenerator:
member_types.insert(0, 'i32')
else:
member_types.insert(0, 'i64')
struct_type_name = f'%"{self.module_sha1}.{class_name}"' if self.module_sha1 else f'%struct.{class_name}'
struct_decl = f'{struct_type_name} = type {{ {", ".join(member_types)} }}'
struct_type_name: str = f'%"{self.module_sha1}.{class_name}"' if self.module_sha1 else f'%struct.{class_name}'
struct_decl: str = f'{struct_type_name} = type {{ {", ".join(member_types)} }}'
decls.append(struct_decl)
if is_cpython_object or is_cvtable:
new_func_name = f'{class_name}.__before_init__'
new_func_name: str = f'{class_name}.__before_init__'
if self.module_sha1:
new_func_name = f"{self.module_sha1}.{new_func_name}"
new_func_decl: str
if new_func_name[0].isdigit():
new_func_decl = f'declare void @"{new_func_name}"({struct_type_name}*)'
else:
@@ -414,45 +500,47 @@ class DeclarationGenerator:
if isinstance(item, ast.FunctionDef):
if hasattr(item, 'type_params') and item.type_params:
continue
method_name = f'{class_name}.{item.name}'
method_name: str = f'{class_name}.{item.name}'
if self.module_sha1:
method_name = f"{self.module_sha1}.{method_name}"
ret_type = self._get_type_str(item.returns) if item.returns else 'void'
ret_type: str = self._get_type_str(item.returns) if item.returns else 'void'
if not ret_type:
ret_type = 'void'
params = []
params: list[str] = []
for arg_idx, arg in enumerate(item.args.args):
arg_type: str
if arg.annotation:
arg_type = self._get_type_str(arg.annotation)
elif arg_idx == 0 and arg.arg == 'self':
arg_type = f'{struct_type_name}*'
else:
arg_type = 'i8*'
# C 语言中数组参数退化为指针:[N x elem_type] → elem_type*
arg_type = self._decay_array_to_ptr(arg_type)
params.append(arg_type)
param_str = ', '.join(params) if params else ''
param_str: str = ', '.join(params) if params else ''
if method_name[0].isdigit():
decls.append(f'declare {ret_type} @"{method_name}"({param_str})')
else:
decls.append(f'declare {ret_type} @{method_name}({param_str})')
return decls
def _get_inherited_members(self, base_name: str):
import ast
member_types = []
seen_names = set()
def _get_inherited_members(self, base_name: str) -> tuple[list[str], set[str]]:
member_types: list[str] = []
seen_names: set[str] = set()
if not hasattr(self, '_pyi_tree') or self._pyi_tree is None:
return member_types, seen_names
base_node = None
base_node: ast.ClassDef | None = None
for node in ast.iter_child_nodes(self._pyi_tree):
if isinstance(node, ast.ClassDef) and node.name == base_name:
base_node = node
break
if base_node is None:
return member_types, seen_names
base_decls = self._generate_class_decl(base_node)
base_decls: list[str] = self._generate_class_decl(base_node)
for decl in base_decls:
if '= type {' in decl:
type_body = decl.split('= type {')[1].rstrip('}').strip()
type_body: str = decl.split('= type {')[1].rstrip('}').strip()
if type_body:
for t in type_body.split(','):
t = t.strip()
@@ -481,30 +569,29 @@ class DeclarationGenerator:
通过 CTypeRegistry.NameToLLVM 查询,利用 CType 的
position/IsSigned/Size 元属性自动推导,无需手动映射表。
"""
from lib.includes.t import CTypeRegistry
result = CTypeRegistry.NameToLLVM(name)
result: str = CTypeRegistry.NameToLLVM(name)
return result
def _get_type_str(self, annotation: ast.AST, embedded: bool = False) -> str:
import ast
from lib.includes.t import CTypeRegistry
def _get_type_str(self, annotation: ast.AST | None, embedded: bool = False) -> str:
if annotation is None:
return 'i8*'
non_struct_types = {'list', 'dict', 'tuple', 'set', 'array'}
non_struct_types: set[str] = {'list', 'dict', 'tuple', 'set', 'array', 'CArray'}
if isinstance(annotation, ast.Name):
if annotation.id == 'None':
return 'void'
if annotation.id in ('str', 'bytes'):
return 'i8*'
llvm_type = CTypeRegistry.NameToLLVM(annotation.id)
llvm_type: str | None = CTypeRegistry.NameToLLVM(annotation.id)
if llvm_type is not None:
return llvm_type
resolved = CTypeRegistry.ResolveName(annotation.id)
resolved: tuple[type, int] | None = CTypeRegistry.ResolveName(annotation.id)
if resolved is not None:
ctype_cls: type
ptr_level: int
ctype_cls, ptr_level = resolved
base = CTypeRegistry.CTypeToLLVM(ctype_cls)
base: str = CTypeRegistry.CTypeToLLVM(ctype_cls)
if ptr_level > 0:
if base == 'void':
return 'i8*'
@@ -512,26 +599,32 @@ class DeclarationGenerator:
return base
return f'{base}*'
return base
if annotation.id in non_struct_types:
return 'i32'
if annotation.id in self.enum_names:
return 'i32'
if annotation.id in self.struct_names:
sha1 = self.struct_sha1_map.get(annotation.id, self.module_sha1)
sname = f'%"{sha1}.{annotation.id}"' if sha1 else f'%struct.{annotation.id}'
sha1: str | None = self.struct_sha1_map.get(annotation.id, self.module_sha1)
sname: str = f'%"{sha1}.{annotation.id}"' if sha1 else f'%struct.{annotation.id}'
if embedded:
return sname
else:
return f'{sname}*'
if annotation.id in non_struct_types:
return 'i32'
if annotation.id in self.enum_names:
return 'i32'
if annotation.id in self.typedef_map:
resolved = self._get_type_str(self.typedef_map[annotation.id], embedded=embedded)
return resolved
sha1 = self.struct_sha1_map.get(annotation.id, self.module_sha1)
sname = f'%"{sha1}.{annotation.id}"' if sha1 else f'%struct.{annotation.id}'
resolved_str: str = self._get_type_str(self.typedef_map[annotation.id], embedded=embedded)
return resolved_str
# 检查 t 类型别名from t import CInt as myint
if annotation.id in self._t_alias_map:
t_cls: type = self._t_alias_map[annotation.id]
llvm: str | None = CTypeRegistry.CTypeToLLVM(t_cls)
if llvm:
return llvm
sha1: str | None = self.struct_sha1_map.get(annotation.id, self.module_sha1)
sname: str = f'%"{sha1}.{annotation.id}"' if sha1 else f'%struct.{annotation.id}'
return f'{sname}*'
elif isinstance(annotation, ast.Attribute):
attr_name = annotation.attr if hasattr(annotation, 'attr') else ''
module_name = ''
attr_name: str = annotation.attr if hasattr(annotation, 'attr') else ''
module_name: str = ''
if hasattr(annotation, 'value'):
if isinstance(annotation.value, ast.Name):
module_name = annotation.value.id
@@ -541,13 +634,15 @@ class DeclarationGenerator:
return ''
if (module_name == 't' and attr_name == 'Callable') or attr_name == 'Callable':
return 'i8*'
llvm_type = CTypeRegistry.NameToLLVM(attr_name)
llvm_type: str | None = CTypeRegistry.NameToLLVM(attr_name)
if llvm_type is not None:
return llvm_type
resolved = CTypeRegistry.ResolveName(attr_name)
resolved: tuple[type, int] | None = CTypeRegistry.ResolveName(attr_name)
if resolved is not None:
ctype_cls: type
ptr_level: int
ctype_cls, ptr_level = resolved
base = CTypeRegistry.CTypeToLLVM(ctype_cls)
base: str = CTypeRegistry.CTypeToLLVM(ctype_cls)
if ptr_level > 0:
if base == 'void':
return 'i8*'
@@ -560,24 +655,24 @@ class DeclarationGenerator:
if attr_name in self.enum_names:
return 'i32'
if attr_name in self.struct_names:
sha1 = self.struct_sha1_map.get(attr_name, self.module_sha1)
sname = f'%"{sha1}.{attr_name}"' if sha1 else f'%struct.{attr_name}'
sha1: str | None = self.struct_sha1_map.get(attr_name, self.module_sha1)
sname: str = f'%"{sha1}.{attr_name}"' if sha1 else f'%struct.{attr_name}'
if embedded:
return sname
else:
return f'{sname}*'
if attr_name and attr_name[0].isupper() and attr_name not in non_struct_types:
sha1 = self.struct_sha1_map.get(attr_name, self.module_sha1)
sname = f'%"{sha1}.{attr_name}"' if sha1 else f'%struct.{attr_name}'
sha1: str | None = self.struct_sha1_map.get(attr_name, self.module_sha1)
sname: str = f'%"{sha1}.{attr_name}"' if sha1 else f'%struct.{attr_name}'
return f'{sname}*'
if attr_name and attr_name not in non_struct_types:
sha1 = self.struct_sha1_map.get(attr_name, self.module_sha1)
sname = f'%"{sha1}.{attr_name}"' if sha1 else f'%struct.{attr_name}'
sha1: str | None = self.struct_sha1_map.get(attr_name, self.module_sha1)
sname: str = f'%"{sha1}.{attr_name}"' if sha1 else f'%struct.{attr_name}'
return f'{sname}*'
return 'i32'
elif isinstance(annotation, ast.BinOp):
left_type = self._get_type_str(annotation.left, embedded=embedded)
right_type = self._get_type_str(annotation.right, embedded=embedded)
left_type: str = self._get_type_str(annotation.left, embedded=embedded)
right_type: str = self._get_type_str(annotation.right, embedded=embedded)
if left_type in ('', 'void'):
return right_type if right_type not in ('', 'void') else (left_type or right_type)
if right_type in ('', 'void'):
@@ -590,30 +685,56 @@ class DeclarationGenerator:
return right_type
return left_type
elif isinstance(annotation, ast.Subscript):
base = self._get_type_str(annotation.value)
base: str = self._get_type_str(annotation.value)
if base == 'i8*' and isinstance(annotation.slice, ast.Constant):
return f'[{self._get_const_int(annotation.slice)} x i8]'
if isinstance(annotation.slice, ast.Constant) and isinstance(annotation.slice.value, int):
return f'[{annotation.slice.value} x {base}]'
if isinstance(annotation.value, ast.Name) and annotation.value.id == 'list':
slice_node = annotation.slice
if isinstance(slice_node, ast.Tuple) and len(slice_node.elts) == 2:
elem_type = self._get_type_str(slice_node.elts[0], embedded=True)
count_node = slice_node.elts[1]
array_count = self._get_const_int(count_node)
if array_count > 0:
return f'[{array_count} x {elem_type}]'
else:
return f'[0 x {elem_type}]'
elem_type = self._get_type_str(slice_node, embedded=True)
return f'[0 x {elem_type}]'
if isinstance(annotation.value, ast.Name) and annotation.value.id == 'tuple':
slice_node = annotation.slice
slice_node: ast.AST = annotation.slice
elem_types: list[str]
if isinstance(slice_node, ast.Tuple):
elem_types = [self._get_type_str(e, embedded=True) for e in slice_node.elts]
else:
elem_types = [self._get_type_str(slice_node, embedded=True)]
return '{ ' + ', '.join(elem_types) + ' }'
# 处理泛型类类型注解(如 list[str]
if isinstance(annotation.value, ast.Name):
gc_name: str = annotation.value.id
if gc_name in non_struct_types and gc_name != 'tuple':
gc_slice: ast.AST = annotation.slice
gc_args: list[str] = []
if isinstance(gc_slice, ast.Name):
gc_args.append(gc_slice.id)
elif isinstance(gc_slice, ast.Tuple):
gc_valid: bool = True
for gc_elt in gc_slice.elts:
if isinstance(gc_elt, ast.Name):
gc_args.append(gc_elt.id)
else:
gc_valid = False
break
if not gc_valid:
gc_args = []
if gc_args:
gc_mangled: list[str] = []
for gc_ta in gc_args:
gc_llvm: str | None = CTypeRegistry.NameToLLVM(gc_ta)
if gc_llvm:
if gc_llvm in ('float', 'double', 'half', 'fp128'):
gc_cls: type | None = CTypeRegistry.GetClassByName(gc_ta)
gc_sz: int = gc_cls().Size if gc_cls else 0
gc_mangled.append('f' + str(gc_sz))
else:
gc_mangled.append(gc_llvm)
else:
gc_mangled.append(gc_ta)
gc_spec: str = gc_name + '[' + ']['.join(gc_mangled) + ']'
gc_sha1: str | None = self.module_sha1
gc_sname: str = f'%"{gc_sha1}.{gc_spec}"' if gc_sha1 else f'%struct.{gc_spec}'
if embedded:
return gc_sname
return f'{gc_sname}*'
return f'{base}'
elif isinstance(annotation, ast.Constant):
if annotation.value is None:
@@ -621,23 +742,25 @@ class DeclarationGenerator:
if isinstance(annotation.value, int):
return 'i32'
if isinstance(annotation.value, str):
type_name = annotation.value
type_name: str = annotation.value
if type_name in self.enum_names:
return 'i32'
if type_name in self.struct_names:
sha1 = self.struct_sha1_map.get(type_name, self.module_sha1)
sname = f'%"{sha1}.{type_name}"' if sha1 else f'%struct.{type_name}'
sha1: str | None = self.struct_sha1_map.get(type_name, self.module_sha1)
sname: str = f'%"{sha1}.{type_name}"' if sha1 else f'%struct.{type_name}'
if embedded:
return sname
else:
return f'{sname}*'
llvm_type = CTypeRegistry.NameToLLVM(type_name)
llvm_type: str | None = CTypeRegistry.NameToLLVM(type_name)
if llvm_type is not None:
return llvm_type
resolved = CTypeRegistry.ResolveName(type_name)
resolved: tuple[type, int] | None = CTypeRegistry.ResolveName(type_name)
if resolved is not None:
ctype_cls: type
ptr_level: int
ctype_cls, ptr_level = resolved
base = CTypeRegistry.CTypeToLLVM(ctype_cls)
base: str = CTypeRegistry.CTypeToLLVM(ctype_cls)
if ptr_level > 0:
if base == 'void':
return 'i8*'
@@ -645,8 +768,8 @@ class DeclarationGenerator:
return base
return f'{base}*'
return base
sha1 = self.struct_sha1_map.get(type_name, self.module_sha1)
sname = f'%"{sha1}.{type_name}"' if sha1 else f'%struct.{type_name}'
sha1: str | None = self.struct_sha1_map.get(type_name, self.module_sha1)
sname: str = f'%"{sha1}.{type_name}"' if sha1 else f'%struct.{type_name}'
return f'{sname}*'
return 'i8*'
elif isinstance(annotation, ast.Call):
@@ -663,23 +786,18 @@ class DeclarationGenerator:
return type_str + '*'
if type_str.endswith('*'):
return type_str
from lib.includes.t import CTypeRegistry
resolved = CTypeRegistry.ResolveName(type_str)
resolved: tuple[type, int] | None = CTypeRegistry.ResolveName(type_str)
if resolved is not None:
ctype_cls: type
ptr_level: int
ctype_cls, ptr_level = resolved
llvm_str = CTypeRegistry.CTypeToLLVM(ctype_cls)
llvm_str: str = CTypeRegistry.CTypeToLLVM(ctype_cls)
if llvm_str:
return f'{llvm_str}{"*" * (ptr_level + 1)}'
cnameres = CTypeRegistry.CNameToClass(type_str)
if cnameres is not None:
llvm_str = CTypeRegistry.CTypeToLLVM(cnameres)
if llvm_str:
return f'{llvm_str}*'
return f'{type_str}*'
def _infer_type(self, value: ast.AST) -> str:
"""从值推断类型"""
import ast
if isinstance(value, ast.Constant):
if isinstance(value.value, int):
return 'i32'
@@ -701,17 +819,16 @@ class DeclarationGenerator:
return 'i8*'
return 'i32'
def _get_bitfield_width(self, annotation: ast.AST):
import ast
def _get_bitfield_width(self, annotation: ast.AST) -> int | None:
if isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
right_width = self._get_bitfield_width(annotation.right)
right_width: int | None = self._get_bitfield_width(annotation.right)
if right_width is not None:
return right_width
return self._get_bitfield_width(annotation.left)
if isinstance(annotation, ast.Call):
if isinstance(annotation.func, ast.Attribute) and annotation.func.attr == 'Bit':
if annotation.args:
arg = annotation.args[0]
arg: ast.AST = annotation.args[0]
if isinstance(arg, ast.Constant) and isinstance(arg.value, int):
return arg.value
if isinstance(annotation, ast.Subscript):
@@ -722,17 +839,16 @@ class DeclarationGenerator:
def _get_const_int(self, node: ast.AST) -> int:
"""获取常量整数值,支持符号常量和简单表达式"""
import ast
if isinstance(node, ast.Constant) and isinstance(node.value, int):
return node.value
if isinstance(node, ast.Name):
if node.id in self._DefineConstants:
val = self._DefineConstants[node.id]
val: int | str = self._DefineConstants[node.id]
if isinstance(val, int):
return val
if isinstance(node, ast.BinOp):
left_val = self._get_const_int(node.left)
right_val = self._get_const_int(node.right)
left_val: int = self._get_const_int(node.left)
right_val: int = self._get_const_int(node.right)
if left_val and right_val:
if isinstance(node.op, ast.Add):
return left_val + right_val

View File

@@ -1,28 +1,34 @@
from __future__ import annotations
import os
import ast
import traceback
from typing import Any
from lib.Projectrans.Utils import compute_sha1, get_file_dependencies, find_reachable_files_from_entries
from lib.Projectrans.DeclarationGenerator import DeclarationGenerator
from lib.core.VLogger import get_logger as _vlog
from lib.constants.config import mode as _ConfigMode
from StubGen import PythonToStubConverter
class Phase1Generator:
"""阶段一:从源文件生成声明接口"""
def __init__(self, src_root: str, temp_dir: str, include_dirs: list = None, entry_files: list = None, target_triple: str = None, target_datalayout: str = None):
self.src_root = os.path.abspath(src_root)
self.temp_dir = os.path.abspath(temp_dir)
self.include_dirs = include_dirs or []
def __init__(self, src_root: str, temp_dir: str, include_dirs: list[str] | None = None, entry_files: list[str] | None = None, target_triple: str | None = None, target_datalayout: str | None = None) -> None:
self.src_root: str = os.path.abspath(src_root)
self.temp_dir: str = os.path.abspath(temp_dir)
self.include_dirs: list[str] = include_dirs or []
self.sha1_map: dict[str, str] = {}
self.include_py_map: dict[str, str] = {}
self.entry_files = entry_files
self.target_triple = target_triple
self.target_datalayout = target_datalayout
self.entry_files: list[str] | None = entry_files
self.target_triple: str | None = target_triple
self.target_datalayout: str | None = target_datalayout
os.makedirs(self.temp_dir, exist_ok=True)
def _get_needed_include_files(self, reachable_source_files: set) -> list:
def _get_needed_include_files(self, reachable_source_files: set[str]) -> list[tuple[str, str, str]]:
"""从可达源文件收集所有被引用(含传递依赖)的 include 文件"""
include_file_map = {}
include_file_map: dict[str, tuple[str, str, str]] = {}
for includes_dir in self.include_dirs:
if not os.path.isdir(includes_dir):
continue
@@ -30,51 +36,87 @@ class Phase1Generator:
dirs[:] = [d for d in dirs if not d.startswith('.') and d != '__pycache__']
for fname in files:
if fname.endswith('.py') or fname.endswith('.pyi'):
src_path = os.path.join(root, fname)
rel_from_inc = os.path.relpath(src_path, includes_dir)
ModulePath = rel_from_inc.replace(os.sep, '.').replace('/', '.')
module_name = os.path.splitext(ModulePath)[0]
info = (src_path, rel_from_inc, includes_dir)
src_path: str = os.path.join(root, fname)
rel_from_inc: str = os.path.relpath(src_path, includes_dir)
ModulePath: str = rel_from_inc.replace(os.sep, '.').replace('/', '.')
module_name: str = os.path.splitext(ModulePath)[0]
info: tuple[str, str, str] = (src_path, rel_from_inc, includes_dir)
include_file_map[module_name] = info
top_pkg = module_name.split('.')[0]
top_pkg: str = module_name.split('.')[0]
if top_pkg not in include_file_map:
include_file_map[top_pkg] = info
imported_from_src = set()
imported_from_src: set[str] = set()
for src_path in reachable_source_files:
deps = get_file_dependencies(src_path, self.src_root)
deps: set[str] = get_file_dependencies(src_path, self.src_root)
imported_from_src.update(deps)
needed = set()
queue = list(imported_from_src)
# 检测 dict/list 容器使用,自动添加 _dict/_list/json 依赖
for src_path in reachable_source_files:
try:
with open(src_path, 'r', encoding='utf-8') as f:
content: str = f.read()
tree = ast.parse(content)
for node in ast.walk(tree):
if isinstance(node, ast.Subscript) and isinstance(node.value, ast.Name):
if node.value.id == 'dict':
imported_from_src.add('_dict')
imported_from_src.add('json')
break
elif node.value.id == 'list':
imported_from_src.add('_list')
break
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
if node.func.id == 'dict':
imported_from_src.add('_dict')
imported_from_src.add('json')
break
elif node.func.id == 'list':
imported_from_src.add('_list')
break
# 检测类型注解 d: dict / l: list (非泛型形式)
if isinstance(node, ast.AnnAssign) and isinstance(node.annotation, ast.Name):
if node.annotation.id == 'dict':
imported_from_src.add('_dict')
imported_from_src.add('json')
break
elif node.annotation.id == 'list':
imported_from_src.add('_list')
break
except Exception:
pass
needed: set[str] = set()
queue: list[str] = list(imported_from_src)
while queue:
mod_name = queue.pop(0)
mod_name: str = queue.pop(0)
if mod_name in needed:
continue
if mod_name in include_file_map:
needed.add(mod_name)
src_path = include_file_map[mod_name][0]
includes_dir = include_file_map[mod_name][2]
deps = get_file_dependencies(src_path, includes_dir)
src_path: str = include_file_map[mod_name][0]
includes_dir: str = include_file_map[mod_name][2]
deps: set[str] = get_file_dependencies(src_path, includes_dir)
for dep in deps:
if dep in include_file_map and dep not in needed:
queue.append(dep)
needed_infos = []
needed_infos: list[tuple[str, str, str]] = []
for mod_name in sorted(needed):
if mod_name in include_file_map:
info = include_file_map[mod_name]
info: tuple[str, str, str] = include_file_map[mod_name]
if info not in needed_infos:
needed_infos.append(info)
return needed_infos
def run(self):
def run(self) -> None:
"""扫描源目录,只处理入口文件可达的 .py 文件(导入遍历)"""
py_files: list[str]
if self.entry_files:
py_files = list(self.entry_files)
else:
main_py = os.path.join(self.src_root, 'main.py')
main_py: str = os.path.join(self.src_root, 'main.py')
if os.path.exists(main_py):
py_files = [main_py]
else:
@@ -86,50 +128,55 @@ class Phase1Generator:
py_files.append(os.path.join(root, file))
if not py_files:
print("[阶段一] 未找到入口文件或源文件")
_vlog().warning("未找到入口文件或源文件")
return
reachable: set[str]
if self.entry_files:
reachable = find_reachable_files_from_entries(self.src_root, py_files)
else:
reachable = find_reachable_files_from_entries(self.src_root, py_files)
print(f"[阶段一] 找到 {len(reachable)} 个可达源文件(从入口遍历)")
_vlog().info(f"找到 {len(reachable)} 个可达源文件(从入口遍历)")
for i, src_path in enumerate(sorted(reachable), 1):
rel = os.path.relpath(src_path, self.src_root)
print(f"[{i}/{len(reachable)}] 生成签名: {rel}")
rel: str = os.path.relpath(src_path, self.src_root)
_vlog().info(f"[{i}/{len(reachable)}] 生成签名: {rel}")
try:
self._process_file_pyi(src_path, rel)
except Exception as e:
print(f" [错误] {e}")
_vlog().error(f"生成签名失败: {e}")
traceback.print_exc()
needed_includes = self._get_needed_include_files(reachable)
needed_includes: list[tuple[str, str, str]] = self._get_needed_include_files(reachable)
self._process_include_py_files_pyi(needed_includes)
struct_names: set[str]
enum_names: set[str]
struct_sha1_map: dict[str, str]
exception_names: set[str]
struct_names, enum_names, struct_sha1_map, exception_names = self._build_struct_registry()
print(f"\n[阶段一] 结构体注册表: {len(struct_names)} 个类型, {len(enum_names)} 个枚举")
_vlog().info(f"结构体注册表: {len(struct_names)} 个类型, {len(enum_names)} 个枚举")
for name in sorted(struct_names):
print(f" {name}")
_vlog().debug(f" {name}")
for i, src_path in enumerate(sorted(reachable), 1):
rel = os.path.relpath(src_path, self.src_root)
rel: str = os.path.relpath(src_path, self.src_root)
try:
self._process_file_stub(src_path, rel, struct_names, enum_names=enum_names, struct_sha1_map=struct_sha1_map, exception_names=exception_names)
except Exception as e:
print(f" [错误] {e}")
_vlog().error(f"生成声明失败: {e}")
traceback.print_exc()
self._process_include_py_files_stub(struct_names, needed_includes, enum_names=enum_names, struct_sha1_map=struct_sha1_map, exception_names=exception_names)
print(f"\n[阶段一完成] 声明接口生成到: {self.temp_dir}")
print(f"SHA1 映射表 ({len(self.sha1_map)} 个文件):")
_vlog().success(f"声明接口生成到: {self.temp_dir}")
_vlog().info(f"SHA1 映射表 ({len(self.sha1_map)} 个文件):")
for sha1, rel in sorted(self.sha1_map.items()):
print(f" {sha1} -> {rel}")
_vlog().debug(f" {sha1} -> {rel}")
def _collect_include_py_files(self):
include_py_files = []
def _collect_include_py_files(self) -> list[tuple[str, str, str]]:
include_py_files: list[tuple[str, str, str]] = []
for includes_dir in self.include_dirs:
if not os.path.isdir(includes_dir):
continue
@@ -137,180 +184,190 @@ class Phase1Generator:
dirs[:] = [d for d in dirs if not d.startswith('.') and d != '__pycache__']
for fname in files:
if fname.endswith('.py') or fname.endswith('.pyi'):
src_path = os.path.join(root, fname)
rel_from_inc = os.path.relpath(src_path, includes_dir)
src_path: str = os.path.join(root, fname)
rel_from_inc: str = os.path.relpath(src_path, includes_dir)
include_py_files.append((src_path, rel_from_inc, includes_dir))
return include_py_files
def _process_include_py_files_pyi(self, needed_includes: list = None):
def _process_include_py_files_pyi(self, needed_includes: list[tuple[str, str, str]] | None = None) -> None:
include_py_files: list[tuple[str, str, str]]
if needed_includes is not None:
include_py_files = needed_includes
else:
include_py_files = self._collect_include_py_files()
if not include_py_files:
return
print(f"\n[阶段一-includes] 处理 {len(include_py_files)} 个被引用的 Python 库文件")
_vlog().info(f"处理 {len(include_py_files)} 个被引用的 Python 库文件")
for src_path, rel_from_inc, includes_dir in include_py_files:
ModulePath = rel_from_inc.replace(os.sep, '.').replace('/', '.')
module_name = os.path.splitext(ModulePath)[0]
ModulePath: str = rel_from_inc.replace(os.sep, '.').replace('/', '.')
module_name: str = os.path.splitext(ModulePath)[0]
try:
with open(src_path, 'r', encoding='utf-8') as f:
content = f.read()
sha1 = compute_sha1(content)
content: str = f.read()
sha1: str = compute_sha1(content)
self.sha1_map[sha1] = f"includes/{rel_from_inc}"
top_module = rel_from_inc.split(os.sep)[0].split('/')[0]
top_module: str = rel_from_inc.split(os.sep)[0].split('/')[0]
if top_module not in self.include_py_map:
self.include_py_map[top_module] = sha1
self.include_py_map[module_name] = sha1
sig_path = os.path.join(self.temp_dir, f"{sha1}.pyi")
sig_path: str = os.path.join(self.temp_dir, f"{sha1}.pyi")
if os.path.isfile(sig_path):
print(f" 缓存命中: {rel_from_inc} -> {sha1}.pyi")
_vlog().info(f" 缓存命中: {rel_from_inc} -> {sha1}.pyi")
continue
sig_content = PythonToStubConverter.convert(content, module_name)
sig_content: str = PythonToStubConverter.convert(content, module_name)
with open(sig_path, 'w', encoding='utf-8', newline='\n') as f:
f.write(sig_content)
print(f" 生成签名: {rel_from_inc} -> {sha1}.pyi")
_vlog().info(f" 生成签名: {rel_from_inc} -> {sha1}.pyi")
except Exception as e:
print(f" [错误] {rel_from_inc}: {e}")
_vlog().error(f"处理 include 文件失败 {rel_from_inc}: {e}")
def _collect_typedef_map(self):
import ast as _ast
typedef_map = {}
def _collect_typedef_map(self) -> dict[str, ast.AST]:
typedef_map: dict[str, ast.AST] = {}
if not os.path.isdir(self.temp_dir):
return typedef_map
for fname in os.listdir(self.temp_dir):
if not fname.endswith('.pyi'):
continue
pyi_path = os.path.join(self.temp_dir, fname)
pyi_path: str = os.path.join(self.temp_dir, fname)
try:
with open(pyi_path, 'r', encoding='utf-8') as f:
content = f.read()
tree = _ast.parse(content)
for node in _ast.iter_child_nodes(tree):
if isinstance(node, _ast.AnnAssign) and isinstance(node.target, _ast.Name):
var_name = node.target.id
is_typedef = False
if isinstance(node.annotation, _ast.Attribute) and hasattr(node.annotation, 'attr') and node.annotation.attr == 'CTypedef':
content: str = f.read()
tree: ast.Module = ast.parse(content)
for node in ast.iter_child_nodes(tree):
if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
var_name: str = node.target.id
is_typedef: bool = False
if isinstance(node.annotation, ast.Attribute) and hasattr(node.annotation, 'attr') and node.annotation.attr == 'CTypedef':
is_typedef = True
elif isinstance(node.annotation, _ast.Name) and node.annotation.id == 'CTypedef':
elif isinstance(node.annotation, ast.Name) and node.annotation.id == 'CTypedef':
is_typedef = True
elif isinstance(node.annotation, _ast.BinOp) and isinstance(node.annotation.left, _ast.Attribute) and hasattr(node.annotation.left, 'attr') and node.annotation.left.attr == 'CTypedef':
elif isinstance(node.annotation, ast.BinOp) and isinstance(node.annotation.left, ast.Attribute) and hasattr(node.annotation.left, 'attr') and node.annotation.left.attr == 'CTypedef':
is_typedef = True
if is_typedef and node.value and var_name not in typedef_map:
typedef_map[var_name] = node.value
except Exception as _e:
from lib.core.VLogger import get_logger as _vlog
from lib.constants.config import mode as _ConfigMode
if _ConfigMode == "strict":
raise
_vlog().warning(f"收集 typedef 映射失败: {_e}", "Exception")
return typedef_map
def _process_include_py_files_stub(self, struct_names: set, needed_includes: list = None, enum_names: set = None, struct_sha1_map: dict = None, exception_names: set = None):
def _process_include_py_files_stub(self, struct_names: set[str], needed_includes: list[tuple[str, str, str]] | None = None, enum_names: set[str] | None = None, struct_sha1_map: dict[str, str] | None = None, exception_names: set[str] | None = None) -> None:
include_py_files: list[tuple[str, str, str]]
if needed_includes is not None:
include_py_files = needed_includes
else:
include_py_files = self._collect_include_py_files()
if not include_py_files:
return
typedef_map = self._collect_typedef_map()
typedef_map: dict[str, ast.AST] = self._collect_typedef_map()
for src_path, rel_from_inc, includes_dir in include_py_files:
ModulePath = rel_from_inc.replace(os.sep, '.').replace('/', '.')
module_name = os.path.splitext(ModulePath)[0]
ModulePath: str = rel_from_inc.replace(os.sep, '.').replace('/', '.')
module_name: str = os.path.splitext(ModulePath)[0]
try:
with open(src_path, 'r', encoding='utf-8') as f:
content = f.read()
sha1 = compute_sha1(content)
content: str = f.read()
sha1: str = compute_sha1(content)
stub_path = os.path.join(self.temp_dir, f"{sha1}.stub.ll")
stub_path: str = os.path.join(self.temp_dir, f"{sha1}.stub.ll")
if os.path.isfile(stub_path):
print(f" 缓存命中: {rel_from_inc} -> {sha1}.stub.ll")
_vlog().info(f" 缓存命中: {rel_from_inc} -> {sha1}.stub.ll")
continue
sig_path = os.path.join(self.temp_dir, f"{sha1}.pyi")
sig_path: str = os.path.join(self.temp_dir, f"{sha1}.pyi")
with open(sig_path, 'r', encoding='utf-8') as f:
sig_content = f.read()
sig_content: str = f.read()
self._generate_decl_ll(sig_content, stub_path, src_path, struct_names, enum_names=enum_names, module_sha1=sha1, struct_sha1_map=struct_sha1_map, exception_names=exception_names, typedef_map=typedef_map)
print(f" 生成声明: {rel_from_inc} -> {sha1}.stub.ll")
_vlog().info(f" 生成声明: {rel_from_inc} -> {sha1}.stub.ll")
except Exception as e:
print(f" [错误] {rel_from_inc}: {e}")
_vlog().error(f"处理 include 文件失败 {rel_from_inc}: {e}")
def _process_file_pyi(self, src_path: str, rel_path: str):
def _process_file_pyi(self, src_path: str, rel_path: str) -> None:
with open(src_path, 'r', encoding='utf-8') as f:
content = f.read()
content: str = f.read()
sha1 = compute_sha1(content)
sha1: str = compute_sha1(content)
self.sha1_map[sha1] = rel_path
sig_path = os.path.join(self.temp_dir, f"{sha1}.pyi")
sig_path: str = os.path.join(self.temp_dir, f"{sha1}.pyi")
if os.path.isfile(sig_path):
print(f" -> {sha1}.pyi (缓存)")
_vlog().info(f" -> {sha1}.pyi (缓存)")
return
module_name = os.path.splitext(rel_path)[0].replace(os.sep, '.').replace('/', '.')
sig_content = PythonToStubConverter.convert(content, module_name)
module_name: str = os.path.splitext(rel_path)[0].replace(os.sep, '.').replace('/', '.')
sig_content: str = PythonToStubConverter.convert(content, module_name)
with open(sig_path, 'w', encoding='utf-8', newline='\n') as f:
f.write(sig_content)
print(f" -> {sha1}.pyi (签名)")
_vlog().info(f" -> {sha1}.pyi (签名)")
def _process_file_stub(self, src_path: str, rel_path: str, struct_names: set, enum_names: set = None, struct_sha1_map: dict = None, exception_names: set = None):
def _process_file_stub(self, src_path: str, rel_path: str, struct_names: set[str], enum_names: set[str] | None = None, struct_sha1_map: dict[str, str] | None = None, exception_names: set[str] | None = None) -> None:
with open(src_path, 'r', encoding='utf-8') as f:
content = f.read()
content: str = f.read()
sha1 = compute_sha1(content)
stub_path = os.path.join(self.temp_dir, f"{sha1}.stub.ll")
sha1: str = compute_sha1(content)
stub_path: str = os.path.join(self.temp_dir, f"{sha1}.stub.ll")
if os.path.isfile(stub_path):
print(f" -> {sha1}.stub.ll (缓存)")
_vlog().info(f" -> {sha1}.stub.ll (缓存)")
return
sig_path = os.path.join(self.temp_dir, f"{sha1}.pyi")
sig_path: str = os.path.join(self.temp_dir, f"{sha1}.pyi")
with open(sig_path, 'r', encoding='utf-8') as f:
sig_content = f.read()
sig_content: str = f.read()
typedef_map = self._collect_typedef_map()
typedef_map: dict[str, ast.AST] = self._collect_typedef_map()
self._generate_decl_ll(sig_content, stub_path, src_path, struct_names, enum_names=enum_names, module_sha1=sha1, struct_sha1_map=struct_sha1_map, exception_names=exception_names, typedef_map=typedef_map)
print(f" -> {sha1}.stub.ll (声明)")
_vlog().info(f" -> {sha1}.stub.ll (声明)")
def _build_struct_registry(self):
struct_names = set()
enum_names = set()
exception_names = set()
struct_sha1_map = {}
valid_sha1_keys = set(self.sha1_map.keys())
def _build_struct_registry(self) -> tuple[set[str], set[str], dict[str, str], set[str]]:
struct_names: set[str] = set()
enum_names: set[str] = set()
exception_names: set[str] = set()
struct_sha1_map: dict[str, str] = {}
valid_sha1_keys: set[str] = set(self.sha1_map.keys())
for fname in os.listdir(self.temp_dir):
if not fname.endswith('.pyi'):
continue
sha1_key = fname.replace('.pyi', '')
sha1_key: str = fname.replace('.pyi', '')
if sha1_key not in valid_sha1_keys:
continue
pyi_path = os.path.join(self.temp_dir, fname)
pyi_path: str = os.path.join(self.temp_dir, fname)
try:
with open(pyi_path, 'r', encoding='utf-8') as f:
content = f.read()
tree = ast.parse(content)
content: str = f.read()
tree: ast.Module = ast.parse(content)
for node in ast.iter_child_nodes(tree):
if isinstance(node, ast.ClassDef):
is_enum = False
is_exception = False
is_enum: bool = False
is_exception: bool = False
if node.bases:
for base in node.bases:
if isinstance(base, ast.Attribute) and hasattr(base, 'attr'):
if base.attr in ('CEnum', 'Enum'):
if base.attr in ('CEnum', 'Enum', 'REnum'):
is_enum = True
break
elif base.attr == 'Exception' or base.attr in exception_names:
is_exception = True
break
elif isinstance(base, ast.Name) and hasattr(base, 'id'):
if base.id in ('CEnum', 'Enum'):
if base.id in ('CEnum', 'Enum', 'REnum'):
is_enum = True
break
elif base.id == 'Exception' or base.id in exception_names:
is_exception = True
break
# 也检查装饰器形式 @t.CEnum / @t.REnum
if not is_enum and not is_exception and hasattr(node, 'decorator_list') and node.decorator_list:
for deco in node.decorator_list:
deco_attr: str | None = None
if isinstance(deco, ast.Attribute) and hasattr(deco, 'attr'):
deco_attr = deco.attr
elif isinstance(deco, ast.Name) and hasattr(deco, 'id'):
deco_attr = deco.id
if deco_attr in ('CEnum', 'Enum', 'REnum'):
is_enum = True
break
if is_enum:
enum_names.add(node.name)
elif is_exception:
@@ -319,20 +376,18 @@ class Phase1Generator:
struct_names.add(node.name)
struct_sha1_map[node.name] = sha1_key
except Exception as _e:
from lib.core.VLogger import get_logger as _vlog
from lib.constants.config import mode as _ConfigMode
if _ConfigMode == "strict":
raise
_vlog().warning(f"构建结构体/枚举注册表失败: {_e}", "Exception")
return struct_names, enum_names, struct_sha1_map, exception_names
def _generate_decl_ll(self, pyi_content: str, ll_path: str, src_path: str, struct_names: set = None, enum_names: set = None, module_sha1: str = None, struct_sha1_map: dict = None, exception_names: set = None, typedef_map: dict = None):
def _generate_decl_ll(self, pyi_content: str, ll_path: str, src_path: str, struct_names: set[str] | None = None, enum_names: set[str] | None = None, module_sha1: str | None = None, struct_sha1_map: dict[str, str] | None = None, exception_names: set[str] | None = None, typedef_map: dict[str, ast.AST] | None = None) -> None:
try:
decl_gen = DeclarationGenerator(struct_names=struct_names, enum_names=enum_names, module_sha1=module_sha1, target_triple=self.target_triple, target_datalayout=self.target_datalayout, struct_sha1_map=struct_sha1_map, exception_names=exception_names, typedef_map=typedef_map)
decl_ll = decl_gen.generate(pyi_content, src_path)
decl_gen: DeclarationGenerator = DeclarationGenerator(struct_names=struct_names, enum_names=enum_names, module_sha1=module_sha1, target_triple=self.target_triple, target_datalayout=self.target_datalayout, struct_sha1_map=struct_sha1_map, exception_names=exception_names, typedef_map=typedef_map)
decl_ll: str = decl_gen.generate(pyi_content, src_path)
with open(ll_path, 'w', encoding='utf-8', newline='\n') as f:
f.write(decl_ll)
except Exception as e:
print(f" [警告] .ll 声明生成失败: {e}")
_vlog().warning(f".ll 声明生成失败: {e}")
with open(ll_path, 'w', encoding='utf-8') as f:
f.write(f"; declaration for {os.path.basename(src_path)}\n; error: {e}\n")

File diff suppressed because it is too large Load Diff

View File

@@ -1,53 +1,51 @@
from __future__ import annotations
import hashlib
import os
import ast
from lib.core.VLogger import get_logger as _vlog
from lib.constants.config import mode as _ConfigMode
from lib.core.SymbolUtils import AnnotationContainsName
def compute_sha1(content: str) -> str:
return hashlib.sha1(content.encode('utf-8')).hexdigest()[:16]
def _check_annotation_for_export(annotation) -> bool:
def _check_annotation_for_export(annotation: ast.AST | None) -> bool:
"""递归检查类型注解中是否包含 CExport"""
if annotation is None:
return False
if isinstance(annotation, ast.Attribute):
if hasattr(annotation, 'attr') and annotation.attr == 'CExport':
return True
elif isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
return _check_annotation_for_export(annotation.left) or _check_annotation_for_export(annotation.right)
elif isinstance(annotation, ast.Name):
return annotation.id == 'CExport'
return False
return AnnotationContainsName(annotation, 'CExport') if annotation else False
def sha1_file(filepath: str) -> str:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
content: str = f.read()
return compute_sha1(content)
def get_file_dependencies(filepath: str, src_root: str) -> set:
def get_file_dependencies(filepath: str, src_root: str) -> set[str]:
"""解析 Python 文件的导入依赖"""
dependencies = set()
dependencies: set[str] = set()
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
tree = ast.parse(content)
content: str = f.read()
tree: ast.Module = ast.parse(content)
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
module = alias.name
module: str = alias.name
dependencies.add(module)
elif isinstance(node, ast.ImportFrom):
if node.module:
module = node.module
module: str = node.module
if node.level > 0:
# from .module import name → 依赖 pkg.module模块本身
pkg_dir = os.path.dirname(filepath)
pkg_rel = os.path.relpath(pkg_dir, src_root).replace(os.sep, '.').replace('/', '.')
pkg_dir: str = os.path.dirname(filepath)
pkg_rel: str = os.path.relpath(pkg_dir, src_root).replace(os.sep, '.').replace('/', '.')
if pkg_rel == '.':
pkg_rel = ''
dep_module: str
if pkg_rel:
dep_module = f'{pkg_rel}.{module}'
else:
@@ -59,51 +57,49 @@ def get_file_dependencies(filepath: str, src_root: str) -> set:
elif node.level > 0 and node.names:
# from . import name → 依赖 pkg.name子模块
for alias in node.names:
pkg_dir = os.path.dirname(filepath)
pkg_rel = os.path.relpath(pkg_dir, src_root).replace(os.sep, '.').replace('/', '.')
pkg_dir: str = os.path.dirname(filepath)
pkg_rel: str = os.path.relpath(pkg_dir, src_root).replace(os.sep, '.').replace('/', '.')
if pkg_rel == '.':
pkg_rel = ''
dep_module = f'{pkg_rel}.{alias.name}' if pkg_rel else alias.name
dep_module: str = f'{pkg_rel}.{alias.name}' if pkg_rel else alias.name
dependencies.add(dep_module)
except Exception as _e:
from lib.core.VLogger import get_logger as _vlog
from lib.constants.config import mode as _ConfigMode
if _ConfigMode == "strict":
raise
_vlog().warning(f"解析文件导入依赖失败: {_e}", "Exception")
return dependencies
def find_reachable_files_from_entries(src_root: str, entry_files: list) -> set:
def find_reachable_files_from_entries(src_root: str, entry_files: list[str]) -> set[str]:
"""从入口文件出发,找出所有可达的 .py 文件(通过导入关系)"""
all_files = {} # module_name -> filepath
all_files: dict[str, str] = {} # module_name -> filepath
for root, dirs, files in os.walk(src_root):
dirs[:] = [d for d in dirs if d not in ('__pycache__',)]
for file in files:
if file.endswith('.py'):
filepath = os.path.join(root, file)
rel = os.path.relpath(filepath, src_root)
module = os.path.splitext(rel)[0].replace(os.sep, '.').replace('/', '.')
filepath: str = os.path.join(root, file)
rel: str = os.path.relpath(filepath, src_root)
module: str = os.path.splitext(rel)[0].replace(os.sep, '.').replace('/', '.')
if filepath.endswith('__init__.py'):
all_files[module] = filepath
parent_module = module.rsplit('.', 1)[0] if '.' in module else module
parent_module: str = module.rsplit('.', 1)[0] if '.' in module else module
if parent_module not in all_files or not all_files[parent_module].endswith('__init__.py'):
all_files[parent_module] = filepath
elif module not in all_files:
all_files[module] = filepath
top_module = module.split('.')[0]
top_module: str = module.split('.')[0]
if top_module not in all_files:
all_files[top_module] = filepath
reachable = set()
queue = list(entry_files)
reachable: set[str] = set()
queue: list[str] = list(entry_files)
while queue:
current = queue.pop(0)
current: str = queue.pop(0)
if current in reachable:
continue
reachable.add(current)
deps = get_file_dependencies(current, src_root)
deps: set[str] = get_file_dependencies(current, src_root)
for dep in deps:
if dep in all_files and all_files[dep] not in reachable:
queue.append(all_files[dep])
@@ -117,43 +113,43 @@ def find_reachable_files_from_entries(src_root: str, entry_files: list) -> set:
return reachable
def topological_sort_files(py_files: list, src_root: str) -> list:
def topological_sort_files(py_files: list[str], src_root: str) -> list[str]:
"""对 Python 文件进行拓扑排序,确保依赖模块先被处理"""
# 构建模块名到文件路径的映射
module_to_file = {}
module_to_file: dict[str, str] = {}
for filepath in py_files:
rel = os.path.relpath(filepath, src_root)
module = os.path.splitext(rel)[0].replace(os.sep, '.').replace('/', '.')
rel: str = os.path.relpath(filepath, src_root)
module: str = os.path.splitext(rel)[0].replace(os.sep, '.').replace('/', '.')
module_to_file[module] = filepath
# 也添加顶层模块名
top_module = module.split('.')[0]
top_module: str = module.split('.')[0]
if top_module not in module_to_file:
module_to_file[top_module] = filepath
# 构建依赖图
graph = {f: set() for f in py_files}
graph: dict[str, set[str]] = {f: set() for f in py_files}
for filepath in py_files:
deps = get_file_dependencies(filepath, src_root)
deps: set[str] = get_file_dependencies(filepath, src_root)
for dep in deps:
if dep in module_to_file:
dep_file = module_to_file[dep]
dep_file: str = module_to_file[dep]
if dep_file != filepath: # 避免自依赖
graph[filepath].add(dep_file)
# 拓扑排序 (Kahn's algorithm)
in_degree = {f: 0 for f in py_files}
in_degree: dict[str, int] = {f: 0 for f in py_files}
for f, deps in graph.items():
for dep in deps:
if dep in in_degree:
in_degree[f] += 1
queue = [f for f, degree in in_degree.items() if degree == 0]
sorted_files = []
queue: list[str] = [f for f, degree in in_degree.items() if degree == 0]
sorted_files: list[str] = []
while queue:
# 按字母顺序处理同级别的文件,确保确定性
queue.sort()
current = queue.pop(0)
current: str = queue.pop(0)
sorted_files.append(current)
for f, deps in graph.items():
@@ -164,7 +160,7 @@ def topological_sort_files(py_files: list, src_root: str) -> list:
# 如果有环,添加剩余文件
if len(sorted_files) < len(py_files):
remaining = [f for f in py_files if f not in sorted_files]
remaining: list[str] = [f for f in py_files if f not in sorted_files]
sorted_files.extend(remaining)
return sorted_files