Files
TransPyC/lib/Shell/TransPyC.py
2026-07-18 19:25:40 +08:00

646 lines
27 KiB
Python
Raw Permalink 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.
from __future__ import annotations
import sys
import os
import ast
import shlex
import datetime
import tempfile
import subprocess
from typing import Any
from lib.includes import c
from lib.includes import t
from lib.constants.config import (
DEFAULT_INPUT_FILE, DEFAULT_OUTPUT_FILE,
DEFAULT_COMPILE_COMMAND,
DEFAULT_COMPILE_FLAGS,
HELP_MESSAGE, DEFAULT_METADATA
)
from lib.utils.helpers import (
DetectFileType, ExecuteCommand,
AppendFileContent
)
from lib.core.VLogger import info, warning, error, success, debug
from lib.core.translator import Translator
from lib.core.Handles.HandlesBase import CTypeInfo
from lib.Shell.Serializer import SerializeSymbolTable, SymbolFile
from lib.Shell.Config import Config
class TransPyC:
"""TransPyC 主类"""
def __init__(self, code: str | None = None, debug: bool = False, triple: str | None = None, datalayout: str | None = None) -> None:
"""初始化TransPyC对象
Args:
code: 代码字符串
debug: 调试模式
triple: LLVM target triple (e.g. "x86_64-pc-linux-gnu")
datalayout: LLVM data layout string
"""
self.Args: dict[str, Any] = {}
self.HeaderFiles: list[str] = []
self.HelperFiles: list[str] = []
self.AnnotationFiles: list[str] = []
self.translator: Translator = Translator()
self.code: str | None = code
self.translator.Content = code
self.config: Config = Config()
self.translator.config = self.config
self.config.debug = debug
self.SymbolFiles: list[SymbolFile] = []
self.SliceLevel: int | str = 3
self.SliceCount: int = 0
self.SliceInfos: list[Any] = []
self.metadata: dict[str, Any] = DEFAULT_METADATA.copy()
self.triple: str | None = triple
self.datalayout: str | None = datalayout
self._source_module_sig_files: dict[str, Any] = {}
@staticmethod
def PreProcessSymbol(SymbolFile: SymbolFile, debug: bool = False) -> bytes | tuple[bytes, str]:
"""预处理符号文件,提取符号表并返回二进制数据
Args:
SymbolFile: SymbolFile对象包含文件路径和类型信息
debug: 是否启用调试模式如果为True返回 (bytes, DebugInfo)
对于Python文件DebugInfo包含p2c日志
对于C文件DebugInfo包含基本处理信息
Returns:
如果debug为False: bytes - 序列化后的符号表二进制数据
如果debug为True: (bytes, str) - (二进制数据, 调试日志)
"""
translator: Translator = Translator()
DebugLogs: list[str] | str = []
try:
# 读取文件内容
if SymbolFile.FilePath:
with open(SymbolFile.FilePath, 'r', encoding=SymbolFile.encoding) as f:
content: str = f.read()
elif SymbolFile.CodeString:
content = SymbolFile.CodeString
else:
raise ValueError("SymbolFile must have either FilePath or CodeString")
# 根据文件类型处理
FileType: str | None = SymbolFile.FileType
if not FileType and SymbolFile.FilePath:
# 从文件路径推断类型
if SymbolFile.FilePath.endswith('.py'):
FileType = 'py'
elif SymbolFile.FilePath.endswith('.c') or SymbolFile.FilePath.endswith('.h'):
FileType = 'c'
if FileType == 'py':
# Python文件处理
translator.OriginalLines = content.split('\n')
translator.Content = content
# 设置调试文件(如果需要)
if debug:
DebugFile: str = SymbolFile.FilePath.replace('.py', '.p2c') if SymbolFile.FilePath else 'debug.p2c'
translator.SetDebugFile(DebugFile)
# 清空调试文件
with open(DebugFile, 'w', encoding=SymbolFile.encoding) as f:
f.write('')
# 解析AST并提取符号
tree: ast.Module = ast.parse(content)
if debug:
with open(DebugFile, 'a', encoding=SymbolFile.encoding) as f:
f.write('=== AST Tree (Compact) ===\n')
f.write(ast.dump(tree))
f.write('\n\n')
# 遍历AST提取符号
for node in ast.iter_child_nodes(tree):
if isinstance(node, ast.ClassDef):
translator.SymbolTable[node.name] = {'type': 'struct', 'members': {}}
for item in node.body:
if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
VarName: str = item.target.id
TypeInfo: CTypeInfo | None = translator.TypeMergeHandler.GetCTypeInfo(item.annotation) if item.annotation else None
VarType: str = TypeInfo.ToString() if TypeInfo else 'unknown'
IsPtr: bool = TypeInfo.IsPtr if TypeInfo else False
translator.SymbolTable[node.name]['members'][VarName] = {
'type': VarType,
'IsPtr': IsPtr
}
elif isinstance(node, ast.FunctionDef):
translator.SymbolTable[node.name] = {'type': 'function'}
elif isinstance(node, ast.AnnAssign):
if isinstance(node.target, ast.Name):
VarName: str = node.target.id
VarType: str = 'unknown'
IsPtr: bool = False
if node.annotation:
try:
TypeInfo: CTypeInfo | None = translator.TypeMergeHandler.GetCTypeInfo(node.annotation)
VarType = TypeInfo.ToString() if TypeInfo else 'unknown'
IsPtr = TypeInfo.IsPtr if TypeInfo else False
except:
pass
translator.SymbolTable[VarName] = {
'type': 'variable',
'declared_type': VarType,
'IsPtr': IsPtr
}
if debug:
with open(DebugFile, 'a', encoding=SymbolFile.encoding) as f:
f.write('=== Symbol Table ===\n')
f.write(str(translator.SymbolTable))
f.write('\n\n')
# 读取调试日志
with open(DebugFile, 'r', encoding=SymbolFile.encoding) as f:
DebugLogs = f.read()
elif FileType == 'c':
# C文件处理 - 使用现有的ParseHelperFiles逻辑
translator.ParseHelperFiles([SymbolFile.FilePath], SymbolFile.encoding)
if debug:
DebugLogs = f"=== C File Symbol Extraction ===\n"
DebugLogs += f"File: {SymbolFile.FilePath}\n"
DebugLogs += f"=== Symbol Table ===\n"
DebugLogs += str(translator.SymbolTable)
DebugLogs += "\n\n"
else:
raise ValueError(f"Unsupported file type: {FileType}")
# 序列化符号表
BinaryData: bytes = SerializeSymbolTable(translator.SymbolTable)
if debug:
return BinaryData, DebugLogs
return BinaryData
except Exception as e:
error_msg: str = f"Error processing symbol file: {e}"
if debug:
return b'', error_msg
raise RuntimeError(error_msg)
def ParseArgs(self) -> None:
"""解析命令行参数"""
I: int = 1
while I < len(sys.argv):
if sys.argv[I] == '-f':
if I + 1 < len(sys.argv):
self.Args['Input'] = sys.argv[I + 1]
I += 2
else:
error('-f requires an argument')
sys.exit(1)
elif sys.argv[I] == '-o':
if I + 1 < len(sys.argv):
self.Args['Output'] = sys.argv[I + 1]
I += 2
else:
error('-o requires an argument')
sys.exit(1)
elif sys.argv[I] == '-wh':
if I + 1 < len(sys.argv):
self.HeaderFiles = sys.argv[I + 1:]
break
else:
error('-wh requires arguments')
sys.exit(1)
elif sys.argv[I] == '-debug':
if I + 1 < len(sys.argv):
self.Args['Debug'] = sys.argv[I + 1]
I += 2
else:
error('-debug requires an argument')
sys.exit(1)
elif sys.argv[I] == '-cc':
# 编译命令
if I + 1 < len(sys.argv):
self.Args['CompileCommand'] = sys.argv[I + 1]
I += 2
else:
error('-cc requires an argument')
sys.exit(1)
elif sys.argv[I] == '-cflags':
# 编译标志
if I + 1 < len(sys.argv):
self.Args['CompileFlags'] = sys.argv[I + 1]
I += 2
else:
error('-cflags requires an argument')
sys.exit(1)
elif sys.argv[I] == '-run':
# 是否运行生成的程序
self.Args['Run'] = True
I += 1
elif sys.argv[I] == '-args':
# 运行时参数
if I + 1 < len(sys.argv):
self.Args['RunArgs'] = sys.argv[I + 1]
I += 2
else:
error('-args requires an argument')
sys.exit(1)
elif sys.argv[I] == '-level' or sys.argv[I] == '-l':
if I + 1 < len(sys.argv):
LevelArg: str = sys.argv[I + 1]
if LevelArg.isdigit():
self.Args['SliceLevel'] = int(LevelArg)
elif LevelArg == 's':
self.Args['SliceLevel'] = 's'
else:
self.Args['SliceLevel'] = 3
I += 1
else:
self.Args['SliceLevel'] = 3
elif sys.argv[I] == '-h':
# 辅助文件,用于解析符号信息
if I + 1 < len(sys.argv):
# 收集所有后续参数作为辅助文件,直到遇到下一个以-开头的参数
while I + 1 < len(sys.argv) and not sys.argv[I + 1].startswith('-'):
self.HelperFiles.append(sys.argv[I + 1])
I += 1
I += 1
else:
error('-h requires arguments')
sys.exit(1)
elif sys.argv[I] == '-presym':
# 预处理符号文件,生成.symbin文件
# 格式: -presym <InputFile> -o <OutputFile> [-debug <DebugFile>]
if I + 1 < len(sys.argv):
InputFile: str = sys.argv[I + 1]
I += 2
# 解析可选参数
OutputFile: str | None = None
DebugFile: str | None = None
while I < len(sys.argv) and sys.argv[I].startswith('-'):
if sys.argv[I] == '-o':
if I + 1 < len(sys.argv):
OutputFile = sys.argv[I + 1]
I += 2
else:
error('-o requires an argument')
sys.exit(1)
elif sys.argv[I] == '-debug':
if I + 1 < len(sys.argv):
DebugFile = sys.argv[I + 1]
I += 2
else:
error('-debug requires an argument')
sys.exit(1)
else:
break
# 如果没有指定输出文件,使用默认名称
if not OutputFile:
OutputFile = InputFile.replace('.py', '.symbin').replace('.c', '.symbin')
# 执行预处理
self.Args['PreSym'] = {
'input': InputFile,
'output': OutputFile,
'debug': DebugFile
}
else:
error('-presym requires an argument')
sys.exit(1)
elif sys.argv[I] == '-e' or sys.argv[I] == '--encoding':
if I + 1 < len(sys.argv):
self.Args['Encoding'] = sys.argv[I + 1]
I += 2
else:
error('-e/--encoding requires an argument')
sys.exit(1)
elif sys.argv[I] == '--triple':
if I + 1 < len(sys.argv):
self.triple = sys.argv[I + 1]
I += 2
else:
error('--triple requires an argument')
sys.exit(1)
elif sys.argv[I] == '--datalayout':
if I + 1 < len(sys.argv):
self.datalayout = sys.argv[I + 1]
I += 2
else:
error('--datalayout requires an argument')
sys.exit(1)
else:
error(f'Unknown argument {sys.argv[I]}')
sys.exit(1)
# 检查是否有预处理符号文件的请求,如果有则跳过输入输出检查
if 'PreSym' not in self.Args and ('Input' not in self.Args or 'Output' not in self.Args):
error('缺少参数')
info(HELP_MESSAGE)
sys.exit(1)
def CompileAndRun(self, OutputFile: str) -> None:
"""编译并运行生成的C代码"""
# 获取编译命令
CompileCmd: str = self.Args.get('CompileCommand', DEFAULT_COMPILE_COMMAND)
CompileFlags: str = self.Args.get('CompileFlags', DEFAULT_COMPILE_FLAGS)
# 构建编译命令(使用列表形式,避免 shell 注入)
FullCompileCmd: list[str] = shlex.split(CompileCmd) + shlex.split(CompileFlags) + [OutputFile, '-o', f'{OutputFile}.exe']
info(f'编译命令: {" ".join(FullCompileCmd)}')
# 执行编译命令
try:
CompileResult: subprocess.CompletedProcess[str] | None = ExecuteCommand(FullCompileCmd)
if CompileResult:
success('编译成功!')
# 检查是否需要运行
if self.Args.get('Run', False):
RunArgs: str = self.Args.get('RunArgs', '')
RunCmd: list[str] = [f'{OutputFile}.exe'] + (shlex.split(RunArgs) if RunArgs else [])
info(f'运行: {" ".join(RunCmd)}')
# 执行运行命令
RunResult: subprocess.CompletedProcess[str] | None = ExecuteCommand(RunCmd)
if RunResult:
info('执行输出:')
info(RunResult.stdout)
if RunResult.stderr:
warning('执行错误:')
warning(RunResult.stderr)
except Exception as e:
error(f'编译失败: {e}')
def WriteDebugInfo(self, content: str) -> None:
"""写入调试信息到指定文件"""
# 获取调试文件路径
if 'Debug' in self.Args:
DebugFile: str = self.Args['Debug']
else:
DebugFile: str = self.Args.get('Output', '').replace('.c', '.p2c')
if DebugFile:
AppendFileContent(DebugFile, content)
def PythonToC(self, InputFile: str, OutputFile: str) -> None:
"""Python到C的转换"""
# 获取编码参数
encoding: str = self.Args.get('Encoding', 'utf-8')
# 设置调试文件路径(使用 .p2c 扩展名)
if 'Debug' in self.Args:
DebugFile: str = self.Args['Debug']
else:
# 默认使用 .p2c 文件
DebugFile: str = OutputFile.replace('.c', '.p2c')
# 设置翻译器的调试文件
self.translator.SetDebugFile(DebugFile)
# 先清空调试文件
with open(DebugFile, 'w', encoding=encoding) as f:
f.write('')
# 解析辅助文件,提取符号信息
if self.HelperFiles:
self.translator.ParseHelperFiles(self.HelperFiles, encoding)
# 加载注解文件,提取类型定义
if self.AnnotationFiles:
self.translator.LoadAnnotationFiles(self.AnnotationFiles)
# 获取编码参数
encoding = self.Args.get('Encoding', 'utf-8')
with open(InputFile, 'r', encoding=encoding) as F:
Content: str = F.read()
# 保存原始代码行
self.translator.OriginalLines = Content.split('\n')
self.translator.Content = Content
# 解析Python代码为AST
Tree: ast.Module = ast.parse(Content)
# 解析文件以提取类型信息(用于 EmbeddedAssignments
self.translator.ParsePythonFile(InputFile, encoding)
# 写入AST树信息压缩格式
with open(DebugFile, 'a', encoding=encoding) as f:
f.write('=== AST Tree (Compact) ===\n')
f.write(ast.dump(Tree))
f.write('\n\n')
Target: str = 'llvm'
CCode: str = self.translator.GenerateCCode(Tree, target=Target)
timestamp: str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
filename: str = os.path.basename(OutputFile)
encoding = self.Args.get('Encoding', 'utf-8')
with open(OutputFile, 'w', encoding=encoding) as F:
F.write(CCode)
F.write('\n')
def CToPython(self, InputFile: str, OutputFile: str, HeaderFiles: list[str]) -> None:
"""C到Python的转换暂时禁用"""
warning("C到Python的转换功能暂时禁用请使用Python到C的转换功能。")
def AddSymbol(self, SymbolFiles: SymbolFile | list[SymbolFile]) -> None:
"""添加符号文件
Args:
SymbolFiles: 符号文件或符号文件列表
"""
if isinstance(SymbolFiles, list):
self.SymbolFiles.extend(SymbolFiles)
else:
self.SymbolFiles.append(SymbolFiles)
# 收集文件路径和编码
HelperFiles: list[str] = []
encodings: list[str] = []
for SymbolFile in self.SymbolFiles:
FilePath: str | None = SymbolFile.FilePath
encoding: str = SymbolFile.encoding
if FilePath:
HelperFiles.append(FilePath)
encodings.append(encoding)
# 解析辅助文件
if HelperFiles:
# 暂时使用第一个文件的编码
encoding = encodings[0] if encodings else 'utf-8'
self.translator.ParseHelperFiles(HelperFiles, encoding)
def Convert(self, OutputFilename: str | None = None, SourceFilename: str | None = None, target: str = 'llvm') -> str | tuple[str, dict[str, Any]] | None:
"""转换代码
Args:
OutputFilename: 输出文件名(用于模板渲染)
SourceFilename: 源文件路径用于设置CurrentFile影响导入模块的查找
target: 目标代码类型,仅支持 'llvm'
Returns:
如果debug是字符串则返回生成的代码
如果debug是True则返回生成的代码和调试信息
"""
if not self.code:
error('未提供代码')
return None
from lib.core.Handles.HandlesImports import ImportHandle
ImportHandle._struct_Load_cache.clear()
# 如果提供了 SourceFilename先设置 CurrentFile
# 这样导入模块时会相对于源文件所在目录查找
if SourceFilename:
self.translator.CurrentFile = SourceFilename
# 加载注解文件,提取类型定义
if self.AnnotationFiles:
self.translator.LoadAnnotationFiles(self.AnnotationFiles)
# 保存原始代码行
self.translator.OriginalLines = self.code.split('\n')
# 解析Python代码为AST
Tree: ast.Module = ast.parse(self.code)
# 从源代码直接预扫描 import 语句,注册别名
# 注意:不能依赖 Tree 中的 import 节点,因为 ParsePythonFile 可能修改了 AST
if not getattr(self.translator, '_ImportedModules', None):
self.translator._ImportedModules = set()
_prescan_tree: ast.Module = ast.parse(self.code)
for _node in ast.iter_child_nodes(_prescan_tree):
if isinstance(_node, ast.Import):
for _alias in _node.names:
_name: str = _alias.name
if _name in ('c', 't'):
continue
self.translator._ImportedModules.add(_name)
if _alias.asname:
self.translator.SymbolTable.import_aliases[_alias.asname] = _name
elif isinstance(_node, ast.ImportFrom):
_module_name: str | None = _node.module
if _module_name and _module_name not in ('c', 't'):
self.translator._ImportedModules.add(_module_name)
for _alias in _node.names:
if _alias.asname:
self.translator.SymbolTable.import_aliases[_alias.asname] = f"{_module_name}.{_alias.name}"
# 解析代码以提取类型信息(用于 EmbeddedAssignments 和 TypedefAssignments
# 创建临时文件路径用于解析
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False, encoding='utf-8') as f:
f.write(self.code)
TempFile: str = f.name
try:
# UpdateCurrentFile=False 表示不修改 CurrentFile
if SourceFilename:
self.translator.ParsePythonFile(TempFile, UpdateCurrentFile=False)
else:
self.translator.ParsePythonFile(TempFile)
finally:
os.unlink(TempFile)
timestamp: str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
if self.triple:
self.translator.triple = self.triple
if self.datalayout:
self.translator.datalayout = self.datalayout
# 生成代码
code: str = self.translator.GenerateCCode(Tree, target='llvm')
filename: str = os.path.basename(OutputFilename) if OutputFilename else 'generated.ll'
# 处理调试信息
if isinstance(self.config.debug, str):
# 如果debug是字符串写入调试信息到文件
with open(self.config.debug, 'w', encoding='utf-8') as f:
f.write('=== Symbol Table ===\n')
f.write(str(self.translator.SymbolTable) + '\n\n')
f.write('=== AST Tree ===\n')
f.write(ast.dump(Tree, indent=2) + '\n\n')
f.write('=== Export Table ===\n')
f.write(self.translator.Exportable.to_json() + '\n\n')
f.write('=== Generated Code ===\n')
f.write(code + '\n')
return code
elif self.config.debug:
# 如果debug是True返回生成的代码和调试信息
DebugInfo: dict[str, Any] = {
'SymbolTable': self.translator.SymbolTable,
'ast_tree': ast.dump(Tree, indent=2),
'Exportable': self.translator.Exportable.to_dict(),
'generated_code': code
}
return code, DebugInfo
else:
# 如果debug是False只返回生成的代码
return code
def Run(self) -> None:
"""主运行函数"""
# 检查是否有命令行参数
if len(sys.argv) > 1:
# 有命令行参数,使用 ParseArgs 方法解析
self.ParseArgs()
# 检查是否有预处理符号文件的请求
if 'PreSym' in self.Args:
PresymConfig: dict[str, Any] = self.Args['PreSym']
InputFile: str = PresymConfig['input']
OutputFile: str = PresymConfig['output']
DebugFile: str | None = PresymConfig['debug']
# 获取编码参数
encoding: str = self.Args.get('Encoding', 'utf-8')
# 推断文件类型
FileType: str | None = 'py' if InputFile.endswith('.py') else 'c' if InputFile.endswith('.c') else None
# 创建 SymbolFile 对象,传入编码参数
SymbolFile: SymbolFile = SymbolFile(file=InputFile, type=FileType, encoding=encoding)
# 调用 PreProcessSymbol
if DebugFile:
BinaryData: bytes
DebugInfo: str
BinaryData, DebugInfo = TransPyC.PreProcessSymbol(SymbolFile, debug=True)
# 写入调试文件
with open(DebugFile, 'w', encoding=encoding) as f:
f.write(DebugInfo)
info(f"调试信息写入: {DebugFile}")
else:
BinaryData: bytes = TransPyC.PreProcessSymbol(SymbolFile, debug=False)
# 写入二进制符号文件
with open(OutputFile, 'wb') as f:
f.write(BinaryData)
success(f"符号文件生成: {OutputFile}")
return
InputFile: str = self.Args['Input']
OutputFile: str = self.Args['Output']
else:
# 没有命令行参数,使用默认的测试文件名称
InputFile: str = DEFAULT_INPUT_FILE
OutputFile: str = DEFAULT_OUTPUT_FILE
info('使用默认测试文件: test.py -> test.c')
FileType: str = DetectFileType(InputFile)
if FileType == '.py':
self.PythonToC(InputFile, OutputFile)
# 检查是否需要编译和运行
if 'CompileCommand' in self.Args or self.Args.get('Run', False):
self.CompileAndRun(OutputFile)
elif FileType == '.c':
self.CToPython(InputFile, OutputFile, self.HeaderFiles)
else:
error(f'不支持的文件类型: {FileType}')
sys.exit(1)