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: # 手动复制文件名并追加 .exe(viperlib.snprintf 不支持 %.*s,string 无 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: str = StubMerger._sliced_path(temp_dir, td_len_sf, module_name, "stub.ll") if sf_stub_path is not None: 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: str = StubMerger._sliced_path(temp_dir, td_len_sf, module_name, "text.ll") if sf_text_path is not None: 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