import t, c from stdint import * import memhub import string import stdlib import viperlib import hashlib import w32.win32file import w32.win32base import w32.fileio as fileio # ============================================================ # Utils - 工程工具函数 # # 提供 SHA1 计算和目录清理等通用工具函数。 # ============================================================ # 全局 mbuddy 指针 _mbuddy: memhub.MemBuddy | t.CPtr # ============================================================ # nibble_to_hex_char - 半字节转十六进制字符 # ============================================================ def nibble_to_hex_char(n: int, uppercase: int) -> t.CChar: """0-15 → '0'-'9'/'a'-'f'(uppercase=1 时用 'A'-'F')""" if n < 10: return '0' + n if uppercase != 0: return 'A' + (n - 10) return 'a' + (n - 10) # ============================================================ # bytes_to_hex - 字节序列转十六进制字符串 # ============================================================ def bytes_to_hex(pool: memhub.MemBuddy | t.CPtr, digest: bytes, nbytes: t.CSizeT, uppercase: int, use_malloc: int) -> str: """将 digest 前 nbytes 字节转为 2*nbytes 个十六进制字符(含 null 终止) use_malloc=1 时用 stdlib.malloc(调用方负责 free),否则 pool.alloc。 合并自 Utils.compute_sha1 / IncludesScanner.compute_file_sha1 的重复 hex 转换。 """ if digest is None or nbytes <= 0: return None hex_len: t.CSizeT = nbytes * 2 + 1 hex_buf: str = None if use_malloc != 0: hex_buf = stdlib.malloc(hex_len) else: hex_buf = pool.alloc(hex_len) if hex_buf is None: return None for i in range(nbytes): hi: int = (digest[i] >> 4) & 0xF lo: int = digest[i] & 0xF hex_buf[i * 2] = nibble_to_hex_char(hi, uppercase) hex_buf[i * 2 + 1] = nibble_to_hex_char(lo, uppercase) hex_buf[nbytes * 2] = '\0' return hex_buf # ============================================================ # compute_sha1 - 计算字符串的 SHA1,返回前 16 个十六进制字符 # # 使用 includes/hashlib 库的 sha1 类计算摘要,转为十六进制字符串。 # 参考 Projectrans.py 的 compute_sha1(hashlib.sha1().hexdigest()[:16])。 # ============================================================ def compute_sha1(pool: memhub.MemBuddy | t.CPtr, content: str) -> str: """计算字符串的 SHA1,返回前 16 个十六进制字符(用于文件命名)""" if content is None or pool is None: return None # 构造 sha1 对象(__init__ 会初始化 state/count/buf) # ctx 在栈上分配(alloca),函数内使用完即丢弃,无需堆分配 ctx: hashlib.sha1 | t.CPtr = hashlib.sha1() if ctx is None: return None # 计算摘要 ctx.update(content) digest: bytes = pool.alloc(hashlib.SHA1_DIGEST_LEN) if digest is None: return None ctx.final(digest) # 转为十六进制字符串(取前 8 字节 = 16 个十六进制字符) return bytes_to_hex(pool, digest, 8, 0, 0) # ============================================================ # is_vp_or_py_at - 检查路径段是否以 .vp/.py 后缀结尾 # ============================================================ def is_vp_or_py_at(p: str, start: t.CSizeT, plen: t.CSizeT) -> int: """检查 p[start, start+plen) 是否以 .vp/.py 后缀结尾(1=是,0=否)""" if p is None or plen <= 3: return 0 base: t.CSizeT = start + plen - 3 if p[base] == '.': if p[base + 1] == 'v' and p[base + 2] == 'p': return 1 if p[base + 1] == 'p' and p[base + 2] == 'y': return 1 return 0 # ============================================================ # JoinPath - 拼接两个路径段为 a/b # ============================================================ def JoinPath(a: str, b: str) -> str: """拼接路径段为 "a/b"(stdlib.malloc 分配,调用方负责 free),失败返回 None""" if a is None or b is None: return None a_len: t.CSizeT = string.strlen(a) b_len: t.CSizeT = string.strlen(b) total_len: t.CSizeT = a_len + b_len + 2 buf: str = stdlib.malloc(total_len) if buf is None: return None viperlib.snprintf(buf, total_len, "%s/%s", a, b) return buf # ============================================================ # read_file_to_buf - 读取整个文件到缓冲区(null 终止) # ============================================================ def read_file_to_buf(path: str, buf_size: t.CSizeT) -> bytes: """读取文件到 stdlib.malloc 缓冲区并保证 null 终止 返回缓冲区(调用方负责 free);打开失败/空文件/分配失败返回 None。 合并自 Phase2/StubMerger/IncludesScanner 的重复"打开+读取+终止"序列。 """ if path is None or buf_size <= 0: return None sf: fileio.File | t.CPtr = fileio.File(path, fileio.MODE.R) if sf.closed: return None buf: bytes = stdlib.malloc(buf_size) if buf is None: sf.close() return None br: t.CInt64T = sf.read_all(buf, buf_size) sf.close() if br <= 0: stdlib.free(buf) return None if br < buf_size: buf[br] = 0 else: buf[buf_size - 1] = 0 return buf # ============================================================ # PathToModuleName - 文件路径转模块名 # ============================================================ def PathToModuleName(path: str, strip_includes_prefix: int) -> str: """将文件路径转换为模块名(stdlib.malloc 分配,调用方负责 free) strip_includes_prefix=1 时跳过 includes/ 前缀(如 includes/ast/__init__.vp → ast)。 特殊处理 __init__.vp:pkg/__init__.vp → pkg(而非 pkg.__init__)。 合并自 StubMerger._PathToModuleName / IncludesScanner 内联模块名计算。 """ if path is None: return None plen: t.CSizeT = string.strlen(path) # 跳过 includes/ 或 includes\ 前缀(Windows 反斜杠兼容) prefix_len: t.CSizeT = 9 path_start: t.CSizeT = 0 if strip_includes_prefix != 0 and plen > prefix_len and string.strncmp(path, "includes", 8) == 0: psep_ch: t.CChar = path[8] if psep_ch == '/' or psep_ch == '\\': path_start = prefix_len plen = plen - prefix_len # 去掉 .vp 或 .py 后缀(.vp 为 Viper 源,.py 向后兼容) if is_vp_or_py_at(path, path_start, plen) != 0: plen -= 3 if plen == 0: return None # 检查是否以 /__init__ 或 \__init__ 结尾(包的 __init__.vp) # 同时支持 / 和 \ 两种分隔符(TPC 写入的路径可能混合使用) if plen > 9: tail_off: t.CSizeT = path_start + plen - 9 sep_char: t.CChar = path[tail_off] if (sep_char == '/' or sep_char == '\\') \ and path[tail_off + 1] == '_' and path[tail_off + 2] == '_' \ and path[tail_off + 3] == 'i' and path[tail_off + 4] == 'n' and path[tail_off + 5] == 'i' \ and path[tail_off + 6] == 't' and path[tail_off + 7] == '_' and path[tail_off + 8] == '_': # 去掉 分隔符+__init__ 后缀,plen 就是包名部分长度 plen = tail_off - path_start if plen == 0: return None # 复制并替换 / 为 . result: str = stdlib.malloc(plen + 1) if result is None: return None for ri in range(plen): ch: t.CChar = path[path_start + ri] if ch == '/' or ch == '\\': result[ri] = '.' else: result[ri] = ch result[plen] = '\0' return result # ============================================================ # CleanDir - 递归删除目录中所有文件和子目录(保留 dir_path 本身) # # 用于 --clean 选项:清理 temp_dir 和 output_dir 中的旧文件。 # SHA1 分片目录结构(如 temp/1c/、temp/7a/)需要递归清理, # 否则旧的截断文件(如 256KB text.ll)会被 Phase1 跳过逻辑复用。 # ============================================================ def CleanDir(dir_path: str) -> int: """递归删除目录中所有文件和子目录,保留 dir_path 本身,返回删除的文件数,-1 表示错误""" if dir_path is None: return -1 dir_len: t.CSizeT = string.strlen(dir_path) pattern: bytes = stdlib.malloc(dir_len + 8) if pattern is None: return -1 viperlib.snprintf(pattern, dir_len + 8, "%s/*", dir_path) find_data: w32.win32file.WIN32_FIND_DATAA | t.CPtr = stdlib.malloc(w32.win32file.WIN32_FIND_DATAA.__sizeof__()) if find_data is None: stdlib.free(pattern) return -1 string.memset(find_data, 0, w32.win32file.WIN32_FIND_DATAA.__sizeof__()) handle: w32.win32base.HANDLE = w32.win32file.FindFirstFileA(pattern, find_data) if handle == w32.win32base.INVALID_HANDLE_VALUE: stdlib.free(pattern) stdlib.free(find_data) return 0 deleted: int = 0 while 1: fname: str = find_data.cFileName if fname is not None: # 跳过 . 和 .. if fname[0] == '.': if fname[1] == '\0': fname = None elif fname[1] == '.' and fname[2] == '\0': fname = None if fname is not None: full_path: bytes = JoinPath(dir_path, fname) if full_path is not None: # 检查是否为目录(dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) is_dir: t.CUInt32T = find_data.dwFileAttributes & w32.win32file.FILE_ATTRIBUTE_DIRECTORY if is_dir != 0: # 递归清理子目录中的文件 sub_deleted: int = CleanDir(full_path) if sub_deleted > 0: deleted += sub_deleted # 删除空子目录(RemoveDirectoryA 要求目录为空) w32.win32file.RemoveDirectoryA(full_path) else: # 删除文件 if w32.win32file.DeleteFileA(full_path) != 0: deleted += 1 stdlib.free(full_path) if w32.win32file.FindNextFileA(handle, find_data) == 0: break w32.win32file.FindClose(handle) stdlib.free(pattern) stdlib.free(find_data) return deleted