Files
ViperC/App/lib/Stub/StubMerger/StubMergerBuild.vp
T
TermiNexus 7a9bc5ae75 chore(messages): TransPyV 运行时中文字符串英文化
把 TransPyV/App 下 57 个文件的运行时中文字符串(编译器报错/告警/日志、CLI 帮助与横幅、REPL 提示、Phase1/Phase2 流程日志、Codegen 各 Mixin 的类型与调用报错、Stub/Vpi 诊断等)共 1091 行译为英文。

口径:只改字符串字面量内部文字,行数零变化(+1092/-1092);printf 格式符与转义序列多重集合校验不变(_strcheck 异常 0);译文不引入撇号以免干扰单引号字面量解析。验证:三重重编 SUCCESS(136.20s)+ 全量 77/77 顺序回归通过 + VET 编译 SUCCESS / --search-paths 7 条 exit 0;新产物 help/日志输出已为英文。
2026-10-07 00:03:58 +08:00

579 lines
28 KiB
Plaintext

import lib.core.Gen.GenApi as GenApi
import t, c
from stdint import *
import memhub
import string
import stdlib
import mlirlite
import w32.win32file as win32file
import w32.win32base as win32base
import w32.fileio as fileio
import viperlib
import sys
import ast
import lib.core.VLogger as VLogger
import lib.core.IncludesScanner as IncludesScanner
import lib.core.Gen.Translator as HandlesTranslator
import lib.Phase.BuildPipeline as BuildPipeline
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.Projectrans.Utils as Utils
import lib.Projectrans.Config as Config
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.Stub.StubMerger.StubMergerTopo as StubMergerTopo
import lib.Stub.StubMerger.StubMergerIR as StubMergerIR
import lib.Stub.StubMerger as StubMerger
# _IsAppModule - whether a module name is an App source file (store entry with empty incdir)
# ============================================================
def _IsAppModule(mod_name: str) -> int:
"""Return whether mod_name is an App source file (store hit with empty incdir)."""
if mod_name is None: return 0
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None: return 0
for i in range(StubMerger._sha1_store_count):
midx: sizet = sizet(i) * 64
if string.strcmp(StubMerger._sha1_store_mod + midx, mod_name) == 0:
iidx: sizet = sizet(i) * StubMerger.MAX_INC_DIR_LEN
if StubMerger._sha1_store_incdir[iidx] == '\0': return 1
return 0
return 0
# _AppendDepsToWorklist - append space-separated module names to the worklist (dedup against worklist/processed).
# Returns the new length (>=0), or -1 on overflow/arg error (caller fail-fast).
def _AppendDepsToWorklist(worklist: bytes, wl_len: sizet, deps_str: str, processed: str) -> int:
if worklist is None or deps_str is None: return -1
cur_len: sizet = wl_len
dl: sizet = string.strlen(deps_str)
dpos: sizet = 0
while dpos < dl:
dstart: sizet = dpos
while dpos < dl and deps_str[dpos] != ' ': dpos += 1
dlen: sizet = dpos - dstart
if dpos < dl: dpos += 1
if dlen == 0: continue
dmod: bytes = stdlib.malloc(dlen + 1)
if dmod is None: continue
string.strncpy(dmod, deps_str + dstart, dlen)
dmod[dlen] = '\0'
in_wl: int = 0
if cur_len > 0:
if GenApi.is_module_imported(worklist, dmod) != 0: in_wl = 1
if in_wl == 0 and processed is not None:
if GenApi.is_module_imported(processed, dmod) != 0: in_wl = 1
if in_wl == 0:
if cur_len + dlen + 2 < StubMerger.REACH_WORKLIST_BUF:
if cur_len > 0 and worklist[cur_len - 1] != ' ':
worklist[cur_len] = ' '
cur_len = cur_len + 1
string.strcpy(worklist + cur_len, dmod)
cur_len = cur_len + dlen
worklist[cur_len] = ' '
cur_len = cur_len + 1
worklist[cur_len] = '\0'
else:
stdlib.free(dmod)
VLogger.error("dependency worklist overflow", "StubMerger")
return -1
stdlib.free(dmod)
return int(cur_len)
# _BuildReachableSha1Set - build the reachable SHA1 set (on-demand dep-graph scan; unreferenced includes are never read).
# BFS from app_deps: resolve each module to an includes file and read+hash+parse only reachable ones; returns count (>0 ok, 0 none, <0 fail).
def _BuildReachableSha1Set(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, reachable_set: str, app_deps: str = None, includes_dirs: str = None) -> int:
"""Build the reachable SHA1 set by on-demand dependency-graph scanning."""
if source_dir is None or temp_dir is None or reachable_set is None: return -1
if includes_dirs is None:
VLogger.error("includes_dirs is missing; cannot resolve the dependency graph on demand", "Reachable")
return -1
SRC_BUF_SIZE_R: sizet = 1048576
# Worklist (space-separated module names)
worklist: bytes = stdlib.malloc(StubMerger.REACH_WORKLIST_BUF)
if worklist is None: return -1
worklist[0] = '\0'
wl_len: sizet = 0
# 1. App scan (skipped when app_deps is non-empty; use the caller-provided initial queue directly)
if app_deps is None or app_deps[0] == '\0':
VLogger.info("App scan: resolving import dependencies of source_dir", "Reachable")
scan_res_r: IncludesScanner.ScanResult* = IncludesScanner.scan_includes(mb, source_dir)
if scan_res_r is not None:
es_r: sizet = IncludesScanner.FileEntry.__sizeof__()
for e_i_r in range(scan_res_r.Count):
e_r: IncludesScanner.FileEntry* = IncludesScanner.FileEntry*(uint64(scan_res_r.Entries) + uint64(e_i_r) * es_r)
if e_r is None or e_r.Path is None: continue
# File-level mark: reclaim the AST right after parsing (keeping all App ASTs resident would OOM)
app_mark: sizet = mb.mark()
sbuf: bytes = Utils.read_file_to_buf(e_r.Path, SRC_BUF_SIZE_R)
if sbuf is None:
mb.reset_to(app_mark)
continue
lx: ast.Lexer* = ast.new_lexer(mb)
tree: ast.AST* = None
if lx is not None:
ast._lexer_init(lx, sbuf, mb)
tokens: ast.Token* = ast.tokenize(lx)
tree = ast.parse_tokens(mb, tokens)
if tree is not None:
tr: HandlesTranslator.Translator* = HandlesTranslator.Translator()
if tr is not None:
tr._declare_only = 2
# Source package name from RelPath (relative to source_dir, supports subdirs),
# so relative imports like `from .xxx` resolve correctly
tr.CurrentPackage = GenApi.compute_package_from_relpath(mb, e_r.RelPath)
GenApi.set_current_file_type(e_r.Path)
GenApi.set_current_file_imports(e_r.Path)
GenApi.set_current_module_sha1(e_r.Sha1)
GenApi.clear_cdefine_constants()
GenApi.clear_ctypedefs()
GenApi.reset_visible_structs(mb, 0)
ret_a: int = tr.translate(tree)
if ret_a == 0:
# Container use detection: inject _list/_dict deps when an App file uses list/dict (bare annotation/ctor)
tr._inject_auto_container_visibility(mb, tree)
# Register the App file in the store (empty incdir) and fill deps; read by
# LookupIncludesDeps(app_sha1) when BFS expands its includes deps
if e_r.Sha1 is not None:
StubMergerStore.AppendToSha1MapStore(e_r.Sha1, e_r.RelPath)
StubMergerStore.PopulateIncludesDeps(e_r.Sha1, tr._imported_modules)
# Collect deps into the initial queue (dedup per module)
if tr._imported_modules is not None:
im_len_a: sizet = string.strlen(tr._imported_modules)
im_pos_a: sizet = 0
while im_pos_a < im_len_a:
im_start_a: sizet = im_pos_a
while im_pos_a < im_len_a and tr._imported_modules[im_pos_a] != ' ': im_pos_a += 1
im_mod_len: sizet = im_pos_a - im_start_a
if im_pos_a < im_len_a: im_pos_a += 1
if im_mod_len == 0: continue
im_buf_a: bytes = stdlib.malloc(im_mod_len + 1)
if im_buf_a is None: continue
string.strncpy(im_buf_a, tr._imported_modules + im_start_a, im_mod_len)
im_buf_a[im_mod_len] = '\0'
in_wl_a: int = 0
if wl_len > 0 and GenApi.is_module_imported(worklist, im_buf_a) != 0: in_wl_a = 1
if in_wl_a == 0:
if wl_len + im_mod_len + 2 < StubMerger.REACH_WORKLIST_BUF:
if wl_len > 0:
worklist[wl_len] = ' '
wl_len += 1
string.strcpy(worklist + wl_len, im_buf_a)
wl_len += im_mod_len
worklist[wl_len] = ' '
wl_len += 1
worklist[wl_len] = '\0'
stdlib.free(im_buf_a)
# Release Translator resources
if tr._global_names is not None: stdlib.free(tr._global_names)
if tr._nonlocal_names is not None: stdlib.free(tr._nonlocal_names)
stdlib.free(sbuf)
mb.reset_to(app_mark)
else:
# app_deps already provided (Phase2): use it directly as the initial queue
ad_len: sizet = string.strlen(app_deps)
if ad_len > 0 and ad_len < StubMerger.REACH_WORKLIST_BUF:
string.strcpy(worklist, app_deps)
wl_len = ad_len
else:
VLogger.error("app_deps is too long; cannot build the reachable set", "StubMerger")
stdlib.free(worklist)
return -1
# Trim the trailing extra space
if wl_len > 0 and worklist[wl_len - 1] == ' ':
worklist[wl_len - 1] = '\0'
wl_len -= 1
# 2. Pre-add base modules (builtins): builtins is always compiled in; _list/_dict are injected only for
# explicit list/dict annotations/ctors (list literals to t.CArray are array init and do not trigger _list).
wl_len = _AppendDepsToWorklist(worklist, wl_len, "builtins", None)
if wl_len < 0:
stdlib.free(worklist)
return -1
# 2b. --traceback: force the traceback runtime module into the reachable set (TPC: used_includes.add('traceback')).
# Its Install() symbol and the @llvm.global_ctors ctor come only from compiling this module, so a missing file is fail-fast.
if BuildPipeline._traceback_mode != 0:
tb_abs: t.CPtr = stdlib.malloc(8)
tb_rel: t.CPtr = stdlib.malloc(8)
tb_inc: t.CPtr = stdlib.malloc(8)
tb_found: int = -1
if tb_abs is not None and tb_rel is not None and tb_inc is not None:
tb_abs_slot: sizet* = (sizet*)(tb_abs)
tb_rel_slot: sizet* = (sizet*)(tb_rel)
tb_inc_slot: sizet* = (sizet*)(tb_inc)
tb_abs_slot[0] = 0
tb_rel_slot[0] = 0
tb_inc_slot[0] = 0
tb_found = IncludesScanner.ResolveModuleToPath(includes_dirs, "traceback", tb_abs, tb_rel, tb_inc)
if tb_abs is not None: stdlib.free(tb_abs)
if tb_rel is not None: stdlib.free(tb_rel)
if tb_inc is not None: stdlib.free(tb_inc)
if tb_found != 0:
stdlib.free(worklist)
VLogger.error(f"--traceback: traceback.vp not found in the includes dirs; cannot compile and link Install() (make sure the includes of project.vpj contains the repository includes directory; scanned: {includes_dirs})", "Traceback")
return -1
tb_wl: int = _AppendDepsToWorklist(worklist, wl_len, "traceback", None)
if tb_wl < 0:
stdlib.free(worklist)
return -1
wl_len = sizet(tb_wl)
# 3. Worklist BFS: module name -> filesystem resolution -> read+hash+parse imports
processed: bytes = stdlib.malloc(StubMerger.REACH_WORKLIST_BUF)
if processed is None:
stdlib.free(worklist)
return -1
processed[0] = '\0'
proc_len: sizet = 0
reachable_count: int = 0
wl_pos: t.CPtr = stdlib.malloc(8)
if wl_pos is None:
stdlib.free(worklist)
stdlib.free(processed)
return -1
wl_pos_slot: sizet* = (sizet*)(wl_pos)
wl_pos_slot[0] = 0
while wl_pos_slot[0] < wl_len:
pos: sizet = wl_pos_slot[0]
# Extract one module name (up to a space or the end)
name_start: sizet = pos
while pos < wl_len and worklist[pos] != ' ': pos += 1
name_len: sizet = pos - name_start
if pos < wl_len: pos += 1
wl_pos_slot[0] = pos
if name_len == 0: continue
# Copy the module name
mod_buf: bytes = stdlib.malloc(name_len + 1)
if mod_buf is None: continue
string.strncpy(mod_buf, worklist + name_start, name_len)
mod_buf[name_len] = '\0'
# Dedup against already processed
if GenApi.is_module_imported(processed, mod_buf) != 0:
stdlib.free(mod_buf)
continue
# Add to processed
if proc_len + name_len + 1 < StubMerger.REACH_WORKLIST_BUF:
string.strcpy(processed + proc_len, mod_buf)
proc_len = proc_len + name_len
processed[proc_len] = ' '
proc_len = proc_len + 1
processed[proc_len] = '\0'
else:
VLogger.error("dependency processed overflow", "StubMerger")
stdlib.free(mod_buf)
continue
# App intermediate node: only expand its includes deps, do not add to reachable_set
# (Phase B already compiled its .obj; adding it would recompile and duplicate symbols)
if _IsAppModule(mod_buf) != 0:
app_sha1: str = StubMergerStore.LookupSha1ByModName(mod_buf)
if app_sha1 is not None:
app_deps_str: str = StubMergerStore.LookupIncludesDeps(app_sha1)
if app_deps_str is not None:
ret_ap: int = _AppendDepsToWorklist(worklist, wl_len, app_deps_str, processed)
if ret_ap < 0:
stdlib.free(mod_buf)
stdlib.free(worklist)
stdlib.free(processed)
stdlib.free(wl_pos)
return -1
wl_len = sizet(ret_ap)
stdlib.free(mod_buf)
continue
# includes: resolve on demand via the filesystem (unreferenced includes are never touched)
out_abs: t.CPtr = stdlib.malloc(8)
out_rel: t.CPtr = stdlib.malloc(8)
out_inc: t.CPtr = stdlib.malloc(8)
if out_abs is None or out_rel is None or out_inc is None:
if out_abs is not None: stdlib.free(out_abs)
if out_rel is not None: stdlib.free(out_rel)
if out_inc is not None: stdlib.free(out_inc)
stdlib.free(mod_buf)
continue
# Defensive init of the output slots (the success path always writes all). The slot union must use (sizet*):
# t.CPtr* collapses to t.CPtr and [0] becomes a 1-byte access, truncating the pointer (0x2e crash root cause).
abs_slot: sizet* = (sizet*)(out_abs)
rel_slot: sizet* = (sizet*)(out_rel)
inc_slot: sizet* = (sizet*)(out_inc)
abs_slot[0] = 0
rel_slot[0] = 0
inc_slot[0] = 0
rcode: int = IncludesScanner.ResolveModuleToPath(includes_dirs, mod_buf, out_abs, out_rel, out_inc)
if rcode != 0:
# Builtin module (t/c/builtins internal libs) or not found: silently skip
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(mod_buf)
continue
abs_path: str = rawptr(abs_slot[0])
rel_path: str = rawptr(rel_slot[0])
inc_dir: str = rawptr(inc_slot[0])
# Read the file and compute SHA1 (CRLF->LF, matching IncludesScanner)
sha1_r2: str = IncludesScanner.compute_file_sha1(mb, abs_path)
if sha1_r2 is None:
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(mod_buf)
continue
# Already in the reachable set (parsed by an earlier BFS): only expand its deps
if StubMergerCollect._is_in_sha1_set(sha1_r2, reachable_set, reachable_count) != 0:
deps_ex: str = StubMergerStore.LookupIncludesDeps(sha1_r2)
if deps_ex is not None:
ret_ex: int = _AppendDepsToWorklist(worklist, wl_len, deps_ex, processed)
if ret_ex < 0:
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(mod_buf)
stdlib.free(worklist)
stdlib.free(processed)
stdlib.free(wl_pos)
return -1
wl_len = sizet(ret_ex)
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(mod_buf)
continue
# Add to the reachable set and register in Sha1Store (reachable includes only)
reachable_count = StubMergerIR._AddSha1ToSet(reachable_set, reachable_count, sha1_r2)
StubMergerStore.AddIncludeToSha1MapStore(sha1_r2, rel_path, inc_dir, mod_buf)
# File-level mark: reclaim the AST right after parsing imports (keeping all reachable includes
# ASTs resident would OOM the main pool)
scan_mark_r: sizet = mb.mark()
sbuf_r: bytes = Utils.read_file_to_buf(abs_path, SRC_BUF_SIZE_R)
if sbuf_r is None:
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(mod_buf)
mb.reset_to(scan_mark_r)
continue
lx_r: ast.Lexer* = ast.new_lexer(mb)
tree_r: ast.AST* = None
if lx_r is not None:
ast._lexer_init(lx_r, sbuf_r, mb)
tokens_r: ast.Token* = ast.tokenize(lx_r)
tree_r = ast.parse_tokens(mb, tokens_r)
if tree_r is None:
# parse_tokens keeps the error detail (line/col/message) only in the global channel, so print the unified
# parse traceback before the one-line log; without it a reachable-includes syntax error is undiagnosable.
GenApi.dump_parse_traceback(abs_path)
VLogger.error(f"reachable includes parse failed: {abs_path}", "Reachable")
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(sbuf_r)
stdlib.free(mod_buf)
stdlib.free(worklist)
stdlib.free(processed)
stdlib.free(wl_pos)
mb.reset_to(scan_mark_r)
return -1
tr_r2: HandlesTranslator.Translator* = HandlesTranslator.Translator()
if tr_r2 is None:
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(sbuf_r)
stdlib.free(mod_buf)
mb.reset_to(scan_mark_r)
continue
tr_r2.ModuleSha1 = sha1_r2
tr_r2._declare_only = 2
# Package name: RelPath relative to the includes root (supports relative imports like `from .xxx`)
tr_r2.CurrentPackage = GenApi.compute_package_from_relpath(mb, rel_path)
GenApi.set_current_file_type(abs_path)
GenApi.set_current_file_imports(abs_path)
GenApi.set_current_module_sha1(sha1_r2)
GenApi.clear_cdefine_constants()
GenApi.clear_ctypedefs()
GenApi.reset_visible_structs(mb, 0)
ret_r2: int = tr_r2.translate(tree_r)
if ret_r2 != 0:
VLogger.error(f"import dependency scan failed (reachable includes): {abs_path}", "Reachable")
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(sbuf_r)
if tr_r2._global_names is not None: stdlib.free(tr_r2._global_names)
if tr_r2._nonlocal_names is not None: stdlib.free(tr_r2._nonlocal_names)
stdlib.free(mod_buf)
stdlib.free(worklist)
stdlib.free(processed)
stdlib.free(wl_pos)
mb.reset_to(scan_mark_r)
return -1
# Inject containers on demand (append _list/_dict when includes use list/dict generics);
# inlined to break the _list/_dict cycle
tr_r2._inject_auto_container_visibility(mb, tree_r)
# Fill in-memory deps and write deps.txt (for dep-graph expansion and on-demand Phase B loading)
deps_r2: str = tr_r2._imported_modules
StubMergerStore.PopulateIncludesDeps(sha1_r2, deps_r2)
if deps_r2 is not None:
td_len_w: sizet = string.strlen(temp_dir)
deps_path_w: str = StubMergerCollect._sliced_path(temp_dir, td_len_w, sha1_r2, "deps.txt")
if deps_path_w is not None:
df_w: fileio.File* = fileio.File(deps_path_w, fileio.MODE.W)
if not df_w.closed:
dl_w: sizet = string.strlen(deps_r2)
if dl_w > 0: df_w.write(deps_r2, dl_w)
df_w.close()
stdlib.free(deps_path_w)
# Append deps to the worklist (dedup to prevent overflow)
ret_w: int = _AppendDepsToWorklist(worklist, wl_len, deps_r2, processed)
if ret_w < 0:
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(sbuf_r)
stdlib.free(mod_buf)
stdlib.free(worklist)
stdlib.free(processed)
stdlib.free(wl_pos)
mb.reset_to(scan_mark_r)
return -1
wl_len = sizet(ret_w)
else: VLogger.debug(f"no dependencies: {rel_path}", "Reachable")
# Free the Translator's C malloc resources and reclaim this file's AST
if tr_r2._global_names is not None: stdlib.free(tr_r2._global_names)
if tr_r2._nonlocal_names is not None: stdlib.free(tr_r2._nonlocal_names)
stdlib.free(sbuf_r)
mb.reset_to(scan_mark_r)
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(mod_buf)
stdlib.free(worklist)
stdlib.free(processed)
stdlib.free(wl_pos)
return reachable_count
# LoadFullIR - read {temp_dir}/{sha1}.full.ll into out_buf (④-c: merge phase uses the online FULL artifact).
# Contract: returns bytes written (sizet; 0=failure); out_buf is '\0'-terminated. full.ll is a self-contained single-module IR.
def LoadFullIR(temp_dir: str, sha1: str, out_buf: bytes, out_size: sizet) -> sizet:
"""Read {sha1}.full.ll into out_buf; returns the length (0 on failure)."""
if temp_dir is None or sha1 is None or out_buf is None or out_size == 0: return 0
td_len: sizet = string.strlen(temp_dir)
p: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "full.ll")
if p is None: return 0
f: fileio.File* = fileio.File(p, fileio.MODE.R)
stdlib.free(p)
out_buf[0] = '\0'
if f.closed: return 0
if f.size() >= out_size:
f.close()
VLogger.error("full.ll exceeds the IR buffer limit", "StubMerger")
return 0
rd: int64 = f.read_all(out_buf, out_size)
f.close()
if rd <= 0: return 0
out_buf[rd] = '\0'
return sizet(rd)
# LoadDepVpiIntoModule - load dependency modules' .vpi files and inject their declares into the current module.
# Call after Import handling and before translate_children; returns the number of successfully loaded .vpi files.
def LoadDepVpiIntoModule(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, imported_modules: str, from_imports: str, temp_dir: str, trans: HandlesTranslator.Translator*) -> int:
"""Load dependency modules' .vpi files and inject their declares into the current module."""
if pool is None or mod is None or temp_dir is None or trans is None: return 0
if imported_modules is None: return 0
# Reset the typedef recursion guard before injecting each module's stub: the CTypedef table is
# per-file, so stale visited state registers stdint typedefs only once and degrades param types (FuncTest root cause).
VpiReader.ResetVpiTypedefVisited()
# Reset the on-demand injection reentry table too: the target mod differs per module, so carrying it
# over would cache-hit and miss the declares this module needs.
VpiReader.ResetVpiInjectVisited()
td_len: sizet = string.strlen(temp_dir)
total_len: sizet = string.strlen(imported_modules)
success_count: int = 0
# Iterate each module name in imported_modules (space-separated)
ci: sizet = 0
while ci < total_len:
# Skip leading spaces
while ci < total_len and imported_modules[ci] == ' ': ci += 1
if ci >= total_len: break
# Extract the module name (up to the next space or end of string)
word_start: sizet = ci
while ci < total_len and imported_modules[ci] != ' ': ci += 1
word_len: sizet = ci - word_start
if word_len == 0: continue
# Copy the module name into a temp buffer and null-terminate it
mod_nm_buf: str = stdlib.malloc(word_len + 1)
if mod_nm_buf is None: continue
string.strncpy(mod_nm_buf, imported_modules + word_start, word_len)
mod_nm_buf[word_len] = '\0'
# Look up the module's SHA1 via LookupSha1ByModName
sha1_val: str = StubMergerStore.LookupSha1ByModName(mod_nm_buf)
# Import alias (import X as Y, e.g. HT->lib.core.Handles.HandlesTranslator): when exact/package
# lookups fail, resolve the alias via from_imports; otherwise its vpi/stub is not injected and cross-module symbols fail.
if sha1_val is None and from_imports is not None: sha1_val = StubMergerStore.LookupSha1ByAlias(from_imports, mod_nm_buf)
# Exact lookup failed: package fallback (e.g. "ast" -> "ast.__init__") is already handled inside LookupSha1ByModName
if sha1_val is not None:
# Build the .vpi file path
vpi_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1_val, "vpi")
if vpi_path is not None:
# Check that the .vpi file exists (is readable)
vf: fileio.File* = fileio.File(vpi_path, fileio.MODE.R)
if not vf.closed:
vf.close()
# Parse the .vpi via VpiReader and inject declares (the error context frame is kept by the caller HandlesTranslator)
ret: int = VpiReader.ParseVpiIntoModule(pool, mod, vpi_path, trans)
if ret == 1: success_count += 1
stdlib.free(vpi_path)
# sha1_val points into internal storage, no free needed
stdlib.free(mod_nm_buf)
return success_count