Files
Viper_VET/App/lib/VJson.vp
T
TermiNexus 02df100204 chore(vet): 初始化 VET 仓(Viper Environment Tool)
首次入库,远端 https://git.gvsds.com/GVSDS/Viper_VET 。

19 个文件全部为源码/配置,无构建产物:
  App/main.vp、App/lib/*.vp(VCache / VCheck / VCli / VDirs / VFs / VGuard /
    VGuid / VJson / VLog / VPe / VProc / VScan / __init__)
  1.json、LICENSE、README.md、project.vpj、.gitignore

既有 .gitignore 已覆盖本仓的产物目录(**/output/、**/temp/、**/.tpv_build/)
与 *.exe / *.obj / *.o / *.img / *.iso / __pycache__/ / .cache/;
实测 `git add -A --dry-run` 仅 19 个文件、零产物混入,故未改动 .gitignore。
2026-09-29 15:20:13 +08:00

259 lines
14 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 stdlib
import memhub
import string
import json
import lib.VFs as VFs
import lib.VLog as VLog
import lib.VCache as VCache
# ============================================================
# VJson - JSON 构建(单条对象 / 整轮数组,全部走仓库 json 库)
#
# 本轮改造(需求 ①/②):
# * 不再手工拼 `{"k":"v",...}`、不再手写 JsonEscape:条目改为用库的
# json.object + json.object_set + json.string_val 构造,再由 json.write 序列化;
# 转义(\ " \n \r \t)与 UTF-8 原样透传由库保证(includes/json/__writer.vp:39-66)。
# * 输出为**紧凑单行**:库的 write 忽略 pretty 参数、且从不写空白/换行
# (includes/json/__writer.vp:69-137、:151-155),故整轮数组与其中每个对象在同一行。
# * 数组框架('[' / ',' / ']' / 结尾换行)仍由本模块拼接:库的 write 只写单个输出缓冲
# (缺省 cap = 4096,includes/json/__writer.vp:143-148),整轮数组(最多 256 条)
# 无法一次交给库写,故逐条 write 再拼接。
#
# 库接口(静态核验,详见交付报告「能力与用法清单」):
# json.object(pool) -> rawptr[JsonValue]
# json.string_val(pool, s: str) -> rawptr[JsonValue] # 内部复制一份字符串
# json.object_set(pool, obj, key: str, val) -> 无返回 # 追加序 = 输出键序
# json.write(pool, val, pretty: bool, cap: t.CSizeT = 4096) -> str
# # pretty 被忽略,恒紧凑单行;超出 cap 返回 None
# json.parse(pool, src: str) -> rawptr[JsonValue]
# pool 形参类型 rawptr[memhub.MemManager](vtable 基类)⇒ MemBuddy / MemPool 都可传
# (先例:includes/requests/https.vp:823,883 传 rawptr[memhub.MemPool])。
#
# 用途约定:本模块声明的 Json_Buf 同时供 stdout 与实际写盘的缓存文件使用
# (调用点见 VScan.RunScanRound),二者逐字节相同。
#
# 失败处理(fail-closed):条目在分配前先过 Json_Pool.AllocOk(Need) 预检(BuildEntry),
# 预检不足即跳过该条目(返回 None);逐条跳过原因(含路径)仅 --verbose 输出,默认由
# EmitJson 的轮末聚合("本轮有 N 条被跳过")给出计数——库内部的 pool.alloc 不会在本模块
# 路径上走到 includes/memhub.vp:262-272 的 printf + exit(1);跳过不写 Json_Buf,
# 故本轮数组仍是合法单行 JSON(末尾 ']' 与换行照常闭合)。
#
# 依赖:VCache(CacheCount/CacheExe… 读表)、VFs(AnsiToUtf8/CopyRaw)、VLog。
#
# 内存归属(进程生命周期,均不释放):
# * Json_Buf / Conv_Buf 由 Vet_Mb(main 注入的 MemBuddy)分配;
# * Json_Arena 由 stdlib.malloc 分配;
# * Json_Pool(MemPool 管理器对象)由 memhub.NewMemPool 在堆上就地构造
# ——JsonInit 的栈帧在返回后失效,管理器对象必须在堆上(见 memhub.vp 的
# NewMemPool 注释与 Debugger/memhub-lifetime.md)。
# ============================================================
JSON_BUF_CAP: t.CDefine = 1048576 # 一轮 JSON 数组缓冲 1 MiB
JSON_POOL_CAP: t.CDefine = 65536 # 逐条序列化工作池 64 KiB(每条目 reset 复用)
JSON_ENTRY_MAX: t.CDefine = 4096 # 单条上限 = json 库 write 的缺省 cap(4096)
JSON_ENTRY_SLACK: t.CDefine = 512 # 单条固定语法开销上界(实测 77:键/值引号 24 + 键名 40 + 冒号 6 + 逗号 5 + 大括号 2)
# 单条序列化在 Json_Pool 上的固定开销上界(BuildEntry 的 AllocOk 预检的一部分)。
# 组成(一次 BuildEntry 的全部池分配;条目级 reset 之后 offset=0):
# * 7 个 JsonValue 节点(1 个对象 + 6 个字符串值)× 单节点上界 72 字节 = 504
# (JsonValue 9 字段 × 8 字节上界,includes/json/__init__.vp:23-39,与 VCache
# 解析树的估算口径一致);
# * 6 个键名副本(含 NUL)46 字节:id 3 + executable 11 + version 8 + arch 5
# + source 7 + displayName 12(includes/json/__init__.vp:225-229);
# * json.write 的输出缓冲 4096 字节(includes/json/__writer.vp:143-148,缺省 cap);
# * 余量 512 字节:6 个值副本各带一个结尾 NUL,加条内 20 次池分配按
# MEMHUB_ALIGN=8 向上取整的浪费(每次 ≤7,合计 ≤140)。
# 合计 504 + 46 + 4096 + 512 = 5158。
JSON_ENTRY_POOL_FIXED: t.CDefine = 5158
# 单条里 executable / displayName 的取值长度上界:二者都由 Conv_Buf(VFs.CONV_CAP 字节)
# 拷出(WideCharToMultiByte 以 Cap 封顶并写结尾 NUL,失败时 VFs.CopyRaw 写空串),
# 故每个值 ≤ CONV_CAP-1 字符。写 2047 = VFs.CONV_CAP(2048) - 1 的字面量:模块级
# t.CDefine 不引用跨模块常量,VFs.CONV_CAP 只在函数体内参与算术。
JSON_ENTRY_CONV_MAX: t.CDefine = 2047
# 由 App/main.vp 注入
_mbuddy: rawptr[memhub.MemBuddy]
Json_Buf: bytes = None # 整轮数组文本(stdout 与缓存文件共用)
Json_Len: int = 0 # Json_Buf 的有效字节数
Conv_Buf: bytes = None # ANSI → UTF-8 转换缓冲
Json_Arena: bytes = None # 序列化工作池 arena(stdlib.malloc,常驻)
Json_Pool: rawptr[memhub.MemPool] # 序列化工作池(堆上就地构造;见 JsonInit)
# 分配 JSON 静态缓冲与序列化工作池(进程生命周期内复用)。返回 1=成功,0=失败
def JsonInit() -> int:
global Json_Buf, Conv_Buf, Json_Arena, Json_Pool
Json_Buf = _mbuddy.alloc(JSON_BUF_CAP)
Conv_Buf = _mbuddy.alloc(VFs.CONV_CAP)
Json_Arena = stdlib.malloc(JSON_POOL_CAP)
if Json_Buf is None or Conv_Buf is None or Json_Arena is None:
return 0
# 池对象走堆上就地构造(memhub.NewMemPool):Json_Pool 要被 EmitJson/BuildEntry
# 跨帧使用,而本函数的栈帧在返回时即失效——构造表达式在这里会把池对象的存储
# 落在本帧栈上(IR:VJson 的 alloca %MemPool + __before_init__ + __init__)。
# arena 由 stdlib.malloc 常驻;池对象同样常驻,进程生命周期内不 free。
Pool: rawptr[memhub.MemPool] = memhub.NewMemPool(Json_Arena, JSON_POOL_CAP)
if Pool is None:
return 0
Json_Pool = Pool
return 1
# 供 VScan 写 stdout / VCache 写缓存文件读取结果
def JsonBuf() -> bytes:
return Json_Buf
def JsonLen() -> int:
return Json_Len
def JReset():
global Json_Len
Json_Len = 0
if Json_Buf is not None:
Json_Buf[0] = 0
# 把字面量/已序列化文本追加到 Json_Buf(容量不足返回 0,不写穿)
def JWrite(S: str) -> int:
global Json_Len
if S is None or Json_Buf is None:
return 0
n: t.CSizeT = string.strlen(S)
if t.CSizeT(Json_Len) + n + 1 > t.CSizeT(JSON_BUF_CAP):
return 0
i: t.CSizeT = 0
while i < n:
Json_Buf[Json_Len + t.CInt(i)] = S[i]
i += 1
Json_Len += t.CInt(n)
Json_Buf[Json_Len] = 0
return 1
# 构造第 Idx 条 JSON 对象并交给库序列化为紧凑单行文本。
# 返回该条文本(分配在 Json_Pool 上,长度 < JSON_ENTRY_MAX);None=已跳过。
# 每条跳过的具体原因(含被处理路径)仅在 --verbose 时逐条给出;默认安静模式下由
# EmitJson 的轮末聚合("本轮有 N 条被跳过")给出计数,既不静默异常也不逐路径刷屏。
def BuildEntry(Idx: int) -> str:
# 池容量预检(fail-closed):本条在 Json_Pool 上的全部分配必须先确认放得下,
# 否则库内部的 pool.alloc 会走到 includes/memhub.vp:262-272 的 printf + exit(1)。
# 预检在 EmitJson 的条目级 reset 之后执行(池 offset=0),故单次 AllocOk 覆盖整条;
# Need 由固定开销 + 全部值的长度上界构成,是条内池占用(含 20 次分配的 8 字节对齐
# 浪费)的上界,推导见 JSON_ENTRY_POOL_FIXED 与 JSON_ENTRY_CONV_MAX 的注释。
ValNeed: t.CSizeT = t.CSizeT(JSON_ENTRY_CONV_MAX) + t.CSizeT(JSON_ENTRY_CONV_MAX)
ValNeed = ValNeed + string.strlen(VCache.CacheId(Idx))
ValNeed = ValNeed + string.strlen(VCache.CacheVer(Idx))
ValNeed = ValNeed + string.strlen(VCache.CacheArch(Idx))
ValNeed = ValNeed + string.strlen(VCache.CacheSrc(Idx)) # source("workspace"/"check")
Need: t.CSizeT = t.CSizeT(JSON_ENTRY_POOL_FIXED) + ValNeed
if Json_Pool.AllocOk(Need) == 0:
VLog.VerboseFmtIS(" 警告: JSON 工作池不足(本条需 %zu 字节),已跳过: %s\n",
Need, VCache.CacheExe(Idx))
return None
Obj: rawptr[json.JsonValue] = json.object(Json_Pool)
if Obj == None:
VLog.VerboseStr(" 警告: JSON 对象分配失败(工作池不足),已跳过该条目\n")
return None
# 键插入顺序 = 输出顺序(库按 child→next 链表输出,见 __writer.vp:92-107)。
# executable / displayName 按既有约定转 UTF-8,且**转换后立即交给库复制**:
# Conv_Buf 随后要复用于 displayName,晚一步用就会让 executable 串被覆盖。
json.object_set(Json_Pool, Obj, "id", json.string_val(Json_Pool, VCache.CacheId(Idx)))
if VFs.AnsiToUtf8(VCache.CacheExe(Idx), Conv_Buf, VFs.CONV_CAP) == 0:
VFs.CopyRaw(Conv_Buf, VFs.CONV_CAP, VCache.CacheExe(Idx))
json.object_set(Json_Pool, Obj, "executable", json.string_val(Json_Pool, str(Conv_Buf)))
json.object_set(Json_Pool, Obj, "version", json.string_val(Json_Pool, VCache.CacheVer(Idx)))
json.object_set(Json_Pool, Obj, "arch", json.string_val(Json_Pool, VCache.CacheArch(Idx)))
# source 取自缓存条目(扫盘="workspace";check 通道="check",见 VCache.Cache_Src)
json.object_set(Json_Pool, Obj, "source", json.string_val(Json_Pool, VCache.CacheSrc(Idx)))
if VFs.AnsiToUtf8(VCache.CacheDisp(Idx), Conv_Buf, VFs.CONV_CAP) == 0:
VFs.CopyRaw(Conv_Buf, VFs.CONV_CAP, VCache.CacheDisp(Idx))
json.object_set(Json_Pool, Obj, "displayName", json.string_val(Json_Pool, str(Conv_Buf)))
# 结构自检:库在各分配点"失败即不追加/字段留空",此处逐字段确认 6 个键都带上了
# 键名与字符串值(池耗尽时若漏检会写出 `{null:"…"}` 这类非法 JSON)。
# 顺带累计各值长度,供容量守卫使用。
Ok: int = 1
N: int = 0
Sum: t.CSizeT = 0
cu: rawptr[json.JsonValue] = Obj.child
while cu != None:
if cu.key == None or cu.vtype != json.JSON_STRING or cu.str_val == None:
Ok = 0
else:
Sum = Sum + string.strlen(cu.str_val)
N += 1
cu = cu.next
if Ok == 0 or N != 6:
VLog.VerboseStr(" 警告: JSON 条目构造不完整(工作池不足),已跳过该条目\n")
return None
# 容量守卫:库的 write 缺省 cap=4096(=JSON_ENTRY_MAX),超出已会返回 None 且不写穿
# (includes/json/__writer.vp:143-154);此处提前守卫只是避免走到"整条被丢弃 + 告警"。
# 上界推导(Sum = 6 个值长度和):
# 实际输出 = 各值转义长度和 + 固定语法 77(键/值引号 24 + 键名 40 + 冒号 6 + 逗号 5 + 大括号 2)
# ≤ 2*Sum + 77 ≤ 2*Sum + JSON_ENTRY_SLACK(512)
# 故 2*Sum + JSON_ENTRY_SLACK ≤ JSON_ENTRY_MAX ⇒ 实际输出 ≤ 3661 < 4095,含结尾 NUL 也放得下。
if 2 * Sum + t.CSizeT(JSON_ENTRY_SLACK) > t.CSizeT(JSON_ENTRY_MAX):
VLog.VerboseFmtIS(" 警告: JSON 条目过长(超过 %zu 字节),已跳过: %s\n",
t.CSizeT(JSON_ENTRY_MAX), VCache.CacheExe(Idx))
return None
Txt: str = json.write(Json_Pool, Obj, False)
if Txt is None:
VLog.VerboseStr(" 警告: JSON 序列化失败(工作池不足),已跳过该条目\n")
return None
return Txt
# 把当前缓存整体构建为**单行** JSON 数组文本写入 Json_Buf(stdout 与缓存文件共用)
def EmitJson() -> int:
JReset()
if Json_Pool is None:
# 不变式:JsonInit(VScan.ScanInit 调用)成功才会走到 EmitJson;此分支仅为不崩。
VLog.LogStr(" 警告: JSON 工作池未初始化,跳过本轮 JSON 输出\n")
return 0
Truncated: int = 0
Skipped: int = 0
Count: int = 0
if JWrite("[") == 0:
return 0
i: int = 0
while i < VCache.CacheCount():
# 条目级回收:工作池峰值 = 单条(节点 + 字符串副本 + 库的 4096 字节输出缓冲)
Json_Pool.reset()
Txt: str = BuildEntry(i)
if Txt is None:
Skipped += 1
else:
n: t.CSizeT = string.strlen(Txt)
Need: t.CSizeT = n + 3 # 自身 + 结尾 ']' + '\n' + NUL
if Count > 0:
Need = Need + 1 # 分隔逗号
if t.CSizeT(Json_Len) + Need > t.CSizeT(JSON_BUF_CAP):
# 缓冲不足:跳过该条(不再像旧实现那样写出半条),末尾仍闭合 ']'
Truncated = 1
else:
if Count > 0:
JWrite(",")
JWrite(Txt)
Count += 1
i += 1
if JWrite("]") == 0:
Truncated = 1
if JWrite("\n") == 0:
Truncated = 1
if Truncated != 0:
VLog.LogFmt2(" 警告: JSON 输出缓冲不足,本轮已截断(已写 %zu 条 / 缓存 %zu 条)\n",
t.CSizeT(Count), t.CSizeT(VCache.CacheCount()))
if Skipped != 0:
VLog.LogFmt1(" 警告: 本轮有 %zu 条 JSON 条目因过长/工作池不足被跳过\n",
t.CSizeT(Skipped))
return 0