Files
TransPyC/TransPyV/App/main.py
2026-07-18 19:25:40 +08:00

511 lines
23 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import t, c
from stdint import *
import stdio
import string
import stdlib
import memhub
import argparse
import w32.win32console as w32cmd
import w32.fileio as fileio
import w32.win32file
import w32.win32base
import sys
import ast
import lib
import lib.core.VLogger as VLogger
import lib.core.Handles.HandlesTranslator as HandlesTranslator
import lib.core.Handles.HandlesStruct as HandlesStruct
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.core.BuildPipeline as BuildPipeline
import lib.core.IncludesScanner as IncludesScanner
import lib.core.StubMerger as StubMerger
import lib.core.Phase1 as Phase1
import lib.core.Phase2 as Phase2
import lib.Projectrans.Config as Config
import lib.Projectrans.Utils as Utils
import llvmlite
import subprocess
import viperlib
import hashlib
# 内存大小: fl_bytes=264 (33*8), POOL_SIZE-fl_bytes 必须是 2 的幂以避免浪费
# 1073742088 - 264 = 1073741824 (1024MB usable = 2^30)
# Phase1 翻译 87 个 includes 文件 + Phase2 翻译 30 个测试文件512MB 不足导致 Phase2 崩溃
POOL_SIZE: t.CDefine = 1073741824
# 语言编码页
CODE_PAGE: t.CDefine = 65001
# 源代码缓冲区大小1MB
SRC_BUF_SIZE: t.CDefine = 1048576
# ============================================================
# main: 命令行入口
#
# 解析参数 → 加载配置 → AST 解析 → LLVM IR 翻译 → 编译管线 → 可选执行
# ============================================================
def main() -> int:
w32cmd.SetConsoleOutputCP(CODE_PAGE)
w32cmd.SetConsoleCP(CODE_PAGE)
# 初始化 mbuddy 内存池
arena: bytes = stdlib.malloc(POOL_SIZE)
if arena is None:
stdio.printf("FAIL: malloc for arena failed\n")
return 1
mb: memhub.MemBuddy | t.CPtr = memhub.MemBuddy(arena, POOL_SIZE)
if mb is None:
stdio.printf("FAIL: MemBuddy init failed\n")
return 1
# 设置全局 mbuddy 指针sys 和 argparse 都需要)
sys._mbuddy = mb
argparse._mbuddy = mb
ast._mbuddy = mb
lib._mbuddy = (memhub.MemManager | t.CPtr)(mb)
Config._mbuddy = (memhub.MemManager | t.CPtr)(mb)
Utils._mbuddy = (memhub.MemManager | t.CPtr)(mb)
HandlesTranslator._mbuddy = mb
BuildPipeline._mbuddy = (memhub.MemManager | t.CPtr)(mb)
IncludesScanner._mbuddy = (memhub.MemManager | t.CPtr)(mb)
StubMerger._mbuddy = (memhub.MemManager | t.CPtr)(mb)
Phase1._mbuddy = (memhub.MemManager | t.CPtr)(mb)
Phase2._mbuddy = (memhub.MemManager | t.CPtr)(mb)
subprocess._mbuddy = mb
hashlib._mbuddy = (memhub.MemManager | t.CPtr)(mb)
lib.InitLib((memhub.MemManager | t.CPtr)(mb))
# 初始化 VLogger 并打印启动日志
log: VLogger.Logger | t.CPtr = VLogger.get_logger()
if log is not None:
log.info("TransPyV 启动")
# 初始化命令行参数Windows: GetCommandLineA, POSIX: /proc/self/cmdline
sys._init_argv()
# 创建参数解析器
parser: argparse.ArgumentParser | t.CPtr = argparse.ArgumentParser(
"TransPyV", "TransPyV 命令行参数解析", pool=mb)
# 注册参数(与 Projectrans.py main() 一致)
parser.add_argument("--project", None, argparse.STRING, 0, None, False,
argparse.STORE, "project.json 路径(默认查找当前目录)")
parser.add_argument("--src", None, argparse.STRING, 0, None, False,
argparse.STORE, "源文件目录(覆盖 project.json")
parser.add_argument("--temp", None, argparse.STRING, 0, None, False,
argparse.STORE, "声明接口临时目录(覆盖 project.json")
parser.add_argument("--output", None, argparse.STRING, 0, None, False,
argparse.STORE, "输出目录(覆盖 project.json")
parser.add_argument("--phase", None, argparse.STRING, 0, None, False,
argparse.STORE, "阶段: 1=生成声明, 2=翻译+编译, all=全部")
parser.add_argument("--cc", None, argparse.STRING, 0, None, False,
argparse.STORE, "LLVM 编译器命令(覆盖 project.json")
parser.add_argument("--clean", None, argparse.BOOL, 0, None, False,
argparse.STORE_TRUE, "清理 output 和 temp 目录")
parser.add_argument("--run", None, argparse.BOOL, 0, None, False,
argparse.STORE_TRUE, "编译成功后立即执行生成的可执行文件")
parser.add_argument("--rebuild-includes", None, argparse.BOOL, 0, None, False,
argparse.STORE_TRUE, "删除 includes.binary 预编译缓存并重新编译所有 includes")
parser.add_argument("--clear-cache", None, argparse.BOOL, 0, None, False,
argparse.STORE_TRUE, "清除 .transpyc_cache 全局缓存")
# 解析命令行参数
args: argparse.ParsedArgs | t.CPtr = parser.parse_args(sys._argc, sys._argv)
if args is None:
if log is not None:
log.error("参数解析失败", "argparse")
return 1
# 打印解析结果
if log is not None:
log.banner("TransPyV 参数解析结果")
# 字符串参数
project: str = args.get_str("project")
if project is not None:
stdio.printf(" --project: %s\n", project)
else:
stdio.printf(" --project: (未指定)\n")
src: str = args.get_str("src")
if src is not None:
stdio.printf(" --src: %s\n", src)
else:
stdio.printf(" --src: (未指定)\n")
temp: str = args.get_str("temp")
if temp is not None:
stdio.printf(" --temp: %s\n", temp)
else:
stdio.printf(" --temp: (未指定)\n")
output: str = args.get_str("output")
if output is not None:
stdio.printf(" --output: %s\n", output)
else:
stdio.printf(" --output: (未指定)\n")
phase: str = args.get_str("phase")
if phase is not None:
stdio.printf(" --phase: %s\n", phase)
else:
stdio.printf(" --phase: (未指定,默认 all)\n")
cc: str = args.get_str("cc")
if cc is not None:
stdio.printf(" --cc: %s\n", cc)
else:
stdio.printf(" --cc: (未指定)\n")
# 布尔参数
_gb_clean: INT = args.get_bool("clean")
if _gb_clean:
stdio.printf(" --clean: True\n")
else:
stdio.printf(" --clean: False\n")
if args.get_bool("run"):
stdio.printf(" --run: True\n")
else:
stdio.printf(" --run: False\n")
if args.get_bool("rebuild-includes"):
stdio.printf(" --rebuild-includes: True\n")
else:
stdio.printf(" --rebuild-includes: False\n")
if args.get_bool("clear-cache"):
stdio.printf(" --clear-cache: True\n")
else:
stdio.printf(" --clear-cache: False\n")
stdio.printf("\n参数解析完成。\n")
# 加载 project.vpj 配置
proj_loaded: int = 0
proj_path: str = project
if proj_path is not None:
if log is not None:
log.banner("工程配置")
if Config.Load_project_config(proj_path) == 0:
proj_loaded = 1
else:
stdio.printf(" 警告: 无法加载 project.vpj: %s\n", proj_path)
else:
# 尝试默认路径 project.vpj当前目录和上级目录
proj_path = "project.vpj"
if Config.Load_project_config(proj_path) == 0:
proj_loaded = 1
else:
proj_path = "../project.vpj"
if Config.Load_project_config(proj_path) == 0:
proj_loaded = 1
if proj_loaded == 1:
# 提取 project.vpj 所在目录,将相对路径解析为基于该目录的路径
proj_len: t.CSizeT = string.strlen(proj_path)
slash_pos: t.CSizeT = proj_len
for i in range(proj_len):
idx: t.CSizeT = proj_len - 1 - i
ch: t.CChar = proj_path[idx]
if ch == '/' or ch == '\\':
slash_pos = idx
break
proj_dir: str = ""
if slash_pos > 0 and slash_pos < proj_len:
proj_dir = stdlib.malloc(slash_pos + 1)
if proj_dir is not None:
string.memcpy(proj_dir, proj_path, slash_pos)
proj_dir[slash_pos] = '\0'
Config.resolve_paths(proj_dir)
Config.print_config()
# === --clean: 清理 temp 和 output 目录 ===
if _gb_clean:
stdio.printf("\n[clean] 清理临时目录...\n")
if Config.TempDir is not None:
n: int = Utils.CleanDir(Config.TempDir)
stdio.printf("[clean] %s: 删除 %d 个文件\n", Config.TempDir, n)
if Config.OutputDir is not None:
n2: int = Utils.CleanDir(Config.OutputDir)
stdio.printf("[clean] %s: 删除 %d 个文件\n", Config.OutputDir, n2)
# === --phase 参数控制 ===
# phase=1: 仅 stub 分离(生成 .stub.ll/.text.ll不编译
# phase=2: 仅 stub 合并 + 编译(跳过 stub 分离)
# phase=all: 两者都执行(默认)
phase_mode: str = phase
if phase_mode is None:
phase_mode = "all"
do_phase1: int = 0
do_phase2: int = 0
if phase_mode == "1" or phase_mode == "all":
do_phase1 = 1
if phase_mode == "2" or phase_mode == "all":
do_phase2 = 1
stdio.printf("[phase] 模式: %s (phase1=%d phase2=%d)\n", phase_mode, do_phase1, do_phase2)
# === Phase1: 扫描 includes按需翻译===
# 对 includes 目录中每个 .py 文件,若 stub 不存在则翻译并分离 stub
if do_phase1 != 0 and Config.IncludesDir is not None:
ph1_temp: str = Config.TempDir
if ph1_temp is None:
ph1_temp = "."
Phase1.RunPhase1(mb, Config.IncludesDir, ph1_temp, log)
# 如果仅 Phase1不执行 Phase2直接退出
if do_phase2 == 0:
stdio.printf("[phase] Phase1 完成,退出\n")
return 0
# AST 解析:如果指定了 --src 或 --project读取文件并解析为 AST 树
src_path: str = args.get_str("src")
# === 多文件项目模式 ===
# 当 --project 指定但 --src 未指定时,扫描 source_dir 下所有 .py 文件编译
if src_path is None and project is not None and Config.SourceDir is not None:
mf_temp: str = Config.TempDir if Config.TempDir is not None else "."
mf_output: str = Config.OutputDir if Config.OutputDir is not None else "."
mf_cc: str = Config.CompilerCmd if Config.CompilerCmd is not None else "llc"
mf_cc_flags: str = Config.CompilerFlags if Config.CompilerFlags is not None else "-filetype=obj -relocation-model=pic"
mf_linker: str = Config.LinkerCmd if Config.LinkerCmd is not None else "clang++"
mf_linker_flags: str = Config.LinkerFlags if Config.LinkerFlags is not None else "-lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -lgcc -Wl,--allow-multiple-definition"
mf_linker_out: str = Config.LinkerOutput if Config.LinkerOutput is not None else "app.exe"
mf_includes_bin: str = Config.get_includes_binary_dir()
mf_ret: int = Phase2.RunMultiFileProject(
mb, Config.SourceDir, mf_temp, mf_output,
mf_cc, mf_cc_flags, mf_linker, mf_linker_flags, mf_linker_out,
mf_includes_bin, Config.IncludesDir, do_phase1, do_phase2, log, args)
argparse.release(args)
if log is not None:
log.success("TransPyV 完成")
return mf_ret
# === 单文件模式:--src 指定时使用指定文件;--project 但无 SourceDir 时回退 ===
if src_path is None and project is not None:
if Config.SourceDir is not None:
# 构造入口路径: SourceDir/main.py
sd_len: t.CSizeT = string.strlen(Config.SourceDir)
path_buf: bytes = stdlib.malloc(sd_len + 16)
if path_buf is not None:
viperlib.snprintf(path_buf, sd_len + 16, "%s/main.py", Config.SourceDir)
src_path = path_buf
stdio.printf("[project] 入口文件: %s\n", src_path)
if src_path is not None:
if log is not None:
log.banner("AST 解析")
# 打开文件
f: fileio.File | t.CPtr = fileio.File(src_path, fileio.MODE.R)
if f.closed:
if log is not None:
log.error("无法打开文件", "fileio")
stdio.printf(" 路径: %s\n", src_path)
argparse.release(args)
return 1
# 分配源代码缓冲区
src_buf: bytes = stdlib.malloc(SRC_BUF_SIZE)
if src_buf is None:
if log is not None:
log.error("malloc for src_buf failed", "memhub")
f.close()
argparse.release(args)
return 1
# 读取文件内容
bytes_read: LONG = f.read_all(src_buf, SRC_BUF_SIZE)
f.close()
if bytes_read < 0:
if log is not None:
log.error("读取文件失败", "fileio")
stdio.printf(" 错误码: %d\n", bytes_read)
argparse.release(args)
return 1
# 添加 null 终止符,确保 strlen 和 SHA1 计算正确
if bytes_read < SRC_BUF_SIZE:
src_buf[bytes_read] = 0
else:
src_buf[SRC_BUF_SIZE - 1] = 0
if log is not None:
log.info("文件读取完成", "fileio")
stdio.printf("读取 %d 字节\n", bytes_read)
# 解析 AST拆分为词法+语法两阶段,便于调试)
ast._init_tables(mb)
lx: ast.Lexer | t.CPtr = ast.new_lexer(mb)
ast._lexer_init(lx, src_buf, mb)
tokens: ast.Token | t.CPtr = ast.tokenize(lx)
tree: ast.AST | t.CPtr = ast.parse_tokens(mb, tokens)
if tree is None:
if log is not None:
log.error("AST 解析失败", "ast")
argparse.release(args)
return 1
# === 翻译 AST → LLVM IR ===
if log is not None:
log.banner("LLVM IR 翻译")
tr: HandlesTranslator.Translator | t.CPtr = HandlesTranslator.Translator()
result: int = tr.translate(tree)
if result == 0:
IR_BUF_SIZE: t.CSizeT = 262144
ir_buf: bytes = stdlib.malloc(IR_BUF_SIZE)
if ir_buf is not None:
tr.dump_ir(ir_buf, IR_BUF_SIZE, llvmlite.OUTPUT_FULL)
# === 编译管线: .ll → .obj → .exe ===
if log is not None:
log.banner("编译管线")
# module_name 用源代码内容的 SHA1前 16 字符),与 Projectrans.py 一致
# output_name 从文件名推导(如 test.py -> test.exe保持可读性
module_name: str = Utils.compute_sha1(mb, src_buf)
if module_name is None:
module_name = "main"
output_name: str = "test_prog.exe"
# 从文件名提取 output_name去掉路径和 .py 扩展名)
src_len: t.CSizeT = string.strlen(src_path)
if src_len > 0:
# 找到最后一个 / 或 \ 之后的部分
base_start: t.CSizeT = src_len
i: t.CSizeT = src_len
while i > 0:
i -= 1
ch: t.CChar = src_path[i]
if ch == '/' or ch == '\\':
base_start = i + 1
break
if i == 0:
base_start = 0
# 找到 .py 扩展名
base_end: t.CSizeT = src_len
j: t.CSizeT = base_start
while j < src_len:
if src_path[j] == '.':
base_end = j
break
j += 1
# 输出名 = 文件名 + ".exe"
name_len: t.CSizeT = base_end - base_start
if name_len > 0:
out_len: t.CSizeT = name_len + 5
output_name = stdlib.malloc(out_len)
if output_name is not None:
# 手动复制文件名并追加 .exeviperlib.snprintf 不支持 %.*sstring 无 strcat
string.strncpy(output_name, src_path + base_start, name_len)
output_name[name_len] = '.'
output_name[name_len + 1] = 'e'
output_name[name_len + 2] = 'x'
output_name[name_len + 3] = 'e'
output_name[name_len + 4] = '\0'
# 使用 project.vpj 中的配置
temp_dir: str = Config.TempDir if Config.TempDir is not None else "."
output_dir: str = Config.OutputDir if Config.OutputDir is not None else "."
cc_cmd: str = Config.CompilerCmd if Config.CompilerCmd is not None else "llc"
cc_flags: str = "-filetype=obj -relocation-model=pic"
linker_cmd: str = Config.LinkerCmd if Config.LinkerCmd is not None else "clang++"
linker_flags: str = "-lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -lgcc -Wl,--allow-multiple-definition"
# --project 模式且未显式指定 --src 时:使用 config 中的 linker_output如 TransPyV.exe
# 显式指定 --src 时:使用从文件名推导的 output_name如 test.exe避免覆盖正在运行的 exe
if project is not None and Config.LinkerOutput is not None and args.get_str("src") is None:
linker_output: str = Config.LinkerOutput
else:
linker_output: str = output_name
# 计算 includes.binary 目录路径(链接时附加预编译 .obj
includes_binary_dir: str = Config.get_includes_binary_dir()
ir_len: t.CSizeT = string.strlen(ir_buf)
# (--phase 参数已在配置加载后处理,此处直接使用 do_phase1/do_phase2)
# === Phase1 stub 分离:使用 OUTPUT_STUB/TEXT 模式分别生成 stub.ll 和 text.ll ===
if do_phase1 != 0 and temp_dir is not None and module_name is not None:
if log is not None:
log.banner("stub 分离")
SF_IR_SIZE: t.CSizeT = 262144
td_len_sf: t.CSizeT = string.strlen(temp_dir)
# 保存 stub.ll
sf_stub_buf: bytes = stdlib.malloc(SF_IR_SIZE)
if sf_stub_buf is not None:
tr.dump_ir(sf_stub_buf, SF_IR_SIZE, llvmlite.OUTPUT_STUB)
sf_stub_len: t.CSizeT = string.strlen(sf_stub_buf)
sf_stub_path: bytes = stdlib.malloc(td_len_sf + 32)
if sf_stub_path is not None:
viperlib.snprintf(sf_stub_path, td_len_sf + 32, "%s/%s.stub.ll", temp_dir, module_name)
sf_f: fileio.File | t.CPtr = fileio.File(sf_stub_path, fileio.MODE.W)
if not sf_f.closed:
sf_f.write(sf_stub_buf, sf_stub_len)
sf_f.close()
stdlib.free(sf_stub_path)
stdlib.free(sf_stub_buf)
# 保存 text.ll
sf_text_buf: bytes = stdlib.malloc(SF_IR_SIZE)
if sf_text_buf is not None:
tr.dump_ir(sf_text_buf, SF_IR_SIZE, llvmlite.OUTPUT_TEXT)
sf_text_len: t.CSizeT = string.strlen(sf_text_buf)
sf_text_path: bytes = stdlib.malloc(td_len_sf + 32)
if sf_text_path is not None:
viperlib.snprintf(sf_text_path, td_len_sf + 32, "%s/%s.text.ll", temp_dir, module_name)
sf_tf: fileio.File | t.CPtr = fileio.File(sf_text_path, fileio.MODE.W)
if not sf_tf.closed:
sf_tf.write(sf_text_buf, sf_text_len)
sf_tf.close()
stdlib.free(sf_text_path)
stdlib.free(sf_text_buf)
# phase=1 模式:仅生成 stub不执行编译
if do_phase2 == 0:
if log is not None:
log.success("Phase1 完成(仅 stub 分离,跳过编译)")
argparse.release(args)
return 0
# === Phase2: 组合本地 stub + 依赖 stubs + 本地 text → 完整 IR ===
final_ir: bytes = ir_buf
final_ir_len: t.CSizeT = ir_len
if temp_dir is not None and module_name is not None:
if log is not None:
log.banner("IR 组合")
SF_COMBINED_SIZE: t.CSizeT = 4194304 # 4MB
combined_buf: bytes = stdlib.malloc(SF_COMBINED_SIZE)
if combined_buf is not None:
combined_len: t.CSizeT = StubMerger.BuildCombinedIR(temp_dir, module_name, combined_buf, SF_COMBINED_SIZE)
if combined_len > 0:
final_ir = combined_buf
final_ir_len = combined_len
else:
stdio.printf("[stub] BuildCombinedIR 失败,使用原始 IR\n")
stdlib.free(combined_buf)
br: BuildPipeline.BuildResult | t.CPtr = BuildPipeline.run_pipeline(
final_ir, final_ir_len, temp_dir, output_dir, module_name,
cc_cmd, cc_flags, linker_cmd, linker_flags, linker_output,
includes_binary_dir
)
if br is not None and br.Success == 1:
if log is not None:
log.success("编译管线完成")
stdio.printf("输出: %s/%s\n", output_dir, linker_output)
# --run 模式:执行生成的可执行文件
if args.get_bool("run"):
exe_path: bytes = stdlib.malloc(string.strlen(output_dir) + string.strlen(linker_output) + 2)
if exe_path is not None:
viperlib.snprintf(exe_path, 256, "%s/%s", output_dir, linker_output)
stdio.printf("[run] 执行: %s\n", exe_path)
r: subprocess.CompletedProcess | t.CPtr = subprocess.run(exe_path, False, False)
if r is not None:
stdio.printf("[run] 退出码: %d\n", r.returncode)
else:
if log is not None:
log.error("编译管线失败", "pipeline")
if br is not None and br.ErrorMsg is not None:
stdio.printf("[FATAL] 编译管线失败: %s\n", br.ErrorMsg)
sys.exit(1)
else:
if log is not None:
log.error("malloc for IR buffer failed", "memhub")
else:
if log is not None:
log.error("翻译失败", "translator")
argparse.release(args)
if log is not None:
log.success("TransPyV 完成")
return 0