- Config.vp 解析 includes 数组全部元素为空格分隔 IncludesDirs(首元素仍存 IncludesDir 兼容单目录) - IncludesScanner 逐目录递归扫描合并,FileEntry.IncDir 记录所属 includes 根目录 - StubMerger 存储新增 IncDir 并行数组(MAX_INC_DIR_LEN=512),容量 256→1024 - Phase2 预注册/3.5 按需编译改用逐条目 IncDir 拼接源路径(空串回退 includes_dir) - HandlesImports import 未注册错误显示行内内容 + App 兄弟模块文件豁免 - 新增 TransPyV 自身 project.vpj
365 lines
13 KiB
Plaintext
365 lines
13 KiB
Plaintext
import t, c
|
||
from stdint import *
|
||
import memhub
|
||
import string
|
||
import stdio
|
||
import stdlib
|
||
import w32.win32file as win32file
|
||
import w32.win32base as win32base
|
||
import hashlib
|
||
import viperlib
|
||
import lib.Projectrans.Utils as Utils
|
||
import lib.core.VLogger as VLogger
|
||
|
||
|
||
# ============================================================
|
||
# IncludesScanner - includes 目录递归扫描
|
||
#
|
||
# 递归扫描 includes 目录,收集所有 .vp/.py 文件路径和 SHA1,
|
||
# 为 Phase1 stub 生成和 Phase2 stub 合并提供基础数据。
|
||
#
|
||
# 使用 Win32 FindFirstFileA/FindNextFileA 实现目录遍历。
|
||
# ============================================================
|
||
|
||
# 全局 mbuddy 指针
|
||
_mbuddy: memhub.MemBuddy | t.CPtr
|
||
|
||
# 文件路径最大长度
|
||
MAX_PATH_LEN: t.CDefine = 512
|
||
# 单次扫描最大文件数
|
||
MAX_FILES: t.CDefine = 1024
|
||
|
||
|
||
# ============================================================
|
||
# FileEntry - 文件条目(路径 + SHA1)
|
||
# ============================================================
|
||
@t.NoVTable
|
||
class FileEntry:
|
||
"""扫描到的文件条目"""
|
||
Path: str # 文件绝对路径
|
||
Sha1: str # SHA1 前16字符(16字节+null)
|
||
RelPath: str # 相对于 includes 根目录的路径
|
||
ModuleName: str # 模块名(如 "ast.parser")
|
||
IncDir: str # 所属 includes 根目录(多 includes 目录支持)
|
||
|
||
|
||
# ============================================================
|
||
# ScanResult - 扫描结果
|
||
# ============================================================
|
||
@t.NoVTable
|
||
class ScanResult:
|
||
"""扫描结果集合"""
|
||
Entries: FileEntry | t.CPtr # FileEntry 数组
|
||
Count: t.CInt
|
||
Capacity: t.CInt
|
||
|
||
|
||
# ============================================================
|
||
# create_scan_result - 创建扫描结果容器
|
||
# ============================================================
|
||
def create_scan_result(pool: memhub.MemBuddy | t.CPtr) -> ScanResult | t.CPtr:
|
||
"""创建扫描结果容器,预分配 MAX_FILES 个槽位"""
|
||
if pool is None:
|
||
return None
|
||
size: t.CSizeT = MAX_FILES * FileEntry.__sizeof__()
|
||
# 使用 stdlib.malloc 避免 mbuddy 池耗尽(与 StubMerger 保持一致)
|
||
entries: FileEntry | t.CPtr = stdlib.malloc(size)
|
||
if entries is None:
|
||
return None
|
||
string.memset(entries, 0, size)
|
||
|
||
result: ScanResult | t.CPtr = stdlib.malloc(ScanResult.__sizeof__())
|
||
if result is None:
|
||
return None
|
||
string.memset(result, 0, ScanResult.__sizeof__())
|
||
result.Entries = entries
|
||
result.Count = 0
|
||
result.Capacity = MAX_FILES
|
||
return result
|
||
|
||
|
||
# ============================================================
|
||
# add_file_entry - 向扫描结果添加文件条目
|
||
# ============================================================
|
||
def add_file_entry(result: ScanResult | t.CPtr,
|
||
pool: memhub.MemBuddy | t.CPtr,
|
||
abs_path: str, rel_path: str,
|
||
sha1: str, inc_dir: str) -> int:
|
||
"""添加文件条目到扫描结果,返回 0 成功(inc_dir 为所属 includes 根目录)"""
|
||
if result is None or pool is None:
|
||
return 1
|
||
if result.Count >= result.Capacity:
|
||
return 1
|
||
|
||
# 计算模块名:rel_path 中 / 替换为 .,去掉 .vp/.py 扩展名(__init__.vp 特判由 Utils 统一处理)
|
||
mod_buf: str = Utils.PathToModuleName(rel_path, 0)
|
||
if mod_buf is None:
|
||
return 1
|
||
|
||
# 获取条目地址
|
||
entry_size: t.CSizeT = FileEntry.__sizeof__()
|
||
entry_addr: t.CUInt64T = t.CUInt64T(result.Entries) + result.Count * entry_size
|
||
entry: FileEntry | t.CPtr = (FileEntry | t.CPtr)(t.CVoid(entry_addr, t.CPtr))
|
||
if entry is None:
|
||
return 1
|
||
|
||
# 复制路径字符串
|
||
abs_len: t.CSizeT = string.strlen(abs_path)
|
||
abs_buf: str = stdlib.malloc(abs_len + 1)
|
||
if abs_buf is None:
|
||
return 1
|
||
string.strcpy(abs_buf, abs_path)
|
||
entry.Path = abs_buf
|
||
|
||
# 复制 SHA1
|
||
sha1_buf: str = stdlib.malloc(17)
|
||
if sha1_buf is None:
|
||
return 1
|
||
string.strcpy(sha1_buf, sha1)
|
||
entry.Sha1 = sha1_buf
|
||
|
||
# 复制相对路径
|
||
rel_buf: str = stdlib.malloc(string.strlen(rel_path) + 1)
|
||
if rel_buf is None:
|
||
return 1
|
||
string.strcpy(rel_buf, rel_path)
|
||
entry.RelPath = rel_buf
|
||
|
||
# 复制模块名
|
||
entry.ModuleName = mod_buf
|
||
|
||
# 复制所属 includes 根目录(多 includes 目录支持,Phase2 拼路径用)
|
||
if inc_dir is not None:
|
||
inc_len: t.CSizeT = string.strlen(inc_dir)
|
||
inc_buf: str = stdlib.malloc(inc_len + 1)
|
||
if inc_buf is None:
|
||
return 1
|
||
string.strcpy(inc_buf, inc_dir)
|
||
entry.IncDir = inc_buf
|
||
|
||
result.Count += 1
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# compute_file_sha1 - 读取文件内容并计算 SHA1
|
||
#
|
||
# 与 Projectrans.py 一致:CRLF → LF 转换后计算 SHA1。
|
||
# Projectrans.py 用 Python 文本模式读取(自动 CRLF→LF),
|
||
# IncludesScanner 用二进制模式读取,需手动去除 \r。
|
||
# ============================================================
|
||
def compute_file_sha1(pool: memhub.MemBuddy | t.CPtr,
|
||
file_path: str) -> str:
|
||
"""读取文件内容并计算 SHA1 前16字符(CRLF→LF 后计算,与 Projectrans.py 一致)"""
|
||
if pool is None or file_path is None:
|
||
return None
|
||
|
||
# 读取文件内容(stdlib.malloc 分配,避免 mbuddy 池耗尽)
|
||
BUF_SIZE: t.CSizeT = 131072
|
||
buf: bytes = Utils.read_file_to_buf(file_path, BUF_SIZE)
|
||
if buf is None:
|
||
return None
|
||
|
||
# 原地去除 \r(CRLF → LF),与 Projectrans.py 文本模式读取一致
|
||
buf_len: t.CSizeT = string.strlen(buf)
|
||
write_pos: t.CSizeT = 0
|
||
read_pos: t.CSizeT = 0
|
||
while read_pos < buf_len:
|
||
ch: t.CChar = buf[read_pos]
|
||
if ch != '\r':
|
||
buf[write_pos] = ch
|
||
write_pos += 1
|
||
read_pos += 1
|
||
|
||
# 添加 null 终止符
|
||
buf[write_pos] = 0
|
||
|
||
# 计算 SHA1
|
||
ctx: hashlib.sha1 | t.CPtr = hashlib.sha1()
|
||
if ctx is None:
|
||
stdlib.free(buf)
|
||
return None
|
||
ctx.update(buf)
|
||
|
||
digest: bytes = stdlib.malloc(hashlib.SHA1_DIGEST_LEN)
|
||
if digest is None:
|
||
stdlib.free(buf)
|
||
return None
|
||
ctx.final(digest)
|
||
|
||
# 转为十六进制字符串(取前 8 字节 = 16 个十六进制字符,stdlib 分配保持原行为)
|
||
hex_buf: str = Utils.bytes_to_hex(pool, digest, 8, 0, 1)
|
||
stdlib.free(buf)
|
||
stdlib.free(digest)
|
||
return hex_buf
|
||
|
||
|
||
# ============================================================
|
||
# scan_directory_recursive - 递归扫描目录
|
||
#
|
||
# 使用 FindFirstFileA/FindNextFileA 遍历目录树,
|
||
# 对每个 .vp/.py 文件计算 SHA1 并添加到结果中。
|
||
# ============================================================
|
||
def scan_directory_recursive(pool: memhub.MemBuddy | t.CPtr,
|
||
root_dir: str,
|
||
rel_prefix: str,
|
||
result: ScanResult | t.CPtr,
|
||
inc_dir: str) -> int:
|
||
"""递归扫描目录,收集 .vp/.py 文件(inc_dir 为所属 includes 根目录)"""
|
||
if pool is None or root_dir is None or result is None:
|
||
return 1
|
||
|
||
# 构造搜索模式: root_dir/*
|
||
root_len: t.CSizeT = string.strlen(root_dir)
|
||
pattern: bytes = stdlib.malloc(root_len + 4)
|
||
if pattern is None:
|
||
return 1
|
||
viperlib.snprintf(pattern, root_len + 4, "%s/*", root_dir)
|
||
|
||
# 使用 FindFirstFileA 开始搜索
|
||
find_data: win32file.WIN32_FIND_DATAA | t.CPtr = stdlib.malloc(win32file.WIN32_FIND_DATAA.__sizeof__())
|
||
if find_data is None:
|
||
return 1
|
||
string.memset(find_data, 0, win32file.WIN32_FIND_DATAA.__sizeof__())
|
||
|
||
handle: win32base.HANDLE = win32file.FindFirstFileA(pattern, find_data)
|
||
if handle == win32base.INVALID_HANDLE_VALUE:
|
||
return 1
|
||
|
||
# 遍历所有文件和子目录
|
||
while True:
|
||
# 跳过 . 和 ..
|
||
fname: str = find_data.cFileName
|
||
if fname is not None:
|
||
fname0: t.CChar = fname[0]
|
||
if fname0 == '.':
|
||
fname1: t.CChar = fname[1]
|
||
if fname1 == '\0':
|
||
# "."
|
||
if win32file.FindNextFileA(handle, find_data) == 0:
|
||
break
|
||
continue
|
||
elif fname1 == '.':
|
||
fname2: t.CChar = fname[2]
|
||
if fname2 == '\0':
|
||
# ".."
|
||
if win32file.FindNextFileA(handle, find_data) == 0:
|
||
break
|
||
continue
|
||
|
||
# 检查是否为目录
|
||
is_dir: int = find_data.dwFileAttributes & win32file.FILE_ATTRIBUTE_DIRECTORY
|
||
|
||
# 构造完整路径
|
||
fname_len: t.CSizeT = string.strlen(fname)
|
||
full_path: bytes = Utils.JoinPath(root_dir, fname)
|
||
if full_path is None:
|
||
break
|
||
|
||
# 构造相对路径
|
||
prefix_len: t.CSizeT = 0
|
||
if rel_prefix is not None:
|
||
prefix_len = string.strlen(rel_prefix)
|
||
rel_path: bytes = None
|
||
if rel_prefix is not None and prefix_len > 0:
|
||
rel_path = Utils.JoinPath(rel_prefix, fname)
|
||
else:
|
||
rel_path = stdlib.malloc(fname_len + 1)
|
||
if rel_path is None:
|
||
break
|
||
string.strcpy(rel_path, fname)
|
||
if rel_path is None:
|
||
break
|
||
|
||
if is_dir != 0:
|
||
# 递归扫描子目录
|
||
scan_directory_recursive(pool, full_path, rel_path, result, inc_dir)
|
||
else:
|
||
# 检查是否为 .vp 或 .py 文件(.vp 为 Viper 源,.py 向后兼容)
|
||
is_py: int = 0
|
||
if Utils.is_vp_or_py_at(fname, 0, fname_len) != 0:
|
||
is_py = 1
|
||
|
||
if is_py != 0:
|
||
# 跳过 __pycache__ 目录下的文件
|
||
is_pycache: int = 0
|
||
if rel_prefix is not None:
|
||
if string.strcmp(rel_prefix, "__pycache__") == 0:
|
||
is_pycache = 1
|
||
|
||
if is_pycache == 0:
|
||
# 计算 SHA1
|
||
sha1: str = compute_file_sha1(pool, full_path)
|
||
if sha1 is not None:
|
||
add_file_entry(result, pool, full_path, rel_path, sha1, inc_dir)
|
||
fb: t.CChar | t.CPtr = VLogger.fmt_buf()
|
||
if fb is not None:
|
||
viperlib.snprintf(fb, 1024, "扫描: %s -> %s", rel_path, sha1)
|
||
VLogger.debug(fb, "scan")
|
||
|
||
# 继续搜索下一个文件
|
||
if win32file.FindNextFileA(handle, find_data) == 0:
|
||
break
|
||
|
||
win32file.FindClose(handle)
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# scan_includes - 扫描 includes 目录入口
|
||
#
|
||
# 扫描指定的 includes 目录,返回所有 .vp/.py 文件的路径和 SHA1。
|
||
# ============================================================
|
||
def scan_includes(pool: memhub.MemBuddy | t.CPtr,
|
||
includes_dirs: str) -> ScanResult | t.CPtr:
|
||
"""扫描 includes 目录(支持空格分隔的多目录),返回所有 .vp/.py 文件的扫描结果"""
|
||
if pool is None or includes_dirs is None:
|
||
return None
|
||
|
||
result: ScanResult | t.CPtr = create_scan_result(pool)
|
||
if result is None:
|
||
return None
|
||
|
||
# 解析空格分隔的多 includes 目录(如 "../../includes ./includes"),逐目录递归扫描合并
|
||
dirs_total: t.CSizeT = string.strlen(includes_dirs)
|
||
d_i: t.CSizeT = 0
|
||
while d_i < dirs_total:
|
||
while d_i < dirs_total and includes_dirs[d_i] == ' ':
|
||
d_i += 1
|
||
if d_i >= dirs_total:
|
||
break
|
||
d_start: t.CSizeT = d_i
|
||
while d_i < dirs_total and includes_dirs[d_i] != ' ':
|
||
d_i += 1
|
||
d_len: t.CSizeT = d_i - d_start
|
||
if d_len == 0:
|
||
continue
|
||
dir_buf: bytes = stdlib.malloc(d_len + 1)
|
||
if dir_buf is None:
|
||
continue
|
||
string.strncpy(dir_buf, includes_dirs + d_start, d_len)
|
||
dir_buf[d_len] = '\0'
|
||
scan_directory_recursive(pool, dir_buf, None, result, dir_buf)
|
||
stdlib.free(dir_buf)
|
||
|
||
return result
|
||
|
||
|
||
# ============================================================
|
||
# find_entry_by_module - 按模块名查找文件条目
|
||
# ============================================================
|
||
def find_entry_by_module(result: ScanResult | t.CPtr,
|
||
module_name: str) -> FileEntry | t.CPtr:
|
||
"""按模块名查找文件条目"""
|
||
if result is None or module_name is None:
|
||
return None
|
||
entry_size: t.CSizeT = FileEntry.__sizeof__()
|
||
for i in range(result.Count):
|
||
entry_addr: t.CUInt64T = t.CUInt64T(result.Entries) + i * entry_size
|
||
entry: FileEntry | t.CPtr = (FileEntry | t.CPtr)(t.CVoid(entry_addr, t.CPtr))
|
||
if entry is not None:
|
||
if entry.ModuleName is not None:
|
||
if string.strcmp(entry.ModuleName, module_name) == 0:
|
||
return entry
|
||
return None
|