Files
ViperC/App/lib/core/IncludesScanner.vp
T
TermiNexus 60493d6ae5 fix(compiler): 支持多 includes 目录,修复 G4 g4node 未注册 SHA1
- 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
2026-08-08 10:31:44 +08:00

365 lines
13 KiB
Plaintext
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 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