- @t.NoVTable class X → novirtual class X
- t.CDefine = N → define N
- rawptr[X]、X | t.CPtr → X*
- t.CPtr → rawptr(裸指针标记)
- t.Callable[...] → callable[...]
- 去掉 if/elif 条件的行续反斜杠(改单行写法)
- w32.win32file.X → win32file.X(已 import as)
- core/__init__.vp 去掉未使用的 import t, c / from stdint import *
涉及:CompilerFactory、IncludesScanner、Repl、core/__init__、
Gen/Call/{CallGenBase,CallGenCBuiltin,CallGenCall,CallGenDispatch,CallGenLeafKit}、
Gen/Class/ClassGenGeneric。
2971 lines
151 KiB
Plaintext
2971 lines
151 KiB
Plaintext
import t
|
||
from stdint import *
|
||
import stdio
|
||
import stdlib
|
||
import string
|
||
import viperlib
|
||
import llvmlite
|
||
import memhub
|
||
import ast
|
||
import w32.fileio as fileio
|
||
import w32.win32file as w32file
|
||
import w32.win32base as win32base
|
||
import w32.win32process as win32process
|
||
import lib.core.VLogger as VLogger
|
||
import lib.core.Gen.GenApi as GenApi
|
||
import lib.core.Gen.GenTypes as GenTypes
|
||
import lib.core.Gen.Translator as HandlesTranslator
|
||
import lib.core.IncludesScanner as IncludesScanner
|
||
import lib.Phase.Phase1.Phase1Scan as Phase1Scan
|
||
import lib.Phase.BuildPipeline as BuildPipeline
|
||
import lib.Projectrans.Config as Config
|
||
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
|
||
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
|
||
import lib.Stub.StubGen.Converter as StubConverter
|
||
import lib.Stub.StubGen.VpiReader as VpiReader
|
||
|
||
|
||
# ============================================================
|
||
# Repl - TransPyV 交互式循环(M2:会话变量 + ctx 回传结果)
|
||
#
|
||
# 输入:单行片段(既有路径)与**块级续行**——整行去行尾空白后以 ':' 结尾 ⇒ 进入续行
|
||
# (提示符 "... ",本行不执行),空行或 EOF 结束并把整块作为**一个**片段执行一次;
|
||
# 块文本存会话级 PermPool 缓冲(_REPL_BLOCK_CAP),失败/溢出后清空(残块不污染下一行)。
|
||
#
|
||
# 片段通路:行 → 包裹为 `export def __repl_frag_<n>(__repl_ctx__: bytes) -> int`
|
||
# → 翻译为完整模块 IR(会话变量按 ctx 槽寻址)→ .ll → llc → clang++ -shared
|
||
# → LoadLibraryA/GetProcAddress → t.Callable 调用(传 ctx 基址)→ 从 ctx 结果区
|
||
# 读回并回显。
|
||
#
|
||
# ctx(会话内存,宿主 PermPool 一次性分配;布局见 GenTypes 顶部注释):
|
||
# 偏移 0..63 固定头(magic/version/result_kind/result_a/result_b/var_count/last_frag)
|
||
# 偏移 64 起为变量槽(每槽 16 字节)。宿主与片段在**同一进程**内共享该内存:
|
||
# 会话变量只存一份 ⇒ &x 得到的就是槽地址(地址同一性)。
|
||
# 值的类型按首次赋值推导(`x = 1` 与 `x: int = 1` 等价,均定型并分配槽)。
|
||
#
|
||
# 错误可恢复依赖 VLogger 的 jit_flag + setjmp/longjmp:
|
||
# - Repl 用 PermPool 分配 jmp_buf 并注册给 VLogger(PermPool 不受 mb.reset_to 影响);
|
||
# - 每轮 mb.mark() 后 _setjmp,解析/翻译全用 mb 分配;无论成败都 reset_to(mark) 回收。
|
||
#
|
||
# 复用既有机制(不新造进程拉起步子):
|
||
# - BuildPipeline.write_ir_to_file / build_sliced_path / compile_ll_to_obj(llc);
|
||
# - BuildPipeline._run_cmd_wait(CreateProcessA + WaitForSingleObject,见 :488);
|
||
# - Config 的 compiler/linker/target 通道(project.vpj 原样透传);
|
||
# - includes 会话初始化:IncludesScanner.scan_includes + StubMergerStore.PopulateSha1MapStore
|
||
# + StubConverter._GeneratePyiFromAst + StubMergerCollect._sliced_path(与 Phase1 同款,
|
||
# 见 ReplInitIncludes;否则片段里的 `import <includes 模块>` 会因 SHA1 表为空 fail-fast)。
|
||
# ============================================================
|
||
|
||
# 输入行缓冲容量(含 NUL 终止符)
|
||
define _REPL_LINE_CAP = 4096
|
||
|
||
# 块输入缓冲容量(会话级 PermPool,含 NUL 终止符):续行状态下逐行追加的用户原文,
|
||
# 行间以一个 '\n' 连接、无行尾换行。
|
||
# 取 8 KiB 的理由:常见多行块(10 行 × 80 列 ≈ 0.8 KiB)绰绰有余;整段粘贴
|
||
# 100 行 × 80 列 ≈ 8 KiB 也刚好容纳;再大属异常输入,按"明确报错 + 丢弃该块"
|
||
# 处理(绝不截断后执行)。上界同时约束包裹源码缓冲的下界(见 _REPL_FRAG_SRC_CAP)。
|
||
define _REPL_BLOCK_CAP = 8192
|
||
|
||
# jmp_buf 实测大小:mingw-w64 x86_64 setjmp.h 中 _JBLEN=16、_JBTYPE=SETJMP_FLOAT128(16B)
|
||
# ⇒ 16 * 16 = 256 字节(已用 C 探针 sizeof(jmp_buf) 实测 = 256)
|
||
define _REPL_JMPBUF_SIZE = 256
|
||
|
||
# setjmp/longjmp:mingw-w64 x86_64 的 _setjmp 第二参数为 frame 指针,
|
||
# setjmp 宏即展开为 _setjmp(buf, NULL)(非 SEH 场景传 None 即可)
|
||
extern def _setjmp(buf: rawptr, frame: rawptr) -> int
|
||
extern def longjmp(buf: rawptr, val: int)
|
||
|
||
# PermPool(由 main 注入):jmp_buf 生命周期须覆盖整个会话,不能被 mb.reset_to 回收
|
||
_perm_pool: memhub.MemBuddy*
|
||
|
||
|
||
# ============================================================
|
||
# M2/M3 常量
|
||
# ============================================================
|
||
# 片段包裹源码容量(含 NUL)。三条路径的上界取大者:
|
||
# 单行路径 = 行缓冲 4096 + 包裹开销 128(原口径,未变);
|
||
# 块路径 = 块缓冲 8192 + 逐行 4 空格缩进(块内每行至少 1 字符 + 一个行间 '\n'
|
||
# ⇒ 行数 ≤ 4096 ⇒ 缩进 ≤ 4096*4 = 16384)≤ 24576;
|
||
# 提升路径(M3)= 上面 24576 + 每个被提升 def 的头部注入("export " 7 +
|
||
# "__repl_ctx__: bytes, " 21 ⇒ 按 64 字节/个上 slack,def 数 ≤ 16
|
||
# ⇒ ≤ 1024)+ 包裹头尾开销 512 ⇒ ≤ 26112。
|
||
# 缩进是"块路径"的唯一放大项,故上界必须按"行数上界"而非"行数×行长"估;
|
||
# 提升行按原文进入模块区(不缩进),其代价低于按 4 空格放大的上界,故不再附加项。
|
||
_REPL_FRAG_SRC_CAP: t.CDefine = 26624
|
||
# 提升出的模块级 def 源码缓冲容量(含 NUL):提升行原文 ≤ 8192 + 注入 ≤ 1024
|
||
_REPL_LIFT_CAP: t.CDefine = 12288
|
||
# 单片段最多提升的会话函数数(≤ GenTypes.REPL_FUNC_CAP;超出 ⇒ 明确报错)
|
||
_REPL_LIFT_MAX: t.CDefine = 16
|
||
# 会话函数名长度上限(写入 ReplLiftNames 的槽宽 = 上限 + 1)
|
||
_REPL_FN_NAME_MAX: t.CDefine = 64
|
||
# 会话函数名表容量(_REPL_LIFT_MAX 个定长槽)
|
||
_REPL_FN_NAMES_CAP: t.CDefine = 1024
|
||
# define 行里单个类型串的拷贝缓冲("i32"/"i8*"/"%struct.x*" 截断到这里再比较)
|
||
_REPL_FN_TY_CAP: t.CDefine = 64
|
||
# 诊断显示缓冲(把非 NUL 结尾的行/名字截断后供 %s 打印)
|
||
_REPL_DIAG_CAP: t.CDefine = 128
|
||
# 签名渲染缓冲容量("(int, str) -> int" 最长 ≈ 8×(5+2)+8+8 < 96)
|
||
_REPL_SIG_TEXT_CAP: t.CDefine = 192
|
||
# 片段符号名/探针缓冲容量("__repl_frag_<n>" / "define i32 @<name>(i8* %__repl_ctx__")
|
||
_REPL_NAME_CAP: t.CDefine = 64
|
||
_REPL_NEEDLE_CAP: t.CDefine = 128
|
||
# 临时文件路径缓冲容量(目录/源文件路径)
|
||
_REPL_PATH_CAP: t.CDefine = 512
|
||
# 完整模块 IR 文本容量(与 App/main.vp IR_BUF_SIZE 一致)
|
||
_REPL_IR_CAP: t.CDefine = 262144
|
||
# GetTempPathA 输出缓冲容量(MAX_PATH)
|
||
_REPL_TMP_CAP: t.CDefine = 260
|
||
# 外部命令(clang++ -shared)命令行容量。
|
||
# M4 扩容 1024 → 16384:片段链接命令行现在除了宿主 import lib,还要带上**先前
|
||
# 片段**的 import lib(每个 ≈ 80~110 字节路径),否则"片段 N 定义的类/方法"在
|
||
# 后续片段里是 undefined symbol(见 ReplFragImplibs 段)。
|
||
_REPL_CMD_CAP: t.CDefine = 16384
|
||
# includes 源文件读取缓冲(与 Phase1.SRC_BUF_SIZE / BuildPipeline.SRC_BUF_SIZE 一致)
|
||
_REPL_SRC_BUF_CAP: t.CDefine = 1048576
|
||
# .vpi 存根缓冲(与 BuildPipeline.PYI_BUF_SIZE 一致)
|
||
_REPL_VPI_BUF_CAP: t.CDefine = 262144
|
||
# ---- M4:会话 sha1 / 片段 import lib / 会话基线(CDefine + CTypedef)----
|
||
# 先前片段 import lib 的槽数上限(每个"IR 里出现会话前缀符号"的片段最多一条;
|
||
# 判据是"出现过"而非"定义过" ⇒ 纯调用片段也会占一个槽,故留足余量)
|
||
_REPL_FRAG_IMPLIB_MAX: t.CDefine = 128
|
||
# 片段 import lib 记录数组容量(槽数 × 8 字节指针)
|
||
_REPL_FRAG_IMPLIB_ARR_CAP: t.CDefine = 1024
|
||
# 会话基线(CDefine)条目上限:每片段重建表时按基线回灌
|
||
_REPL_BASE_CDEFINE_MAX: t.CDefine = 512
|
||
# 会话基线(CTypedef)条目上限
|
||
_REPL_BASE_CTYPEDEF_MAX: t.CDefine = 512
|
||
# includes 类注册(会话初始化做一次):参与 vpi 注入的模块数上限
|
||
_REPL_INC_CLS_MOD_MAX: t.CDefine = 512
|
||
# includes 类注册的收敛遍数(字段类型依赖同批其它类 ⇒ 多遍收敛)
|
||
_REPL_INC_CLS_PASSES: t.CDefine = 3
|
||
# includes 类注册的结构体表水位守卫(STRUCT_MAX = 512;预留余量)
|
||
_REPL_INC_CLS_STRUCT_GUARD: t.CDefine = 448
|
||
# includes 类注册的逐模块告警行上限(超出只累计计数,由汇总行给出总数)
|
||
_REPL_INC_CLS_WARN_MAX: t.CDefine = 3
|
||
# includes 类注册(会话初始化)的启动时间预算(ms):多遍 vpi 注入 + 逐类布局校验是
|
||
# 会话启动的主要耗时,且失败模块会在后续遍重试 ⇒ 模块数 × 遍数的上界虽有限,但
|
||
# 上界本身可能很长(缺存根时每模块仍要读一遍 .vpi + 解析 + 注入了 declare)。
|
||
# 超预算即停止"启动期注册",剩余模块留待片段 `import` 时按既有 on-demand 路径注册。
|
||
# 取舍:宁可让部分 includes 类在启动后仍不可用(且明确告知),也不能让启动"看起来
|
||
# 死了"——提示符必须先出现(REPL 可用性 > 启动期注册完整度)。
|
||
_REPL_INC_CLS_BUDGET_MS: t.CDefine = 20000
|
||
# includes 类注册用的临时 LLVM 模块名(.vpi 注入的目标模块,会话初始化时一次)
|
||
_REPL_INC_MOD_NAME: t.CDefine = "repl_inc_init"
|
||
# ctx 会话缓冲字节数 = 固定头 64 + 槽数上限 64 × 槽步长 16
|
||
#(M3 追加函数指针槽区:+ 函数上限 32 × 槽步长 16 ⇒ 64 + 1024 + 512 = 1600)
|
||
# 必须与 GenTypes.REPL_CTX_SIZE / REPL_SLOT_BASE / REPL_VAR_CAP / REPL_SLOT_SIZE /
|
||
# REPL_FUNC_BASE / REPL_FUNC_CAP / REPL_FUNC_SLOT_SIZE 一致
|
||
_REPL_CTX_SIZE: t.CDefine = 1600
|
||
# 模板工程文件名(--repl 未给 --project 时在 cwd → exe 同目录查找)
|
||
_REPL_PROJECT_FILE: t.CDefine = "repl.vpj"
|
||
# cwd 候选后缀:GetCurrentDirectoryA 返回值不含结尾分隔符,故须补 '/'
|
||
_REPL_PROJECT_SEP_FILE: t.CDefine = "/repl.vpj"
|
||
# 上述两种后缀的最长长度 + NUL 余量(候选缓冲余量检查用)
|
||
_REPL_PROJECT_PROBE_CAP: t.CDefine = 16
|
||
|
||
|
||
# ============================================================
|
||
# ReplFloatView - ctx 头的浮点视图(宿主回显浮点结果用)
|
||
#
|
||
# 与 GenTypes.ReplResult 同一块内存:F 位于偏移 16,正是 ReplResult.A 的 8 字节。
|
||
# 片段把 double 的 IEEE754 位模式写进 result_a;宿主按本视图读回即得 double
|
||
# (位模式重解释,不做数值转换)。
|
||
# ============================================================
|
||
novirtual class ReplFloatView:
|
||
"""ctx 头(+16 处按 double 读)"""
|
||
Magic: int
|
||
Version: int
|
||
Kind: int
|
||
Reserved0: int
|
||
F: t.CDouble
|
||
|
||
|
||
# ============================================================
|
||
# ReplFuncSlotView - ctx 函数指针槽视图(M3;宿主写入片段函数的入口地址)
|
||
#
|
||
# 与 GenTypes.REPL_FUNC_BASE/REPL_FUNC_SLOT_SIZE 同一布局约定:每个会话函数占
|
||
# 16 字节(低 8 字节 = 地址,高 8 字节保留)。片段侧按同一偏移(i8** 视图)
|
||
# load 该地址后间接调用;宿主用本视图写入 ⇒ 两侧对"槽内是什么"只有一个约定。
|
||
# ============================================================
|
||
novirtual class ReplFuncSlotView:
|
||
"""ctx 函数指针槽(Addr = 定义片段 DLL 里该函数的入口地址)"""
|
||
Addr: VOIDPTR
|
||
Reserved: VOIDPTR
|
||
|
||
|
||
# ============================================================
|
||
# ReplSigOut - 会话函数签名解析结果(M3;宿主从片段 IR 的 define 行解析)
|
||
#
|
||
# 出处:定义片段 translate 后 dump 的 IR 里
|
||
# `define <ret> @f(i8* %__repl_ctx__, <P1> %p1, ...)`
|
||
# 是定义端 ABI 的唯一真相(注解 → LLVM 类型由 codegen 解析)。宿主据此填
|
||
# ReplFuncEntry 的签名 token;调用片段再用 token 重建同型签名 ⇒ 两侧一致。
|
||
# 形参/返回的 token 数组写进会话 scratch(ReplSigTags/ReplSigBits)。
|
||
# ============================================================
|
||
novirtual class ReplSigOut:
|
||
"""签名解析结果(Bad/EHTag 非 0 时拒绝注册)"""
|
||
RetTag: int # GenTypes.REPL_TY_VOID / INT / FLOAT / STR
|
||
RetBits: int
|
||
ParamCount: int # 不含 ctx 首形参
|
||
EHTag: int # 1=define 含 %__eh_msg_out__/%__eh_code_out__(raise 语义)
|
||
Bad: int # 1=含不受支持的类型 token
|
||
BadType: str # 不受支持的类型串(指向 IR 文本内,仅用于打印;否则 None)
|
||
|
||
|
||
# ============================================================
|
||
# 模板工程查找 - --repl 未给 --project 时的启动便利
|
||
#
|
||
# 顺序:① 当前工作目录 repl.vpj → ② TransPyV.exe 同目录 repl.vpj。
|
||
# 命中路径由 main 按 --project 等价路径喂给既有的 Config.Load_project_config
|
||
# 加载流程(不另写加载器);两处都没有时由调用方按未命中报错。
|
||
# Out/Diag 由调用方(main,stdlib.malloc)提供:本函数在会话初始化之前调用,
|
||
# 故只用入参缓冲 + 一次性 stdlib.malloc,不碰 Repl 会话缓冲与 PermPool。
|
||
# ============================================================
|
||
def ReplFindProject(Out: bytes, Cap: int, Diag: bytes, DiagCap: int) -> int:
|
||
"""cwd → exe 同目录 查找 repl.vpj:1=命中(写 Out),0=未命中(写 Diag)"""
|
||
if Out is None or Diag is None:
|
||
return 0
|
||
if Cap < 64 or DiagCap < 64:
|
||
return 0
|
||
# 两个候选各用独立缓冲:未命中时两条路径都要进诊断文本
|
||
c1: bytes = stdlib.malloc(Cap)
|
||
c2: bytes = stdlib.malloc(Cap)
|
||
if c1 is None or c2 is None:
|
||
if c1 is not None:
|
||
stdlib.free(c1)
|
||
if c2 is not None:
|
||
stdlib.free(c2)
|
||
return 0
|
||
c1[0] = 0
|
||
c2[0] = 0
|
||
# ① cwd/repl.vpj
|
||
n_cwd: ULONG = w32file.GetCurrentDirectoryA(ULONG(Cap), c1)
|
||
if n_cwd > 0:
|
||
cl: int = int(n_cwd)
|
||
# 防御:缓冲区不足时 GetCurrentDirectoryA 的写入内容未定义,强制补 NUL
|
||
c1[Cap - 1] = 0
|
||
if cl + _REPL_PROJECT_PROBE_CAP <= Cap:
|
||
viperlib.snprintf(c1 + cl, Cap - cl, "%s", _REPL_PROJECT_SEP_FILE)
|
||
if _is_file(c1) != 0:
|
||
string.strcpy(Out, c1)
|
||
stdlib.free(c1)
|
||
stdlib.free(c2)
|
||
return 1
|
||
else:
|
||
string.strcpy(c1, "(GetCurrentDirectoryA 失败)")
|
||
# ② exe 同目录/repl.vpj(GetModuleFileNameA(NULL) 取自身全路径)
|
||
n_exe: ULONG = w32file.GetModuleFileNameA(None, c2, ULONG(Cap))
|
||
if n_exe > 0:
|
||
el: int = int(n_exe)
|
||
# 防御:截断时返回值 == Cap 且串可能未 NUL 结尾
|
||
c2[Cap - 1] = 0
|
||
if el < Cap:
|
||
c2[el] = 0
|
||
# 定位最后一个分隔符:c2[0..dlen) 为目录前缀(含分隔符);
|
||
# dlen=0 表示 exe 路径无目录部分(等价于 cwd,交由 OS 解析)
|
||
dlen: int = 0
|
||
si: int = el
|
||
while si > 0:
|
||
si -= 1
|
||
ch: char = c2[si]
|
||
if ch == '/' or ch == '\\':
|
||
dlen = si + 1
|
||
break
|
||
if dlen + _REPL_PROJECT_PROBE_CAP <= Cap:
|
||
viperlib.snprintf(c2 + dlen, Cap - dlen, "%s", _REPL_PROJECT_FILE)
|
||
if _is_file(c2) != 0:
|
||
string.strcpy(Out, c2)
|
||
stdlib.free(c1)
|
||
stdlib.free(c2)
|
||
return 1
|
||
else:
|
||
string.strcpy(c2, "(GetModuleFileNameA 失败)")
|
||
# 两处都没有:诊断文本(需要 --project + 两个已查位置 + 模板位置)
|
||
viperlib.snprintf(Diag, DiagCap,
|
||
" 已查找(1): %s\n"
|
||
" 已查找(2): %s\n"
|
||
" 需要 --project: 未找到 repl.vpj 时 REPL 拿不到工程配置,"
|
||
"请用 --project <path/to/repl.vpj> 指定\n"
|
||
" 模板位置: 仓库 TransPyV/repl.vpj;拷到 TransPyV.exe 同目录"
|
||
"或当前工作目录即可被自动找到\n",
|
||
c1, c2)
|
||
stdlib.free(c1)
|
||
stdlib.free(c2)
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# 会话状态
|
||
#
|
||
# 常驻缓冲一律 PermPool 分配(ReplInitSession 一次性建,进程退出随池释放);
|
||
# 按片段产生的短命串(.ll/.obj/.dll 路径、命令行)来自 mb,行末 reset_to(mark) 回收。
|
||
# ============================================================
|
||
# 片段函数指针槽:t.Callable 注解 → i8** 槽(VET/App/lib/VGuard.vp:358 同款范式)。
|
||
# 模块级 AnnAssign 的返回类型签名由 TranslatorGenMixin.vp:721-742 登记 →
|
||
# 调用点经 CallGenMixin.vp:960-984 走"按实参构造签名 + 间接 call"路径。
|
||
# M2 约定:唯一实参是 ctx 基址(i8*),返回状态码 int(结果经 ctx 回传)。
|
||
ReplFragFn: callable[[rawptr], int] = None
|
||
# 片段序号:模块级(longjmp 恢复后仍单调递增)→ 符号名/DLL 文件名在会话内不复用
|
||
ReplFragNo: int = 1
|
||
# 会话临时目录 %TEMP%\TransPyV_repl\<pid>(不污染用户工程目录;pid 隔离并发会话)
|
||
ReplTmpDir: bytes = None
|
||
# 片段符号名 __repl_frag_<n>
|
||
ReplNameBuf: bytes = None
|
||
# 包裹后的片段源码(供解析/翻译)
|
||
ReplSrcBuf: bytes = None
|
||
# 包裹源码落盘路径(错误回显用真实源文件)
|
||
ReplSrcPath: bytes = None
|
||
# 块输入缓冲(会话级 PermPool,容量 _REPL_BLOCK_CAP):续行状态下逐行追加的用户原文,
|
||
# 行间以 '\n' 连接、无行尾换行。一次性分配,不按行 malloc。
|
||
ReplBlockBuf: bytes = None
|
||
# 块缓冲当前字节数(0 = 无缓存内容;NUL 恒写在 ReplBlockBuf[ReplBlockLen])
|
||
ReplBlockLen: int = 0
|
||
# 续行状态:1 = 正在缓存多行块(提示符 "... ");0 = 正常(提示符 ">>> ")
|
||
ReplInBlock: int = 0
|
||
# 完整模块 IR 文本(dump_ir OUTPUT_FULL 的输出缓冲)
|
||
ReplIrBuf: bytes = None
|
||
# IR 探针 "define i32 @<name>(i8* %__repl_ctx__"(确认导出函数签名与调用约定一致)
|
||
ReplNeedleBuf: bytes = None
|
||
# ctx 会话缓冲(宿主与片段共享;字节视图,作为片段首实参传递)
|
||
ReplCtxBuf: bytes = None
|
||
# 会话上下文对象(Gen 侧读:表/容量/形参名/永久池/影子条目)
|
||
ReplCtxObj: rawptr[GenTypes.ReplCtx] = None
|
||
# 会话变量表(PermPool,ReplCtxObj.Table)
|
||
ReplVarTable: rawptr[GenTypes.ReplVarEntry] = None
|
||
# 影子条目(PermPool):仅供"裸表达式结果"做类型分类,不占会话槽、不进表
|
||
ReplScratch: rawptr[GenTypes.ReplVarEntry] = None
|
||
# 恢复点 jmp_buf(PermPool,会话期存活):run 里 _setjmp 到它,
|
||
# VLogger.repl_abort 从流水线任意深度 longjmp 回它。每片段入口重注册(幂等)。
|
||
ReplJmpBuf: rawptr = None
|
||
# 片段开始时的会话表水位(ReplCtxObj.Count):恢复时回滚到它,
|
||
# 丢弃失败片段的半成品登记(repl_ensure_entry 已 Count+1 但 repl_seal 报错的条目)。
|
||
ReplSavedVarCount: int = 0
|
||
|
||
# ==== includes 会话初始化结果(会话级一次;供摘要行与恢复分支报告进度)====
|
||
# 已注册的 includes 模块数(StubMerger SHA1 表条目数)
|
||
ReplIncMods: int = 0
|
||
# includes 扫描耗时(ms)
|
||
ReplIncMs: int = 0
|
||
# 新生成的 .vpi 存根数
|
||
ReplIncVpiNew: int = 0
|
||
# 复用的既有 .vpi 存根数
|
||
ReplIncVpiReuse: int = 0
|
||
# .vpi 生成失败数(读/解析/写盘失败)
|
||
ReplIncVpiFail: int = 0
|
||
|
||
# ==== 宿主导出符号(跨模块调用)====
|
||
# 宿主 exe 的 import lib 路径(<exe 名>.dll.a;PermPool,会话级一次探测)。
|
||
# 非 None ⇒ 片段链接命令把它加进去 ⇒ 片段 DLL 从宿主 exe 导入 includes 符号,
|
||
# 与宿主共用同一份代码与数据(无状态重复)。None ⇒ 宿主未按导出模式构建。
|
||
ReplHostImplib: bytes = None
|
||
|
||
# ==== M3 会话函数(跨片段函数)====
|
||
# 提升出的模块级 def 源码(会话级 PermPool;每片段开头清零长度)
|
||
ReplLiftBuf: bytes = None
|
||
# 提升出的会话函数名表(PermPool,_REPL_FN_NAME_MAX 定长槽 × _REPL_LIFT_MAX 个)
|
||
ReplLiftNames: bytes = None
|
||
# 本次片段提升出的会话函数个数
|
||
ReplLiftNameCount: int = 0
|
||
# 签名 token scratch(PermPool,各 GenTypes.REPL_FUNC_PARAM_MAX 字节;逐函数复用)
|
||
ReplSigTags: bytes = None
|
||
ReplSigBits: bytes = None
|
||
# 签名解析结果体(PermPool,会话级复用;名字避开类名 ReplSigOut)
|
||
ReplSigResult: rawptr[ReplSigOut] = None
|
||
# 会话函数表水位(ReplCtxObj.FuncCount)在片段开始时的快照:失败/回滚时恢复到它,
|
||
# 丢弃失败片段的半成品登记(与 ReplSavedVarCount 同思路,见 ReplExecFragment ⓪)
|
||
ReplSavedFuncCount: int = 0
|
||
# 诊断用行/名字显示缓冲(PermPool,_REPL_DIAG_CAP;%s 不能直接打非 NUL 结尾的行)
|
||
ReplLineShow: bytes = None
|
||
# def 头名字 scratch(PermPool,_REPL_FN_NAME_MAX)
|
||
ReplMsgName: bytes = None
|
||
# 签名解析的类型串 scratch(PermPool,_REPL_FN_TY_CAP)
|
||
ReplTySpan: bytes = None
|
||
# 签名解析的"%+形参名"合成 scratch(PermPool,_REPL_FN_TY_CAP)
|
||
ReplTySpan3: bytes = None
|
||
# IR 里最近命中的 define 行起点偏移(_repl_ir_find_define_at 写、_repl_ir_func_sig 读)
|
||
ReplIrDefOff: int = -1
|
||
# 签名渲染缓冲 ×2(PermPool,_REPL_SIG_TEXT_CAP):新签名 / 已有条目签名对比展示
|
||
ReplSigText: bytes = None
|
||
ReplSigText2: bytes = None
|
||
# 片段体(包裹)当前写入位置(_repl_split_body 写、_repl_build_frag_src_multi 读)
|
||
ReplWrapPos: int = 0
|
||
|
||
# ==== M4:会话模块 sha1(类型名的跨片段唯一键)====
|
||
# PermPool 上的 16 位 hex 串(GenTypes.REPL_SESSION_SHA1 的副本)。
|
||
# 每个片段翻译器都取它作 ModuleSha1 ⇒ 片段内定义的类名恒定形如
|
||
# "<session sha1>.<Class>",后续片段据同一前缀查到同一结构体条目。
|
||
ReplSessionSha1: bytes = None
|
||
|
||
# ==== M4:先前片段的 import lib(跨片段方法/构造符号的解析来源)====
|
||
# 片段 DLL 只把自己 obj 与"已记录 import lib"链在一起:片段 N 里的
|
||
# `p = Point(1, 2)`(Point 定义在片段 M<N)会生成对 <session sha1>.Point.__init__
|
||
# 的引用,该符号只存在于片段 M 的 DLL 里 ⇒ 必须把 M 的 import lib 加进 N 的链接
|
||
# 命令行。数组槽内是 PermPool 里的路径串指针(8 字节/槽)。
|
||
ReplFragImplibs: sizet* = None
|
||
ReplFragImplibCount: int = 0
|
||
|
||
# ==== M4:会话基线(CDefine / CTypedef 的跨片段存活)====
|
||
# 每片段开头的 clear_cdefine_constants/clear_ctypedefs 会把表指针置 None
|
||
# (表内容随片段池一起回收),而表的语义是"整个会话一份"(includes 注入的常量、
|
||
# include 类型别名、片段里 `N: t.CDefine = 3` 等)⇒ 用会话基线 + 片段增量:
|
||
# - 基线:PermPool 里的 (名字, 值/类型串, 种类) 三元组数组,只在**成功**片段
|
||
# 结束时追加该片段新增的条目(失败片段不追加 ⇒ 无污染,水位回滚天然成立);
|
||
# - 每片段清空表后按基线回灌(条目分配在本片段池,随片段回收);
|
||
# - 失败片段用 ReplSavedCDefineCount/ReplSavedCTypedefCount 截断(防御性,
|
||
# 与 ReplSavedVarCount/ReplSavedFuncCount 同思路)。
|
||
ReplBaseCDefineNames: sizet* = None
|
||
ReplBaseCDefineValues: sizet* = None
|
||
ReplBaseCDefineKinds: sizet* = None # 每条目 8 字节:0=int / 1=double / 2=str
|
||
ReplBaseCDefineCount: int = 0
|
||
ReplBaseCTypedefNames: sizet* = None
|
||
ReplBaseCTypedefTypeStrs: sizet* = None
|
||
ReplBaseCTypedefCount: int = 0
|
||
# 片段开始时的基线水位(失败/回滚时截断回它)
|
||
ReplSavedCDefineCount: int = 0
|
||
ReplSavedCTypedefCount: int = 0
|
||
|
||
# ==== M4:includes 类注册(会话初始化做一次)====
|
||
# 已成功注入 .vpi(⇒ 其类进入会话 StructTable)的模块数 / 失败数 / 缺存根数
|
||
ReplIncClsMods: int = 0
|
||
ReplIncClsFail: int = 0
|
||
ReplIncClsMissing: int = 0
|
||
|
||
|
||
# ============================================================
|
||
# 私有 helper
|
||
# ============================================================
|
||
|
||
# 路径是否为"存在的普通文件"(GetFileAttributesA:只返回属性值,无输出缓冲、无越界风险)
|
||
def _is_file(path: str) -> int:
|
||
"""1=存在且非目录;0=不存在/是目录/参数错误"""
|
||
if path is None:
|
||
return 0
|
||
attr: ULONG = w32file.GetFileAttributesA(path)
|
||
if attr == 0xFFFFFFFF: # INVALID_FILE_ATTRIBUTES
|
||
return 0
|
||
if (attr & w32file.FILE_ATTRIBUTE_DIRECTORY) != 0:
|
||
return 0
|
||
return 1
|
||
|
||
|
||
# ============================================================
|
||
# 宿主导出符号:定位宿主 exe 的 import lib(跨模块调用)
|
||
#
|
||
# 为什么需要:片段 IR 对 includes 的引用是 `<sha1>.<函数名>`(.text.ll 实证:
|
||
# `define i32 @"<sha1>.utf8_is_valid"`;模块级全局同理 `<sha1>.<变量名>`,如
|
||
# `@"<sha1>._memdbg_enable" = global i32 zeroinitializer`),这些符号由**宿主 exe**
|
||
# 定义(exe 链接时已把 includes.binary 的 obj 全部链入,实测 output/TransPyV.exe
|
||
# 的 COFF 符号表含 118 个 `<16位hex>.` 前缀)。片段链接命令若只含片段自身 obj,
|
||
# 跨模块符号会以 undefined symbol 停在链接期。
|
||
#
|
||
# 机制(方案 C):宿主以 `-Wl,--export-all-symbols` 构建 ⇒ exe 带导出表;
|
||
# BuildPipeline 同时产出 `{OutputDir}/{exe 名}.dll.a`(`-Wl,--out-implib=`)。
|
||
# 片段链接时把该 import lib 加进命令行 ⇒ 片段 DLL 的导入表指向宿主 exe,
|
||
# 加载时由同一进程内已加载的宿主满足 ⇒ 宿主与片段**共用同一份** includes 代码与
|
||
# 数据(状态不重复;片段 DLL 内不存在任何 `<sha1>.` 定义)。
|
||
#
|
||
# 查找顺序:① 本 exe 同目录(默认构建把 exe 与 .dll.a 一起写在 OutputDir)
|
||
# ② Config.OutputDir(--project 指向真正宿主的 vpj 时)
|
||
# 两处都没有 ⇒ 不报错,按旧口径只用片段自身 obj 链接(失败时给出明确提示)。
|
||
# ============================================================
|
||
def ReplFindHostImplib() -> int:
|
||
"""定位宿主 import lib(<exe 名>.dll.a):1=命中(写 ReplHostImplib),0=未找到"""
|
||
global ReplHostImplib
|
||
if _perm_pool is None:
|
||
return 0
|
||
ReplHostImplib = None
|
||
exe_buf: bytes = _perm_pool.alloc(_REPL_PATH_CAP)
|
||
cand: bytes = _perm_pool.alloc(_REPL_PATH_CAP)
|
||
if exe_buf is None or cand is None:
|
||
return 0
|
||
exe_buf[0] = 0
|
||
cand[0] = 0
|
||
n_exe: ULONG = w32file.GetModuleFileNameA(None, exe_buf, ULONG(_REPL_PATH_CAP))
|
||
if n_exe <= 0:
|
||
return 0
|
||
el: int = int(n_exe)
|
||
# 防御:截断时写入内容可能未 NUL 结尾(与 ReplFindProject 同口径)
|
||
if el >= _REPL_PATH_CAP:
|
||
el = _REPL_PATH_CAP - 1
|
||
exe_buf[el] = 0
|
||
# 拆目录/文件名:dlen = 目录前缀长度(含分隔符)
|
||
dlen: int = 0
|
||
si: int = el
|
||
while si > 0:
|
||
si -= 1
|
||
ch: char = exe_buf[si]
|
||
if ch == '/' or ch == '\\':
|
||
dlen = si + 1
|
||
break
|
||
# 候选 ①:exe 同目录(exe_buf 已含目录前缀)
|
||
viperlib.snprintf(cand, _REPL_PATH_CAP, "%s%s.dll.a", exe_buf, exe_buf + dlen)
|
||
if _is_file(cand) != 0:
|
||
ReplHostImplib = cand
|
||
return 1
|
||
# 候选 ②:Config.OutputDir(REPL 用 repl.vpj 起、宿主用 project.vpj 建时)
|
||
if Config.OutputDir is not None:
|
||
viperlib.snprintf(cand, _REPL_PATH_CAP, "%s/%s.dll.a",
|
||
Config.OutputDir, exe_buf + dlen)
|
||
if _is_file(cand) != 0:
|
||
ReplHostImplib = cand
|
||
return 1
|
||
return 0
|
||
|
||
|
||
# 逐字节 ReadFile 读取一行:控制台(ENABLE_LINE_INPUT,回车后返回整行)与管道均适用
|
||
def _read_line(h: win32base.HANDLE, buf: bytes, cap: int) -> int:
|
||
"""从 h 读取一行写入 buf(NUL 结尾),返回字节数(不含 NUL);EOF/错误返回 -1。
|
||
|
||
剥离 CR(兼容 CRLF 输入),遇 LF 结束;缓冲区满时停止(余量留作下一行)。
|
||
"""
|
||
if buf is None or cap < 2:
|
||
return -1
|
||
total: int = 0
|
||
bytes_read: ULONG = 0
|
||
ok: BOOL = 0
|
||
eof: int = 0
|
||
while total < cap - 1:
|
||
bytes_read = 0
|
||
ok = w32file.ReadFile(h, buf + total, 1, &bytes_read, None)
|
||
if ok == 0 or bytes_read == 0:
|
||
eof = 1
|
||
break
|
||
# 注意:不能用 &(buf + total)——其 IR 生成 i32 load(读 4 字节),
|
||
# 比较 '\n'/'\r' 永不命中;字节索引 buf[total] 才是 i8 load
|
||
ch: char = buf[total]
|
||
if ch == 10: # '\n':行结束
|
||
break
|
||
if ch == 13: # '\r':CRLF,丢弃后原位覆盖读取
|
||
continue
|
||
total += 1
|
||
buf[total] = 0
|
||
if eof != 0 and total == 0:
|
||
return -1
|
||
return total
|
||
|
||
|
||
# 统计顶层语句数(Module.children)
|
||
def _count_top_stmts(tree: ast.AST*) -> int:
|
||
"""返回 Module 的顶层语句数(children 为空时为 0)。"""
|
||
if tree is None or tree.children is None:
|
||
return 0
|
||
stmts: list[ast.AST*]* = tree.children
|
||
return int(len(stmts))
|
||
|
||
|
||
# ============================================================
|
||
# 块级续行(多行块输入)
|
||
#
|
||
# 判据(与用户拍板口径一致,刻意保持"最简单可预测"):
|
||
# 进入:非续行状态下,整行去掉行尾空白后以 ':' 结尾 ⇒ 进入续行,该行进缓冲、不执行;
|
||
# 结束:续行中读到空行(长度 0)或 EOF ⇒ 整块作为**一个**片段执行一次;
|
||
# 续行中不做任何缩进/关键字分析,行尾 ':' 也不反复切换状态。
|
||
# 为什么只看行尾 ':':Viper 的块头(def/class/for/if/while/with/try/match/case…)
|
||
# 一律以 ':' 收尾,而完整单行语句几乎不会以 ':' 结尾 ⇒ 判据零误报面;
|
||
# 唯一代价是"行尾带 ':' 的非法/罕见单行"会多缓存一行,空行即执行,可预期。
|
||
# 为什么空行结束:与 Python REPL 一致(空行 = 块输入完毕),且无需缩进分析;
|
||
# 批处理源码里空行不影响语法(lexer 的 _handle_indent 跳过空行/纯空白行)。
|
||
# ============================================================
|
||
|
||
# 行尾判据:去掉行尾空白后以 ':' 结尾;或(M3)首个非空白字符是 '@'(装饰器行)
|
||
def _line_starts_block(line: str, n: int) -> int:
|
||
"""1=该行进入块缓冲(':' 结尾 / '@' 开头);0=否
|
||
|
||
'@' 开头的行单独成句在 Viper/Python 里都是语法错误(装饰器必须后接 def/class),
|
||
故把它纳入"进入续行"判据零误报面;这是 M3 会话函数能用 @decorator 的前提
|
||
(装饰器行不以 ':' 结尾,否则用户无法把装饰器连同 def 一起送进一个块)。
|
||
"""
|
||
if line is None or n <= 0:
|
||
return 0
|
||
s: int = 0
|
||
while s < n and (line[s] == ' ' or line[s] == '\t'):
|
||
s += 1
|
||
if s >= n:
|
||
return 0
|
||
if line[s] == '@':
|
||
return 1
|
||
i: int = n
|
||
while i > 0:
|
||
ch: char = line[i - 1]
|
||
if ch != ' ' and ch != '\t':
|
||
break
|
||
i -= 1
|
||
if i <= 0:
|
||
return 0
|
||
if line[i - 1] == ':':
|
||
return 1
|
||
return 0
|
||
|
||
|
||
# 追加一行到块缓冲:行间补一个 '\n',末尾写 NUL
|
||
def _block_append(line: str, n: int) -> int:
|
||
"""把 n 字节行追加到会话块缓冲;0=成功,1=容量不足(此时缓冲内容未改)"""
|
||
global ReplBlockLen
|
||
if ReplBlockBuf is None or line is None or n <= 0:
|
||
return 1
|
||
sep: int = 0
|
||
if ReplBlockLen > 0:
|
||
sep = 1 # 行间 '\n'
|
||
if ReplBlockLen + sep + n >= _REPL_BLOCK_CAP: # 末尾还需 1 字节 NUL
|
||
return 1 # 先判容量后写 ⇒ 失败时无需回滚
|
||
if ReplBlockLen > 0:
|
||
ReplBlockBuf[ReplBlockLen] = '\n'
|
||
ReplBlockLen += 1
|
||
string.memcpy(ReplBlockBuf + ReplBlockLen, line, sizet(n))
|
||
ReplBlockLen += n
|
||
ReplBlockBuf[ReplBlockLen] = 0
|
||
return 0
|
||
|
||
|
||
# ctx 头视图(int64 解释):宿主读写 magic/kind/result_a...
|
||
def _ctx_head() -> rawptr[GenTypes.ReplResult]:
|
||
"""ctx 头视图;会话未初始化返回 None"""
|
||
if ReplCtxBuf is None:
|
||
return None
|
||
return rawptr[GenTypes.ReplResult](ReplCtxBuf)
|
||
|
||
|
||
# ctx 头视图(double 解释 result_a):回显浮点结果用
|
||
def _ctx_float_head() -> rawptr[ReplFloatView]:
|
||
"""ctx 头浮点视图;会话未初始化返回 None"""
|
||
if ReplCtxBuf is None:
|
||
return None
|
||
return rawptr[ReplFloatView](ReplCtxBuf)
|
||
|
||
|
||
# 结果回显:按 result_kind 读 ctx 结果区并打印
|
||
def _repl_echo_result(frag_name: str, res: rawptr[GenTypes.ReplResult]) -> int:
|
||
"""按 result_kind 回显本片段结果;0=已处理(含"无结果")"""
|
||
if frag_name is None or res is None:
|
||
return 1
|
||
k: int = res.Kind
|
||
if k == GenTypes.REPL_RES_NONE:
|
||
# 声明类片段(如 `x: int`):本行无结果
|
||
return 0
|
||
if k == GenTypes.REPL_RES_INT:
|
||
stdio.printf("%s => %lld\n", frag_name, res.A)
|
||
return 0
|
||
if k == GenTypes.REPL_RES_FLOAT:
|
||
fv: rawptr[ReplFloatView] = _ctx_float_head()
|
||
if fv is None:
|
||
return 1
|
||
stdio.printf("%s => %f\n", frag_name, fv.F)
|
||
return 0
|
||
if k == GenTypes.REPL_RES_STR:
|
||
sp: rawptr = rawptr(res.A)
|
||
if sp is None:
|
||
stdio.printf("%s => <null str>\n", frag_name)
|
||
return 0
|
||
stdio.printf("%s => %s\n", frag_name, sp)
|
||
return 0
|
||
# REPL_RES_OBJ:result_a != 0 → 非字符串指针(报地址);result_a == 0 →
|
||
# 结构体值(内联在槽里,本身没有地址,类型名需跨片段类型注册表,属 M4)
|
||
if res.A == 0:
|
||
stdio.printf("%s => <struct 值>\n", frag_name)
|
||
return 0
|
||
stdio.printf("%s => <obj @ 0x%llX>\n", frag_name, res.A)
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# M4 - 会话 sha1 / 片段 import lib / 会话基线(CDefine + CTypedef)/ includes 类注册
|
||
#
|
||
# 三段机制各自解决一个"跨片段"问题(设计论证见 repl-design.md §M4):
|
||
# ① 会话 sha1:片段翻译器的 ModuleSha1 恒定取 GenTypes.REPL_SESSION_SHA1
|
||
# ⇒ 片段内定义的类型名恒定 "<sha1>.<Class>",会话 StructTable 的注册与后续
|
||
# 片段查找用同一个键(这是类型跨片段可见的唯一基础)。
|
||
# ② 片段 import lib:片段链接时加 --out-implib;若该片段 IR 里**定义**了会话前缀
|
||
# 符号(类方法/构造/类全局),登记其 .dll.a,后续片段链接时全部带上 ⇒ 那些
|
||
# 符号在链接期解析到**定义片段**的 DLL(代码与数据都只有一份)。
|
||
# ③ 会话基线:CDefine/CTypedef 表每片段被 clear_* 置空 ⇒ 用 PermPool 基线回灌 +
|
||
# 片段成功后追加增量,失败只按水位截断(表语义 = 整会话一份)。
|
||
# ============================================================
|
||
|
||
# 串深拷贝进 pool(调用方给池;池/串无效或分配失败返回 None)
|
||
def _repl_str_dup(pool: memhub.MemBuddy*, s: str) -> str:
|
||
"""深拷贝 s 到 pool;失败返回 None"""
|
||
if pool is None or s is None:
|
||
return None
|
||
n: int = int(string.strlen(s))
|
||
b: str = pool.alloc(n + 1)
|
||
if b is None:
|
||
return None
|
||
string.memcpy(b, s, sizet(n))
|
||
b[n] = 0
|
||
return b
|
||
|
||
|
||
# 建会话 sha1 串(PermPool;片段翻译期作为 ModuleSha1 使用)
|
||
def ReplInitSessionSha1() -> int:
|
||
"""0=成功;非 0=永久池/分配失败"""
|
||
global ReplSessionSha1
|
||
if _perm_pool is None:
|
||
return 1
|
||
ReplSessionSha1 = _repl_str_dup(_perm_pool, GenTypes.REPL_SESSION_SHA1)
|
||
if ReplSessionSha1 is None:
|
||
return 1
|
||
return 0
|
||
|
||
|
||
# ---- 会话基线(CDefine/CTypedef):查找 / 回灌 / 追加 / 截断 ----
|
||
|
||
def _repl_base_cdefine_find(name: str) -> int:
|
||
"""基线里按名查找 CDefine 条目下标;未命中返回 -1"""
|
||
if name is None or ReplBaseCDefineNames is None:
|
||
return -1
|
||
i: int
|
||
for i in range(ReplBaseCDefineCount):
|
||
nm: str = rawptr(ReplBaseCDefineNames[i])
|
||
if nm is not None and string.strcmp(nm, name) == 0:
|
||
return i
|
||
return -1
|
||
|
||
|
||
def ReplBaselineRestore(pool: memhub.MemBuddy*) -> int:
|
||
"""把会话基线(CDefine + CTypedef)回灌进刚被 clear_* 清空的表;0=成功
|
||
|
||
条目分配在 pool(= 本片段池):表每片段被清空,条目只需活到本片段结束。
|
||
回灌在 clear_* 之后、翻译之前调用 ⇒ 片段内的名字解析能同时看到
|
||
"会话基线(先前片段/先前 import 带进来的)∪ 本片段新增"。
|
||
"""
|
||
if pool is None or ReplBaseCDefineNames is None:
|
||
return 1
|
||
if GenApi._g_gen is None:
|
||
return 1
|
||
i: int
|
||
for i in range(ReplBaseCDefineCount):
|
||
nm: str = rawptr(ReplBaseCDefineNames[i])
|
||
val: str = rawptr(ReplBaseCDefineValues[i])
|
||
kind: int = int(ReplBaseCDefineKinds[i])
|
||
if nm is None or val is None:
|
||
continue
|
||
if kind == 0:
|
||
GenApi.register_cdefine_constant(pool, nm, int64(string.atoll(val)))
|
||
elif kind == 1:
|
||
GenApi.register_cdefine_double(pool, nm, string.atof(val))
|
||
else:
|
||
# 字符串 CDefine:GenApi 无同名壳,经 gen 单例直达(与 clear_cdefine_constants
|
||
# 的壳同款调用形态)
|
||
GenApi._g_gen.TypeGenCDef.register_cdefine_string(pool, nm, val)
|
||
for i in range(ReplBaseCTypedefCount):
|
||
tn: str = rawptr(ReplBaseCTypedefNames[i])
|
||
tv: str = rawptr(ReplBaseCTypedefTypeStrs[i])
|
||
if tn is None or tv is None:
|
||
continue
|
||
GenApi.register_ctypedef(pool, tn, tv)
|
||
return 0
|
||
|
||
|
||
def ReplBaselineCapture() -> int:
|
||
"""把当前表里"基线里还没有"的 CDefine/CTypedef 条目追加进会话基线;0=成功
|
||
|
||
只在片段**成功执行**后调用(失败片段不调用 ⇒ 基线天然不含失败片段的半成品,
|
||
无需回滚内容;截断只是防御性的一致性保障)。新条目深拷贝进 PermPool。
|
||
"""
|
||
global ReplBaseCDefineCount, ReplBaseCTypedefCount
|
||
if _perm_pool is None or GenApi._g_gen is None:
|
||
return 1
|
||
cdef_names: list[str]* = GenApi._g_gen.TypeGenCDef.CDefineNames
|
||
if cdef_names is not None:
|
||
cdef_vals: list[str]* = GenApi._g_gen.TypeGenCDef.CDefineValues
|
||
cdef_dbl: list[int]* = GenApi._g_gen.TypeGenCDef.CDefineDoubleFlags
|
||
cdef_str: list[int]* = GenApi._g_gen.TypeGenCDef.CDefineStrFlags
|
||
n: sizet = len(cdef_names)
|
||
i: sizet
|
||
for i in range(n):
|
||
nm: str = cdef_names.get(i)
|
||
if nm is None:
|
||
continue
|
||
if _repl_base_cdefine_find(nm) >= 0:
|
||
continue
|
||
if ReplBaseCDefineCount >= _REPL_BASE_CDEFINE_MAX:
|
||
break
|
||
val: str = None
|
||
if cdef_vals is not None:
|
||
val = cdef_vals.get(i)
|
||
if val is None:
|
||
continue
|
||
kind: int = 0
|
||
if cdef_str is not None and int(cdef_str.get(i)) == 1:
|
||
kind = 2
|
||
elif cdef_dbl is not None and int(cdef_dbl.get(i)) == 1:
|
||
kind = 1
|
||
nc: str = _repl_str_dup(_perm_pool, nm)
|
||
vc: str = _repl_str_dup(_perm_pool, val)
|
||
if nc is None or vc is None:
|
||
continue
|
||
j: int = ReplBaseCDefineCount
|
||
ReplBaseCDefineNames[j] = sizet(nc)
|
||
ReplBaseCDefineValues[j] = sizet(vc)
|
||
ReplBaseCDefineKinds[j] = sizet(kind)
|
||
ReplBaseCDefineCount = j + 1
|
||
ct_names: list[str]* = GenApi._g_gen.TypeGenCType.CTypedefNames
|
||
if ct_names is not None:
|
||
ct_vals: list[str]* = GenApi._g_gen.TypeGenCType.CTypedefTypeStrs
|
||
cn2: sizet = len(ct_names)
|
||
i2: sizet
|
||
for i2 in range(cn2):
|
||
tn: str = ct_names.get(i2)
|
||
if tn is None:
|
||
continue
|
||
if ReplBaseCTypedefCount >= _REPL_BASE_CTYPEDEF_MAX:
|
||
break
|
||
dup_hit: int = 0
|
||
k: int
|
||
for k in range(ReplBaseCTypedefCount):
|
||
ex: str = rawptr(ReplBaseCTypedefNames[k])
|
||
if ex is not None and string.strcmp(ex, tn) == 0:
|
||
dup_hit = 1
|
||
break
|
||
if dup_hit != 0:
|
||
continue
|
||
tv: str = None
|
||
if ct_vals is not None:
|
||
tv = ct_vals.get(i2)
|
||
if tv is None:
|
||
continue
|
||
tnc: str = _repl_str_dup(_perm_pool, tn)
|
||
tvc: str = _repl_str_dup(_perm_pool, tv)
|
||
if tnc is None or tvc is None:
|
||
continue
|
||
j2: int = ReplBaseCTypedefCount
|
||
ReplBaseCTypedefNames[j2] = sizet(tnc)
|
||
ReplBaseCTypedefTypeStrs[j2] = sizet(tvc)
|
||
ReplBaseCTypedefCount = j2 + 1
|
||
return 0
|
||
|
||
|
||
def ReplBaselineTruncate(cd_cnt: int, ct_cnt: int) -> None:
|
||
"""把基线水位截断回片段开始时的值(失败片段的增量不得进基线)"""
|
||
global ReplBaseCDefineCount, ReplBaseCTypedefCount
|
||
if cd_cnt >= 0 and cd_cnt <= ReplBaseCDefineCount:
|
||
ReplBaseCDefineCount = cd_cnt
|
||
if ct_cnt >= 0 and ct_cnt <= ReplBaseCTypedefCount:
|
||
ReplBaseCTypedefCount = ct_cnt
|
||
|
||
|
||
# ---- 片段 import lib:登记 / 追加到链接命令行 ----
|
||
|
||
def ReplFragImplibRecord(implib_path: str) -> int:
|
||
"""登记一个片段 import lib(PermPool 深拷贝);1=已登记,0=未登记(上限/分配失败)"""
|
||
global ReplFragImplibCount
|
||
if implib_path is None or ReplFragImplibs is None or _perm_pool is None:
|
||
return 0
|
||
if ReplFragImplibCount >= _REPL_FRAG_IMPLIB_MAX:
|
||
return 0
|
||
dup: str = _repl_str_dup(_perm_pool, implib_path)
|
||
if dup is None:
|
||
return 0
|
||
ReplFragImplibs[ReplFragImplibCount] = sizet(dup)
|
||
ReplFragImplibCount += 1
|
||
return 1
|
||
|
||
|
||
# 把已登记的全部片段 import lib 追加到命令缓冲(空格分隔);
|
||
# 返回新的位置(-1 = 容量不足,调用方按"不带片段 import lib"重试也不可——直接报错)
|
||
def _repl_cmd_append_implibs(cmd: bytes, cap: int, pos: int) -> int:
|
||
if cmd is None or ReplFragImplibs is None:
|
||
return pos
|
||
i: int
|
||
for i in range(ReplFragImplibCount):
|
||
p: str = rawptr(ReplFragImplibs[i])
|
||
if p is None:
|
||
continue
|
||
pos = _repl_cat_str(cmd, cap, pos, " ")
|
||
if pos < 0:
|
||
return -1
|
||
pos = _repl_cat_str(cmd, cap, pos, p)
|
||
if pos < 0:
|
||
return -1
|
||
return pos
|
||
|
||
|
||
# ---- includes 类注册(会话初始化一次;逐模块隔离,失败只告警)----
|
||
|
||
def ReplInitIncludesClasses(mb: memhub.MemBuddy*,
|
||
sr: rawptr[IncludesScanner.ScanResult],
|
||
vpi_dir: str) -> int:
|
||
"""把各 includes 模块的 .vpi 注入临时模块 ⇒ 其类进入会话 StructTable;0=完成
|
||
|
||
为什么需要:REPL 从不翻译 includes 源码 ⇒ 会话 StructTable(类型解析/字段查找/
|
||
方法归属的唯一入口)里没有任何 includes 类。`.vpi` 存根含类名 + 字段
|
||
(Converter._PyiClass 序列化类体 AnnAssign 与 __init__ 里的 `self.x: T`)+ 方法
|
||
签名;VpiReader.ParseVpiIntoModule(即批处理 LoadDepVpiIntoModule 的最后一步)
|
||
会把"字段类型全部可解析"的类注册为 StructEntry(含字段表)。
|
||
多遍收敛:字段类型可能引用同批的另一个类 ⇒ 单遍必然注册不全;本函数第 1 遍试
|
||
全部模块,第 2..N 遍只重试上一遍失败的模块(成功者不重复注入 ⇒ 代价受控)。
|
||
启动上界:整段注册有 `_REPL_INC_CLS_BUDGET_MS` 时间预算,超预算即停止尝试剩余
|
||
模块(计数进汇总行;这些模块的类在会话里不可用,片段 `import` 时仍按需注册)。
|
||
逐模块隔离:每模块一次 _setjmp(复用会话 jmp_buf)⇒ 单模块的 fatal/解析失败只
|
||
影响该模块(逐模块告警 + 该模块的类不可用),**不终止会话**、不影响其它模块。
|
||
告警聚合:逐模块告警最多打印 `_REPL_INC_CLS_WARN_MAX` 条,其余只累计计数,
|
||
末尾一条汇总行给出"成功/失败/缺存根/未尝试/被省略告警"的数量。
|
||
⚠ 退出约定:本函数内部改写了 jmp_buf 的跳转目标(指向本帧)⇒ 返回后调用方必须
|
||
在**自己的帧上**重新武装恢复点(见 Repl.run 的 includes 段),否则后续 longjmp
|
||
会跳到已返回的帧(未定义行为)。
|
||
收尾(ReplStubLayoutFinish/ReplStubLayoutEnd)**不在**本函数内做:它们只能在
|
||
调用方重新武装恢复点之后调用(见 Repl.run),否则"表满 → VLogger.error →
|
||
longjmp 到已返回帧"会在提示符出现前把进程带走。
|
||
"""
|
||
global ReplIncClsMods, ReplIncClsFail, ReplIncClsMissing
|
||
if mb is None or sr is None or vpi_dir is None or sr.Entries is None:
|
||
return 1
|
||
if ReplJmpBuf is None or _perm_pool is None:
|
||
return 1
|
||
n_mod: int = int(sr.Count)
|
||
if n_mod <= 0:
|
||
return 1
|
||
if n_mod > _REPL_INC_CLS_MOD_MAX:
|
||
n_mod = _REPL_INC_CLS_MOD_MAX
|
||
# 每模块状态(8 字节/槽,PermPool):0=未尝试/待重试、1=已完成(成功或确认不可用)
|
||
st: sizet* = _perm_pool.alloc(_REPL_INC_CLS_MOD_MAX * 8)
|
||
if st is None:
|
||
return 1
|
||
string.memset(st, 0, _REPL_INC_CLS_MOD_MAX * 8)
|
||
# 注入目标:临时 LLVM 模块 + 临时翻译器(只用其注解解析上下文;模块本身丢弃)
|
||
# 结构体注册走永久分配(StdlibAlloc=1)⇒ 条目/字段/类型跨片段存活。
|
||
GenApi.set_stdlib_alloc(1)
|
||
GenApi.reset_visible_structs(mb, 0)
|
||
GenApi.set_temp_dir(vpi_dir)
|
||
GenApi.SetTempDir(vpi_dir)
|
||
GenApi.set_current_file_type(vpi_dir)
|
||
tmp_mod: llvmlite.LLVMModule* = llvmlite.new_module(_perm_pool, _REPL_INC_MOD_NAME)
|
||
tr_inc: rawptr[HandlesTranslator.Translator] = _perm_pool.alloc(HandlesTranslator.Translator.__sizeof__())
|
||
if tmp_mod is None or tr_inc is None:
|
||
return 1
|
||
tr_inc.__before_init__()
|
||
tr_inc.__init__()
|
||
# 给临时翻译器一套真状态(函数表/符号表/名集合缓冲区 + Pool):VpiReader 的
|
||
# 注入会读 trans._funcs(把 inject 出的 declare 登记进函数表,VpiReader:2130)
|
||
# 与 trans.Pool/imported_modules 等;缺它们会在注入中途解引用 None。
|
||
tr_inc._init_state(_perm_pool)
|
||
tr_inc.ModuleSha1 = ReplSessionSha1
|
||
tr_inc._declare_only = 0
|
||
# ReplCtx 置位 = 声明本翻译器处于 REPL 会话(Gen 侧唯一开关,见
|
||
# Translator.vp:256-257)。注入各模块 `.vpi` 时,其注解可能引用"会话里
|
||
# **未登记**(M4.2 布局不可信被拒 / 字段类型不可解析被跳过)的类"——如
|
||
# ast 包的 `rawptr[memhub.MemManager]`。此时由 TypeGenResolve 的 REPL 分支
|
||
# (`trans.ReplCtx != None` 守卫)按定义侧命名造 opaque 具名类型占位,使注解
|
||
# 可解析,避免 S4 fail-fast 中断整个模块的注入;StructTable 仍不登记该类
|
||
# (字段访问照旧明确报错)。批处理 ReplCtx 恒 None ⇒ 该分支不经过。
|
||
# 片段翻译另有自己的 Translator(ReplExecFragment 内同样置 ReplCtx)。
|
||
tr_inc.ReplCtx = ReplCtxObj
|
||
es: sizet = IncludesScanner.FileEntry.__sizeof__()
|
||
dl: sizet = string.strlen(vpi_dir)
|
||
warn_left: int = _REPL_INC_CLS_WARN_MAX
|
||
warn_dropped: int = 0
|
||
budget_t0: uint64 = win32base.GetTickCount64()
|
||
budget_hit: int = 0
|
||
p_i: int
|
||
for p_i in range(_REPL_INC_CLS_PASSES):
|
||
pass_ok: int = 0
|
||
i: int
|
||
for i in range(n_mod):
|
||
if int(st[i]) != 0:
|
||
continue
|
||
# 启动预算(每个模块一次取时;GetTickCount64 无系统调用开销):
|
||
# 超预算即停手,剩余待注册模块留给片段 `import` 的按需路径。
|
||
if int(win32base.GetTickCount64() - budget_t0) > _REPL_INC_CLS_BUDGET_MS:
|
||
budget_hit = 1
|
||
break
|
||
if GenApi.get_struct_count() >= _REPL_INC_CLS_STRUCT_GUARD:
|
||
stdio.printf("includes 类注册: 结构体表已达 %d 条(上限附近),"
|
||
"停止注册;未注册的类在会话里不可用(import 其模块"
|
||
"仍可按需注册)\n", GenApi.get_struct_count())
|
||
stdio.fflush(None)
|
||
return 0
|
||
ent: rawptr[IncludesScanner.FileEntry] = rawptr[IncludesScanner.FileEntry](uint64(sr.Entries) + uint64(i) * uint64(es))
|
||
if ent is None or ent.Sha1 is None:
|
||
st[i] = sizet(1)
|
||
ReplIncClsFail += 1
|
||
continue
|
||
vm: sizet = mb.mark()
|
||
vpi_path: str = StubMergerCollect._sliced_path(vpi_dir, dl, ent.Sha1, "vpi")
|
||
if vpi_path is None:
|
||
st[i] = sizet(1)
|
||
ReplIncClsFail += 1
|
||
mb.reset_to(vm)
|
||
continue
|
||
if _is_file(vpi_path) == 0:
|
||
# 缺存根(未构建过宿主 / includes 目录不含该模块):该模块的类
|
||
# 不可用,会话继续(import 该模块时同样按缺存根退化)
|
||
ReplIncClsMissing += 1
|
||
if warn_left > 0:
|
||
stdio.printf("includes 类注册: 模块 %s 无 .vpi 存根 —— 其类在"
|
||
"会话中不可用(不影响其它模块;先按 project.vpj "
|
||
"构建一次宿主可生成全部存根)\n", ent.Sha1)
|
||
warn_left -= 1
|
||
else:
|
||
warn_dropped += 1
|
||
stdlib.free(vpi_path)
|
||
mb.reset_to(vm)
|
||
st[i] = sizet(1)
|
||
continue
|
||
# 注入期把 trans.ModuleSha1 置为**当前 stub 的源模块 sha1**(对齐批处理
|
||
# "trans.ModuleSha1 = 正在翻译的文件"):注解里的"本模块裸类名"走
|
||
# find_struct_by_module(name, trans.ModuleSha1),父类判定
|
||
# (_collect_parents → find_struct_by_module(parent_name, ModuleSha1))也读它;
|
||
# 同时 REPL 的"未登记类类型占位"(TypeGenResolve 的 ReplCtx 分支)按它拼
|
||
# "{sha1}.{类名}"。沿用会话 sha1 会让本模块类查找与占位命名都错位。
|
||
tr_inc.ModuleSha1 = ent.Sha1
|
||
# 每模块一个恢复点:单模块 fatal 只丢弃该模块**本次**注入(已注册的类
|
||
# 由结构体表去重语义兜住:同名同 sha1 再注入时 find_struct_by_module
|
||
# 命中即只 add_visible_struct,不新增条目)。注意:longjmp 回来后
|
||
# vpi_path/vm 之外的本帧局部(在 _setjmp 之后赋值的)值不确定,故恢复
|
||
# 分支只做 mb.reset_to(vm)——vpi_path 是 stdlib 串,此处不释放(每模块
|
||
# 泄漏 ≤512B,仅发生在 fatal 路径)。
|
||
rc_m: int = _setjmp(ReplJmpBuf, None)
|
||
if rc_m != 0:
|
||
ReplIncClsFail += 1
|
||
if warn_left > 0:
|
||
stdio.printf("includes 类注册: 模块 %d 的 .vpi 注入中断"
|
||
"(其类可能不完整;会话继续)\n", i)
|
||
warn_left -= 1
|
||
else:
|
||
warn_dropped += 1
|
||
mb.reset_to(vm)
|
||
continue
|
||
ok_m: int = VpiReader.ParseVpiIntoModule(_perm_pool, tmp_mod, vpi_path, tr_inc)
|
||
if ok_m != 0:
|
||
st[i] = sizet(1)
|
||
ReplIncClsMods += 1
|
||
pass_ok += 1
|
||
else:
|
||
ReplIncClsFail += 1
|
||
if warn_left > 0:
|
||
stdio.printf("includes 类注册: 模块 %s 的 .vpi 注入失败"
|
||
"(该模块的类不完整/不可用;会话继续)\n", ent.Sha1)
|
||
warn_left -= 1
|
||
else:
|
||
warn_dropped += 1
|
||
stdlib.free(vpi_path)
|
||
mb.reset_to(vm)
|
||
# 本遍没有任何模块成功 ⇒ 后续遍不会带来新信息(失败的模块留在待重试集里,
|
||
# 但重试同样会失败;省下全部重试的解析开销)
|
||
if pass_ok == 0:
|
||
break
|
||
# 汇总(聚合告警):逐模块告警已按上限截断,这里给出全部计数与未尝试模块数。
|
||
skip_cnt: int = 0
|
||
si: int
|
||
for si in range(n_mod):
|
||
if int(st[si]) == 0:
|
||
skip_cnt += 1
|
||
stdio.printf("includes 类: 已注册 %d 个模块的 vpi 类(失败 %d,缺存根 %d,"
|
||
"未尝试/待重试 %d,省略告警 %d)\n",
|
||
ReplIncClsMods, ReplIncClsFail, ReplIncClsMissing,
|
||
skip_cnt, warn_dropped)
|
||
if budget_hit != 0:
|
||
stdio.printf("includes 类: 启动预算 %d ms 用尽,已停止启动期注册(剩余 %d 个"
|
||
"模块的类在会话里不可用;片段 `import` 该模块时仍按需注册)\n",
|
||
_REPL_INC_CLS_BUDGET_MS, skip_cnt)
|
||
stdio.fflush(None)
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# 会话初始化:建 %TEMP%\TransPyV_repl\<pid>\ + 一次性分配常驻缓冲 + ctx
|
||
# 返回 0 成功;非 0 失败(原因由调用方打印)。
|
||
# ============================================================
|
||
def ReplInitSession() -> int:
|
||
"""建临时目录 + PermPool 常驻缓冲 + ctx 会话内存;0=成功"""
|
||
global ReplTmpDir, ReplNameBuf, ReplSrcBuf, ReplSrcPath, ReplIrBuf, ReplNeedleBuf
|
||
global ReplCtxBuf, ReplCtxObj, ReplVarTable, ReplScratch
|
||
global ReplBlockBuf, ReplBlockLen, ReplInBlock
|
||
global ReplLiftBuf, ReplLiftNames, ReplLiftNameCount, ReplSigTags, ReplSigBits
|
||
global ReplSigResult, ReplLineShow, ReplMsgName, ReplTySpan, ReplTySpan3
|
||
global ReplIrDefOff, ReplSigText, ReplSigText2, ReplWrapPos, ReplSavedFuncCount
|
||
global ReplSessionSha1, ReplFragImplibs, ReplFragImplibCount
|
||
global ReplBaseCDefineNames, ReplBaseCDefineValues, ReplBaseCDefineKinds
|
||
global ReplBaseCDefineCount, ReplBaseCTypedefNames, ReplBaseCTypedefTypeStrs
|
||
global ReplBaseCTypedefCount, ReplSavedCDefineCount, ReplSavedCTypedefCount
|
||
global ReplIncClsMods, ReplIncClsFail, ReplIncClsMissing
|
||
if _perm_pool is None:
|
||
return 1
|
||
# ① %TEMP% 前缀(GetTempPathA 返回值含结尾反斜杠;0 表示失败)
|
||
tmp_buf: bytes = _perm_pool.alloc(_REPL_TMP_CAP)
|
||
if tmp_buf is None:
|
||
return 1
|
||
tmp_len: ULONG = w32file.GetTempPathA(_REPL_TMP_CAP, tmp_buf)
|
||
if tmp_len == 0:
|
||
return 1
|
||
if int(tmp_len) >= _REPL_TMP_CAP:
|
||
return 1
|
||
# ② 会话目录:<TEMP>TransPyV_repl/<pid>(pid 隔离并发会话;路径统一用 '/')
|
||
ReplTmpDir = _perm_pool.alloc(_REPL_PATH_CAP)
|
||
if ReplTmpDir is None:
|
||
return 1
|
||
viperlib.snprintf(ReplTmpDir, _REPL_PATH_CAP, "%sTransPyV_repl/%u",
|
||
tmp_buf, win32process.GetCurrentProcessId())
|
||
if BuildPipeline.ensure_dir(ReplTmpDir) != 0:
|
||
return 1
|
||
# ③ 常驻缓冲(会话期存活,不按片段反复分配)
|
||
ReplNameBuf = _perm_pool.alloc(_REPL_NAME_CAP)
|
||
ReplSrcBuf = _perm_pool.alloc(_REPL_FRAG_SRC_CAP)
|
||
ReplSrcPath = _perm_pool.alloc(_REPL_PATH_CAP)
|
||
ReplIrBuf = _perm_pool.alloc(_REPL_IR_CAP)
|
||
ReplNeedleBuf = _perm_pool.alloc(_REPL_NEEDLE_CAP)
|
||
ReplBlockBuf = _perm_pool.alloc(_REPL_BLOCK_CAP)
|
||
ReplBlockLen = 0
|
||
ReplInBlock = 0
|
||
if ReplNameBuf is None or ReplSrcBuf is None or ReplSrcPath is None:
|
||
return 1
|
||
if ReplIrBuf is None or ReplNeedleBuf is None:
|
||
return 1
|
||
if ReplBlockBuf is None:
|
||
return 1
|
||
ReplBlockBuf[0] = 0
|
||
# ③' M3 常驻缓冲:提升源码 / 会话函数名表 / 签名 token scratch / 诊断与渲染缓冲
|
||
ReplLiftBuf = _perm_pool.alloc(_REPL_LIFT_CAP)
|
||
ReplLiftNames = _perm_pool.alloc(_REPL_FN_NAMES_CAP)
|
||
ReplSigTags = _perm_pool.alloc(GenTypes.REPL_FUNC_PARAM_MAX)
|
||
ReplSigBits = _perm_pool.alloc(GenTypes.REPL_FUNC_PARAM_MAX)
|
||
ReplSigResult = _perm_pool.alloc(ReplSigOut.__sizeof__())
|
||
ReplLineShow = _perm_pool.alloc(_REPL_DIAG_CAP)
|
||
ReplMsgName = _perm_pool.alloc(_REPL_FN_NAME_MAX)
|
||
ReplTySpan = _perm_pool.alloc(_REPL_FN_TY_CAP)
|
||
ReplTySpan3 = _perm_pool.alloc(_REPL_FN_TY_CAP)
|
||
ReplSigText = _perm_pool.alloc(_REPL_SIG_TEXT_CAP)
|
||
ReplSigText2 = _perm_pool.alloc(_REPL_SIG_TEXT_CAP)
|
||
if ReplLiftBuf is None or ReplLiftNames is None or ReplSigTags is None \
|
||
or ReplSigBits is None or ReplSigResult is None:
|
||
return 1
|
||
if ReplLineShow is None or ReplMsgName is None or ReplTySpan is None \
|
||
or ReplTySpan3 is None or ReplSigText is None or ReplSigText2 is None:
|
||
return 1
|
||
ReplLiftBuf[0] = 0
|
||
ReplLiftNames[0] = 0
|
||
ReplLiftNameCount = 0
|
||
ReplIrDefOff = -1
|
||
ReplWrapPos = 0
|
||
ReplSavedFuncCount = 0
|
||
# ④ ctx 会话内存(片段首形参指向它):头 64B + 变量槽 64×16B + 函数指针槽 32×16B,全部清零
|
||
ReplCtxBuf = _perm_pool.alloc(_REPL_CTX_SIZE)
|
||
if ReplCtxBuf is None:
|
||
return 1
|
||
string.memset(ReplCtxBuf, 0, _REPL_CTX_SIZE)
|
||
# ⑤ 会话变量表 + 影子条目(PermPool;条目名/结构体名由 Gen 侧深拷贝进同一池)
|
||
entry_size: sizet = GenTypes.ReplVarEntry.__sizeof__()
|
||
ReplVarTable = _perm_pool.alloc(entry_size * GenTypes.REPL_VAR_CAP)
|
||
if ReplVarTable is None:
|
||
return 1
|
||
string.memset(ReplVarTable, 0, entry_size * GenTypes.REPL_VAR_CAP)
|
||
ReplScratch = _perm_pool.alloc(entry_size)
|
||
if ReplScratch is None:
|
||
return 1
|
||
string.memset(ReplScratch, 0, entry_size)
|
||
# 影子条目的名字(错误消息可读:"<expr>")
|
||
repl_scratch_name: str = _perm_pool.alloc(8)
|
||
if repl_scratch_name is None:
|
||
return 1
|
||
viperlib.snprintf(repl_scratch_name, 8, "%s", "<expr>")
|
||
ReplScratch.Name = repl_scratch_name
|
||
ReplScratch.TyName = None
|
||
ReplScratch.Offset = 0
|
||
ReplScratch.Kind = GenTypes.REPL_KIND_UNTYPED
|
||
ReplScratch.TyTag = 0
|
||
ReplScratch.Bits = 0
|
||
# ⑤' 会话函数表(M3;PermPool,宿主写、Gen 只读)——条目数组清零
|
||
func_entry_size: sizet = GenTypes.ReplFuncEntry.__sizeof__()
|
||
ReplFuncTable: rawptr[GenTypes.ReplFuncEntry] = _perm_pool.alloc(func_entry_size * GenTypes.REPL_FUNC_CAP)
|
||
if ReplFuncTable is None:
|
||
return 1
|
||
string.memset(ReplFuncTable, 0, func_entry_size * GenTypes.REPL_FUNC_CAP)
|
||
# ⑥ 会话上下文对象(Gen 侧唯一开关:trans.ReplCtx)
|
||
ReplCtxObj = _perm_pool.alloc(GenTypes.ReplCtx.__sizeof__())
|
||
if ReplCtxObj is None:
|
||
return 1
|
||
string.memset(ReplCtxObj, 0, GenTypes.ReplCtx.__sizeof__())
|
||
ctx_param: str = _perm_pool.alloc(_REPL_NAME_CAP)
|
||
if ctx_param is None:
|
||
return 1
|
||
viperlib.snprintf(ctx_param, _REPL_NAME_CAP, "%s", GenTypes.REPL_CTX_PARAM)
|
||
ReplCtxObj.Marker = GenTypes.REPL_CTX_MARKER
|
||
ReplCtxObj.Table = ReplVarTable
|
||
ReplCtxObj.Count = 0
|
||
ReplCtxObj.Cap = GenTypes.REPL_VAR_CAP
|
||
ReplCtxObj.CtxParam = ctx_param
|
||
ReplCtxObj.SlotSize = GenTypes.REPL_SLOT_SIZE
|
||
ReplCtxObj.Scratch = ReplScratch
|
||
ReplCtxObj.Perm = _perm_pool
|
||
# 会话函数表(M3):切片跨片段存活 ⇒ 表体/条目名/token 数组都在 PermPool
|
||
ReplCtxObj.FuncTable = ReplFuncTable
|
||
ReplCtxObj.FuncCount = 0
|
||
ReplCtxObj.FuncCap = GenTypes.REPL_FUNC_CAP
|
||
ReplSavedFuncCount = 0
|
||
# ⑦ ctx 固定头:magic/version 由宿主写一次(片段只读约定,不校验)
|
||
head: rawptr[GenTypes.ReplResult] = _ctx_head()
|
||
if head is None:
|
||
return 1
|
||
head.Magic = GenTypes.REPL_CTX_MAGIC
|
||
head.Version = GenTypes.REPL_CTX_VERSION
|
||
head.Kind = GenTypes.REPL_RES_NONE
|
||
head.Reserved0 = 0
|
||
head.A = 0
|
||
head.B = 0
|
||
head.VarCount = 0
|
||
head.LastFrag = 0
|
||
# ⑧ M4 常驻状态:会话 sha1 + 片段 import lib 表 + 会话基线(CDefine/CTypedef)
|
||
if ReplInitSessionSha1() != 0:
|
||
return 1
|
||
ReplFragImplibs = _perm_pool.alloc(_REPL_FRAG_IMPLIB_ARR_CAP)
|
||
ReplBaseCDefineNames = _perm_pool.alloc(8 * _REPL_BASE_CDEFINE_MAX)
|
||
ReplBaseCDefineValues = _perm_pool.alloc(8 * _REPL_BASE_CDEFINE_MAX)
|
||
ReplBaseCDefineKinds = _perm_pool.alloc(8 * _REPL_BASE_CDEFINE_MAX)
|
||
ReplBaseCTypedefNames = _perm_pool.alloc(8 * _REPL_BASE_CTYPEDEF_MAX)
|
||
ReplBaseCTypedefTypeStrs = _perm_pool.alloc(8 * _REPL_BASE_CTYPEDEF_MAX)
|
||
if ReplFragImplibs is None or ReplBaseCDefineNames is None \
|
||
or ReplBaseCDefineValues is None or ReplBaseCDefineKinds is None:
|
||
return 1
|
||
if ReplBaseCTypedefNames is None or ReplBaseCTypedefTypeStrs is None:
|
||
return 1
|
||
string.memset(ReplFragImplibs, 0, _REPL_FRAG_IMPLIB_ARR_CAP)
|
||
string.memset(ReplBaseCDefineNames, 0, 8 * _REPL_BASE_CDEFINE_MAX)
|
||
string.memset(ReplBaseCDefineValues, 0, 8 * _REPL_BASE_CDEFINE_MAX)
|
||
string.memset(ReplBaseCDefineKinds, 0, 8 * _REPL_BASE_CDEFINE_MAX)
|
||
string.memset(ReplBaseCTypedefNames, 0, 8 * _REPL_BASE_CTYPEDEF_MAX)
|
||
string.memset(ReplBaseCTypedefTypeStrs, 0, 8 * _REPL_BASE_CTYPEDEF_MAX)
|
||
ReplFragImplibCount = 0
|
||
ReplBaseCDefineCount = 0
|
||
ReplBaseCTypedefCount = 0
|
||
ReplSavedCDefineCount = 0
|
||
ReplSavedCTypedefCount = 0
|
||
ReplIncClsMods = 0
|
||
ReplIncClsFail = 0
|
||
ReplIncClsMissing = 0
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# includes 会话级初始化:扫描 includes 目录 → 注册"模块名 → SHA1" → 预生成 .vpi 存根
|
||
#
|
||
# 为什么必须做:片段按**全量翻译**走(tr._declare_only = 0),片段体里的 import 会
|
||
# 经 ImportsGenMixin._check_import_sha1(ImportsGenMixin.vp:781-812)校验"模块名 →
|
||
# SHA1"是否已注册,未注册即 fail-fast("模块 'X' 未注册 SHA1(includes 扫描未找到
|
||
# 该模块…)")。该判据查的是 StubMerger 的全局存储器
|
||
# (StubMerger/__init__.vp:46-50 的 _sha1_store_arr/_mod/_rel/_incdir + _count,
|
||
# 由 StubMergerStore.vp 读写);批处理路径上这张表由 Phase1 依赖图扫描填充
|
||
# (Phase1Main.RunPhase1 → StubMergerBuild._BuildReachableSha1Set →
|
||
# AddIncludeToSha1MapStore,StubMergerBuild.vp:1049),而 REPL 从不跑 Phase1
|
||
# ⇒ 表为空 ⇒ 片段里任何 `import <includes 模块>` 都报未注册 SHA1。
|
||
#
|
||
# .vpi 存根同理:跨模块的返回类型/CDefine/CExport 判定都要读
|
||
# {GenApi temp_dir}/{sha1[0:2]}/{sha1}.vpi(CallGenMixin.vp:1364-1370、
|
||
# CallGenBase._LookupMethodRetTyFromTextLL:1092、ExprGenCdef.vp:282),批处理由
|
||
# Phase1 1b-pre 预生成(Phase1Main.vp:716-798),REPL 不跑 Phase1 ⇒ 目录里没有
|
||
# ⇒ 跨模块解析退化为默认推断(如返回类型默认 i32)。
|
||
#
|
||
# 复用既有能力(不另写扫描):
|
||
# - 扫描:IncludesScanner.scan_includes(与 Phase1Main.vp:526 同款调用);
|
||
# - 注册:StubMergerStore.PopulateSha1MapStore(全量 includes → 同一张表,
|
||
# 写入字段与 AddIncludeToSha1MapStore 一致)。批处理现入口
|
||
# _BuildReachableSha1Set 与 Phase1 强耦合(需 Config.SourceDir、deps 图、
|
||
# worklist、写 deps.txt),repl.vpj 刻意不含 source_dir,故取同族的"全量扫描
|
||
# + 注册"入口;差别是该表在批处理只含**可达** includes,REPL 含全部 includes
|
||
# (超集,注册内容/消费者完全一致:LookupSha1ByModName/Suffix);
|
||
# - 存根:StubConverter._GeneratePyiFromAst + StubMergerCollect._sliced_path
|
||
# (与 Phase1Main.vp:787-798、BuildPipeline.TranslateFileGetTrans:121-131 同款)。
|
||
#
|
||
# 一次性 vs 每次:放会话开始时做一次。includes 源文件在一个会话内视为静态
|
||
# (片段新增的是片段源码,不落 includes 目录),重扫会给每个片段增加 O(文件数)
|
||
# 的读盘 + SHA1/parse 成本;改动 includes 后重启会话即可。
|
||
#
|
||
# 失败口径(宽容,与 M0 的恢复点一致):includes 目录缺失/扫描为 0/存根生成失败
|
||
# 都只告警,**不终止会话**——"不带 import 的片段"仍照常可用。
|
||
# ============================================================
|
||
def _repl_gen_includes_vpi(mb: memhub.MemBuddy*,
|
||
sr: rawptr[IncludesScanner.ScanResult],
|
||
vpi_dir: str) -> int:
|
||
"""为 includes 扫描结果预生成 .vpi 存根;返回新生成个数(-1=参数错误)
|
||
|
||
已存在同名 .vpi 直接复用(.vpi 按内容 SHA1 命名 ⇒ 同名必然同内容,与
|
||
Phase1 1b-pre 的"vpi 已存在则跳过"同判据)。每文件取一次 mb.mark() 并在末尾
|
||
reset_to:读文件/parse/generate 的瞬时分配不跨文件累积(对齐 Phase1 1b-pre)。
|
||
"""
|
||
global ReplIncVpiNew, ReplIncVpiReuse, ReplIncVpiFail
|
||
if mb is None or sr is None or vpi_dir is None:
|
||
return -1
|
||
if sr.Entries is None:
|
||
return -1
|
||
es: sizet = IncludesScanner.FileEntry.__sizeof__()
|
||
dl: sizet = string.strlen(vpi_dir)
|
||
n: int
|
||
for n in range(sr.Count):
|
||
ent: rawptr[IncludesScanner.FileEntry] = rawptr[IncludesScanner.FileEntry](uint64(sr.Entries) + uint64(n) * es)
|
||
if ent is None or ent.Sha1 is None or ent.Path is None:
|
||
ReplIncVpiFail += 1
|
||
continue
|
||
fm: sizet = mb.mark()
|
||
vpi_path: str = StubMergerCollect._sliced_path(vpi_dir, dl, ent.Sha1, "vpi")
|
||
if vpi_path is None:
|
||
ReplIncVpiFail += 1
|
||
mb.reset_to(fm)
|
||
continue
|
||
# 已有存根 → 复用
|
||
vf: rawptr[fileio.File] = fileio.File(vpi_path, fileio.MODE.R)
|
||
if not vf.closed:
|
||
vf.close()
|
||
stdlib.free(vpi_path)
|
||
ReplIncVpiReuse += 1
|
||
mb.reset_to(fm)
|
||
continue
|
||
# 读源文件(1MB 上限与 Phase1/BuildPipeline 的 SRC_BUF_SIZE 一致)
|
||
f: rawptr[fileio.File] = fileio.File(ent.Path, fileio.MODE.R)
|
||
if f.closed:
|
||
stdlib.free(vpi_path)
|
||
ReplIncVpiFail += 1
|
||
mb.reset_to(fm)
|
||
continue
|
||
if f.size() >= _REPL_SRC_BUF_CAP:
|
||
f.close()
|
||
stdlib.free(vpi_path)
|
||
ReplIncVpiFail += 1
|
||
mb.reset_to(fm)
|
||
continue
|
||
src_buf: bytes = stdlib.malloc(_REPL_SRC_BUF_CAP)
|
||
if src_buf is None:
|
||
f.close()
|
||
stdlib.free(vpi_path)
|
||
ReplIncVpiFail += 1
|
||
mb.reset_to(fm)
|
||
continue
|
||
bytes_read: LONG = f.read_all(src_buf, _REPL_SRC_BUF_CAP)
|
||
f.close()
|
||
if bytes_read <= 0:
|
||
stdlib.free(src_buf)
|
||
stdlib.free(vpi_path)
|
||
ReplIncVpiFail += 1
|
||
mb.reset_to(fm)
|
||
continue
|
||
if bytes_read < _REPL_SRC_BUF_CAP:
|
||
src_buf[bytes_read] = 0
|
||
# 解析(AST 全部分配在本文件的 mb 窗口内)
|
||
lx: ast.Lexer* = ast.new_lexer(mb)
|
||
tree: ast.AST* = None
|
||
if lx is not None:
|
||
ast._lexer_init(lx, src_buf, mb)
|
||
tokens: ast.Token* = ast.tokenize(lx)
|
||
tree = ast.parse_tokens(mb, tokens)
|
||
if tree is None:
|
||
stdlib.free(src_buf)
|
||
stdlib.free(vpi_path)
|
||
ReplIncVpiFail += 1
|
||
mb.reset_to(fm)
|
||
continue
|
||
pyi_buf: bytes = stdlib.malloc(_REPL_VPI_BUF_CAP)
|
||
if pyi_buf is None:
|
||
stdlib.free(src_buf)
|
||
stdlib.free(vpi_path)
|
||
ReplIncVpiFail += 1
|
||
mb.reset_to(fm)
|
||
continue
|
||
# rel_path 传绝对路径 ent.Path:vpi 生成期的 fatal_error 需按该路径读源码行
|
||
pos: sizet = StubConverter._GeneratePyiFromAst(mb, tree, ent.Path,
|
||
pyi_buf, _REPL_VPI_BUF_CAP)
|
||
if pos > 0:
|
||
pf: rawptr[fileio.File] = fileio.File(vpi_path, fileio.MODE.W)
|
||
if not pf.closed:
|
||
pf.write(pyi_buf, pos)
|
||
pf.close()
|
||
ReplIncVpiNew += 1
|
||
else:
|
||
ReplIncVpiFail += 1
|
||
else:
|
||
ReplIncVpiFail += 1
|
||
stdlib.free(pyi_buf)
|
||
stdlib.free(src_buf)
|
||
stdlib.free(vpi_path)
|
||
mb.reset_to(fm)
|
||
return ReplIncVpiNew
|
||
|
||
|
||
def ReplInitIncludes(mb: memhub.MemBuddy*) -> int:
|
||
"""会话级 includes 初始化:扫描 → 注册 SHA1 → 预生成 .vpi → 注册泛型模板
|
||
→ 逐模块类注册;0=成功,非 0=降级跳过
|
||
|
||
降级(返回非 0)时调用方只告警、继续进入读行循环(不带 import 的片段不受影响)。
|
||
⚠ 本函数内部会经 `ReplInitIncludesClasses` 使用**嵌套 _setjmp**(逐模块隔离),
|
||
返回后 jmp_buf 的跳转目标指向那些已返回的帧 ⇒ 调用方必须在**自己的帧上**重新
|
||
武装恢复点,再做 includes 收尾(ReplStubLayoutFinish/ReplStubLayoutEnd,见 run)。
|
||
故收尾不放在本函数内。
|
||
"""
|
||
global ReplIncMods, ReplIncMs
|
||
if mb is None:
|
||
return 1
|
||
inc_dirs: str = Config.IncludesDirs
|
||
if inc_dirs is None or string.strlen(inc_dirs) == 0:
|
||
stdio.printf("REPL: 工程未配置 includes 目录(project.vpj 的 includes),"
|
||
"跳过 includes 初始化;import includes 模块会报未注册 SHA1\n")
|
||
return 1
|
||
# .vpi 目录与片段侧一致:优先工程临时目录,无工程配置时退回会话目录
|
||
vpi_dir: str = Config.TempDir
|
||
if vpi_dir is None:
|
||
vpi_dir = ReplTmpDir
|
||
if vpi_dir is None:
|
||
stdio.printf("REPL: 无可用 .vpi 目录(Config.TempDir 与会话目录均为空),"
|
||
"跳过 includes 初始化\n")
|
||
return 1
|
||
# ① 扫描 includes(可达性不判定:REPL 无 SourceDir/deps 图,取全量超集)
|
||
t0: uint64 = win32base.GetTickCount64()
|
||
sr: rawptr[IncludesScanner.ScanResult] = IncludesScanner.scan_includes(mb, inc_dirs)
|
||
if sr is None:
|
||
stdio.printf("REPL: includes 扫描失败(目录: %s);"
|
||
"import includes 模块会报未注册 SHA1\n", inc_dirs)
|
||
return 1
|
||
# ② 注册"模块名 → SHA1"(同一张 StubMerger 全局表,批处理消费者直接可用)
|
||
n: int = StubMergerStore.PopulateSha1MapStore(sr)
|
||
ReplIncMs = int(win32base.GetTickCount64() - t0)
|
||
if n <= 0:
|
||
stdio.printf("REPL: includes 扫描未注册任何模块(目录: %s):"
|
||
"请检查 includes 路径与 .vp 文件\n", inc_dirs)
|
||
return 1
|
||
ReplIncMods = n
|
||
# ③ 预生成 .vpi 存根(失败逐个计数,不终止会话)
|
||
_repl_gen_includes_vpi(mb, sr, vpi_dir)
|
||
stdio.printf("includes: 已注册 %d 个模块(扫描耗时 %d ms);"
|
||
"存根 新生成 %d / 复用 %d / 失败 %d\n",
|
||
n, ReplIncMs, ReplIncVpiNew, ReplIncVpiReuse, ReplIncVpiFail)
|
||
# ③'' 会话泛型模板注册(**真注册**,不再只造 opaque 类型占位):按批处理同一
|
||
# 机制把 includes 的泛型类模板(list/dict/Vector/…)注册进会话泛型模板表——
|
||
# Phase1Scan._PreRegisterAllGenericTemplates 复用 includes 源码,先按
|
||
# "class X[" 文本标记预筛(不 parse 全部文件),仅对命中文件用**永久池**
|
||
# 解析并调 GenApi._register_generic_template ⇒ 模板节点与 4 张表
|
||
# (GenericClassNames/Nodes/Sha1s/Files + GenericClassTpParams)都落在
|
||
# PermPool(M4 已定的会话级常驻池;不随片段 reset 回收),与批处理建表
|
||
# 内容/顺序逐字一致。
|
||
# 为什么必须:REPL 从不跑 Phase1/Phase2、也不翻译 includes 源码 ⇒ 会话里
|
||
# 模板表为空 ⇒ 注解里的 `list[...]`/`dict[...]` 泛型下标恒 miss(日志
|
||
# "[GENERIC-DBG] 模板未注册: list"),只能退到"仅类型"opaque 占位;而
|
||
# opaque 只让类型"有名"(可过注入校验、指针签名与宿主兼容),**没有元素
|
||
# 类型/布局/方法**(.get() 无从按正确元素类型分派,字段与方法都不可用)。
|
||
# 真注册后 _resolve_generic_subscript_ty 走既有特化路径:`list[rawptr[AST]]`
|
||
# → 真实特化 struct(含 __data__/__count__/__capacity__ 布局)+ 方法 declare。
|
||
# 失败隔离:本调用在 run 的恢复点保护下(其内部唯一终止出口是"源文件超
|
||
# 上限"的 VLogger.error → longjmp 回 run 的 includes 恢复点,会话继续、
|
||
# 提示符照常出现);函数自身对每个文件 continue,不抛异常。
|
||
# 影响面:只写会话泛型模板表;批处理不调用本函数(其表由 Phase1/Phase2 的
|
||
# 两遍预注册建),表内容与插入顺序不受影响 ⇒ 批处理逐字不变。
|
||
cur_file_saved: str = GenApi.get_current_file()
|
||
n_gen_tpl: int = Phase1Scan._PreRegisterAllGenericTemplates(mb, sr)
|
||
if cur_file_saved is not None:
|
||
GenApi.set_current_file_type(cur_file_saved)
|
||
if n_gen_tpl <= 0:
|
||
stdio.printf("includes 泛型模板: 0 个 —— 注解里的 list[...]/dict[...] 只能按"
|
||
"\"仅类型\"占位解析(元素布局与方法不可用)\n")
|
||
# ③' M4.2:开启"存根布局恢复"——随后的 vpi 注入会按 .stub.ll 校验并登记
|
||
# includes 类的**完整布局**(含父类展平字段与 __vtable__)与虚表方法索引;
|
||
# 布局不可信 ⇒ 该类的 StructEntry 不登记(字段访问明确报错,不静默错位)。
|
||
# 同时清空"泛型类归属表"(跟随后的 vpi 注入逐 .vpi 重新登记;会话级单例,
|
||
# 见 StubMergerStore.RegisterGenericOwner):注解里的泛型下标要靠它取
|
||
# "模板模块 sha1" 拼定义侧特化名造类型占位(M4.3)。
|
||
StubMergerStore.ResetGenericOwners()
|
||
VpiReader.ReplStubLayoutBegin(vpi_dir)
|
||
# ④ M4 类注册:把各模块 .vpi 注入临时模块 ⇒ 其类(含字段表)进入**会话**
|
||
# StructTable(这是后续片段里 `memhub.NewMemPool(...)` 之类跨模块构造/
|
||
# 方法调用的前提:类型解析与字段查找都只认 StructEntry)。
|
||
# 逐模块隔离 + 多遍收敛 + 启动时间预算,失败只告警(见 ReplInitIncludesClasses)。
|
||
ReplInitIncludesClasses(mb, sr, vpi_dir)
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# M2 - 片段执行通路(语句 → IR → llc → clang++ -shared → DLL → 调用 → ctx 回显)
|
||
# M3 - 顶层 def 提升 + 会话函数表 + 定义端签名解析(跨片段函数)
|
||
#
|
||
# 本段(到 _repl_ir_func_sig 结束)是 M3 的全部宿主侧机制:装配期提升顶层 def、
|
||
# 从片段 IR 解析定义端 ABI、注册前校验、DLL 加载后取址写 ctx 槽 + 建表。
|
||
# 片段执行通路本体见 _build_frag_src / _exec_repl_src / ReplExecFragment(其后)。
|
||
#
|
||
# ---- M3 顶层 def 提升 ----
|
||
|
||
# 目标:片段里顶层(顶格)的 `def` 块被**提升**为片段模块里的顶层 `export def`
|
||
# (裸名导出),片段体只留其余语句 ⇒ 该函数进 DLL 导出表,宿主
|
||
# GetProcAddress(裸名) 取到地址、写入 ctx 的函数指针槽,后续片段即可经槽间接
|
||
# 调用(函数代码只在定义片段里一份,调用点不复制、不重编译)。
|
||
#
|
||
# 为什么选**源码行分割**而不是 AST 拆分 + 重新打印:
|
||
# ① 本仓库没有"AST → 源码"的打印器(Gen 侧只有 IR 打印);自写打印器要覆盖
|
||
# 全部节点类型/字面量转义/注解还原/缩进规则,代码量与走样风险远超收益;
|
||
# ② 行分割只做两件可预测的事——把 def 块**原样搬**到模块区、在 def 头上注入
|
||
# ctx 形参并加 `export `;其余字符(docstring/注释/多行表达式/字符串)逐字保留,
|
||
# 不存在"重新打印后语义走样"这一整类风险。
|
||
# 误判面(已知,写进文档):判断只看"是否顶格 def(async 拒绝)"+"签名是否单行",
|
||
# 不做缩进语义分析;签名行内的字符串字面量若含括号会干扰 '(' ')' 配对。
|
||
#
|
||
# 精确规则:
|
||
# ① 提升块 = [可选若干顶格 '@' 装饰器行] + [顶格 def 头行] + 其后所有
|
||
# "缩进 > 0 的行或纯空白行";遇到下一个**顶格非空行**即结束(该行重新判定)。
|
||
# ② def 头行形如 `def <名>[(<泛型参数>)](…)…:`,且 '(' 到行尾的 ':' 必须在
|
||
# **同一行**(多行签名 ⇒ 明确报错,不猜);注入规则:名字后的 '(' 之后插入
|
||
# "__repl_ctx__: bytes, "(形参表为空时插入不带逗号的版本)。
|
||
# ③ 提升行按原文写出(不缩进);其余行进片段体,每行统一加 4 空格(块路径口径)。
|
||
# ④ 拒绝(明确报错 + **整片段不执行**):
|
||
# - 顶格 async def(协程 ABI 与 M3 的签名 token 不相容)
|
||
# - 装饰器行后不是 def 头行 / def 头缺 '(' / ')' 或 ':' 不在同一行
|
||
# - 形参表里已出现 __repl_ctx__(用户自带 ctx 形参)
|
||
# - 函数体(含嵌套)任一行以 raise 开头(EH 出参 ⇒ 间接调用 arity 不匹配)
|
||
# - 同一片段内同名 def 出现两次 / 名字超长 / 数量超 _REPL_LIFT_MAX
|
||
# ⑤ 不提升:缩进 > 0 的 def(走原嵌套函数/闭包路径);`fn`/`func`/`function`/
|
||
# `define` 形式不在本次范围(同样走原路径)。
|
||
# ============================================================
|
||
|
||
# 把一行(可能非 NUL 结尾)截断拷进会话缓冲,供 %s 打印
|
||
def _repl_show_line(p: bytes, n: int) -> str:
|
||
"""行片段 → 会话缓冲里的 NUL 结尾串(截断到 118 字节)"""
|
||
if ReplLineShow is None or p is None:
|
||
return "(?)"
|
||
m: int = n
|
||
if m > 118:
|
||
m = 118
|
||
if m < 0:
|
||
m = 0
|
||
string.memcpy(ReplLineShow, p, sizet(m))
|
||
ReplLineShow[m] = 0
|
||
return ReplLineShow
|
||
|
||
|
||
# 行长度(到 '\n' 或 NUL 为止)
|
||
def _repl_line_len(p: bytes) -> int:
|
||
n: int = 0
|
||
while p[n] != '\n' and p[n] != 0:
|
||
n += 1
|
||
return n
|
||
|
||
|
||
# 前导空白字节数
|
||
def _repl_indent_len(p: bytes, n: int) -> int:
|
||
i: int = 0
|
||
while i < n and (p[i] == ' ' or p[i] == '\t'):
|
||
i += 1
|
||
return i
|
||
|
||
|
||
# 在 [ind, n) 处是否以关键词 kw 开头(且 kw 后是空白)
|
||
def _repl_line_has_kw(p: bytes, n: int, ind: int, kw: str) -> int:
|
||
k: int = int(string.strlen(kw))
|
||
if ind + k + 1 > n:
|
||
return 0
|
||
if string.strncmp(p + ind, kw, sizet(k)) != 0:
|
||
return 0
|
||
c: char = p[ind + k]
|
||
if c != ' ' and c != '\t':
|
||
return 0
|
||
return 1
|
||
|
||
|
||
# 追加字节段到 dst(维护末尾 NUL);返回新长度,-1=容量不足
|
||
def _repl_cat(dst: bytes, cap: int, pos: int, src: bytes, n: int) -> int:
|
||
if dst is None or src is None or pos < 0 or n < 0:
|
||
return -1
|
||
if pos + n + 1 > cap:
|
||
return -1
|
||
string.memcpy(dst + pos, src, sizet(n))
|
||
pos += n
|
||
dst[pos] = 0
|
||
return pos
|
||
|
||
|
||
# 追加 NUL 结尾串到 dst;返回新长度,-1=容量不足
|
||
def _repl_cat_str(dst: bytes, cap: int, pos: int, s: str) -> int:
|
||
if s is None:
|
||
return pos
|
||
return _repl_cat(dst, cap, pos, s, int(string.strlen(s)))
|
||
|
||
|
||
# 提升出的函数名入表(保留前缀检查 + 同片段查重 + 上限);0=成功,1=拒绝(已打印)
|
||
def _repl_lift_add_name(name: str) -> int:
|
||
global ReplLiftNameCount
|
||
if name is None or ReplLiftNames is None:
|
||
return 1
|
||
# 保留前缀:片段入口函数名("__repl_frag_<n>")是 Gen 侧判定"片段入口作用域"的
|
||
# 依据(会话变量的写路径只在该作用域生效),用户不得占用该前缀
|
||
pfx: str = GenTypes.REPL_ENTRY_PREFIX
|
||
pl: int = int(string.strlen(pfx))
|
||
if string.strncmp(name, pfx, sizet(pl)) == 0:
|
||
stdio.printf("M3: 会话函数名不能以保留前缀 '%s' 开头:%s\n", pfx, name)
|
||
return 1
|
||
i: int
|
||
for i in range(ReplLiftNameCount):
|
||
if string.strcmp(ReplLiftNames + i * _REPL_FN_NAME_MAX, name) == 0:
|
||
stdio.printf("M3: 同一片段内重复定义会话函数 '%s'(请只定义一次)\n", name)
|
||
return 1
|
||
if ReplLiftNameCount >= _REPL_LIFT_MAX:
|
||
stdio.printf("M3: 单个片段最多提升 %d 个会话函数(本片段已提升 %d 个)\n",
|
||
_REPL_LIFT_MAX, ReplLiftNameCount)
|
||
return 1
|
||
string.strcpy(ReplLiftNames + ReplLiftNameCount * _REPL_FN_NAME_MAX, name)
|
||
ReplLiftNameCount += 1
|
||
return 0
|
||
|
||
|
||
# 解析并改写一行顶格 def 头 → 追加到 ReplLiftBuf
|
||
#("export def <名>(__repl_ctx__: bytes, <原形参表>) -> <原返回注解>:",其余逐字保留)
|
||
# 名字写入 name_out(NUL 结尾)。返回 0=成功;1=规则拒绝(已打印);2=缓冲不足
|
||
def _repl_lift_def_header(line: bytes, n: int, name_out: bytes, name_cap: int) -> int:
|
||
if n < 5 or name_out is None or ReplLiftBuf is None:
|
||
return 1
|
||
p: int = 3 # 跳过 "def"
|
||
while p < n and (line[p] == ' ' or line[p] == '\t'):
|
||
p += 1
|
||
# 名字(标识符字符)
|
||
ns: int = p
|
||
while p < n:
|
||
c: char = line[p]
|
||
is_id: int = 0
|
||
if (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') or (c >= '0' and c <= '9') \
|
||
or c == '_':
|
||
is_id = 1
|
||
if is_id == 0:
|
||
break
|
||
p += 1
|
||
nlen: int = p - ns
|
||
if nlen <= 0:
|
||
stdio.printf("M3: def 头缺少函数名:%s\n", _repl_show_line(line, n))
|
||
return 1
|
||
if nlen >= name_cap:
|
||
stdio.printf("M3: 会话函数名过长(上限 %d 字节):%s\n",
|
||
name_cap - 1, _repl_show_line(line, n))
|
||
return 1
|
||
string.memcpy(name_out, line + ns, sizet(nlen))
|
||
name_out[nlen] = 0
|
||
# PEP 695 泛型参数 [T]:跳过平衡的方括号(翻译侧把 T 映射为 int)
|
||
if p < n and line[p] == '[':
|
||
d: int = 0
|
||
while p < n:
|
||
if line[p] == '[':
|
||
d += 1
|
||
elif line[p] == ']':
|
||
d -= 1
|
||
if d == 0:
|
||
p += 1
|
||
break
|
||
p += 1
|
||
if d != 0:
|
||
stdio.printf("M3: def 头的泛型参数方括号不闭合:%s\n", _repl_show_line(line, n))
|
||
return 1
|
||
if p >= n or line[p] != '(':
|
||
stdio.printf("M3: def 头在函数名之后缺少 '(':%s\n", _repl_show_line(line, n))
|
||
return 1
|
||
lp: int = p
|
||
# 匹配的 ')'
|
||
d2: int = 0
|
||
rp: int = -1
|
||
q: int = lp
|
||
while q < n:
|
||
c2: char = line[q]
|
||
if c2 == '(':
|
||
d2 += 1
|
||
elif c2 == ')':
|
||
d2 -= 1
|
||
if d2 == 0:
|
||
rp = q
|
||
break
|
||
q += 1
|
||
if rp < 0:
|
||
stdio.printf("M3: def 头的 ')' 缺失(签名必须写在一行内):%s\n", _repl_show_line(line, n))
|
||
return 1
|
||
# ':' 必须在同一行(返回注解也必须在同一行)
|
||
has_colon: int = 0
|
||
q2: int = rp + 1
|
||
while q2 < n:
|
||
if line[q2] == ':':
|
||
has_colon = 1
|
||
break
|
||
q2 += 1
|
||
if has_colon == 0:
|
||
stdio.printf("M3: def 头的 ':' 不在同一行(签名必须写在一行内):%s\n",
|
||
_repl_show_line(line, n))
|
||
return 1
|
||
# 形参表是否为空
|
||
empty: int = 1
|
||
q3: int = lp + 1
|
||
while q3 < rp:
|
||
if line[q3] != ' ' and line[q3] != '\t':
|
||
empty = 0
|
||
break
|
||
q3 += 1
|
||
# 用户自带 __repl_ctx__ 形参 ⇒ 拒绝(避免与注入的首形参重复)
|
||
if empty == 0:
|
||
seg: int = lp + 1
|
||
while seg + 12 <= rp:
|
||
if line[seg] == '_' and string.strncmp(line + seg, GenTypes.REPL_CTX_PARAM,
|
||
sizet(12)) == 0:
|
||
stdio.printf("M3: 会话函数 '%s' 不能自定义 %s 形参"
|
||
"(提升时自动注入为第一个形参)\n", name_out, GenTypes.REPL_CTX_PARAM)
|
||
return 1
|
||
seg += 1
|
||
# 写出:"export " + 原头 [0,lp] + 注入 + 原头 [lp+1,n) + '\n'
|
||
pos: int = int(string.strlen(ReplLiftBuf))
|
||
pos = _repl_cat_str(ReplLiftBuf, _REPL_LIFT_CAP, pos, "export ")
|
||
pos = _repl_cat(ReplLiftBuf, _REPL_LIFT_CAP, pos, line, lp + 1)
|
||
if empty != 0:
|
||
pos = _repl_cat_str(ReplLiftBuf, _REPL_LIFT_CAP, pos, "__repl_ctx__: bytes")
|
||
else:
|
||
pos = _repl_cat_str(ReplLiftBuf, _REPL_LIFT_CAP, pos, "__repl_ctx__: bytes, ")
|
||
pos = _repl_cat(ReplLiftBuf, _REPL_LIFT_CAP, pos, line + lp + 1, n - lp - 1)
|
||
pos = _repl_cat_str(ReplLiftBuf, _REPL_LIFT_CAP, pos, "\n")
|
||
if pos < 0:
|
||
stdio.printf("M3: 提升源码缓冲(%d 字节)不足\n", _REPL_LIFT_CAP)
|
||
return 2
|
||
return 0
|
||
|
||
|
||
# 拆分片段体:提升块 → ReplLiftBuf(模块级 export def,原文不缩进);
|
||
# 其余行 → ReplSrcBuf(自 ReplWrapPos 起,每行 4 空格);并登记被提升的函数名。
|
||
# 返回 0=成功;1=规则拒绝(已打印);2=缓冲不足。成功时 ReplWrapPos 更新为片段体末尾位置。
|
||
def _repl_split_body(body: bytes) -> int:
|
||
global ReplWrapPos
|
||
if body is None or ReplLiftBuf is None or ReplSrcBuf is None:
|
||
return 2
|
||
total: int = int(string.strlen(body))
|
||
i: int = 0
|
||
pos: int = ReplWrapPos
|
||
in_lift: int = 0 # 1=正在提升块内
|
||
need_header: int = 0 # 提升块内已见装饰器行、尚未见 def 头
|
||
in_body: int = 0 # 已越过 def 头(后续缩进行是函数体,逐行查 raise)
|
||
name_buf: bytes = ReplMsgName
|
||
while i < total:
|
||
n: int = _repl_line_len(body + i)
|
||
ind: int = _repl_indent_len(body + i, n)
|
||
blank: int = 0
|
||
if ind >= n:
|
||
blank = 1
|
||
if in_lift != 0:
|
||
if blank != 0:
|
||
# 纯空白行:留在提升块内(函数体里的空行,对缩进无语义)
|
||
lp0: int = int(string.strlen(ReplLiftBuf))
|
||
lp0 = _repl_cat(ReplLiftBuf, _REPL_LIFT_CAP, lp0, body + i, n)
|
||
lp0 = _repl_cat_str(ReplLiftBuf, _REPL_LIFT_CAP, lp0, "\n")
|
||
if lp0 < 0:
|
||
return 2
|
||
i += n + 1
|
||
continue
|
||
if ind > 0:
|
||
if need_header != 0:
|
||
stdio.printf("M3: 装饰器之后必须紧跟 def 头行:%s\n",
|
||
_repl_show_line(body + i, n))
|
||
return 1
|
||
if in_body != 0 and _repl_line_has_kw(body + i, n, ind, "raise") != 0:
|
||
stdio.printf("M3: 会话函数 '%s' 的函数体含 raise:%s\n"
|
||
" M3 不支持跨片段异常传播(raise 会给函数追加 "
|
||
"__eh_msg_out__/__eh_code_out__ 出参,"
|
||
"而经函数指针的间接调用不会补这些实参)——该片段整体不执行\n",
|
||
name_buf, _repl_show_line(body + i, n))
|
||
return 1
|
||
lp1: int = int(string.strlen(ReplLiftBuf))
|
||
lp1 = _repl_cat(ReplLiftBuf, _REPL_LIFT_CAP, lp1, body + i, n)
|
||
lp1 = _repl_cat_str(ReplLiftBuf, _REPL_LIFT_CAP, lp1, "\n")
|
||
if lp1 < 0:
|
||
return 2
|
||
i += n + 1
|
||
continue
|
||
# 顶格非空行 ⇒ 提升块结束,本行按"未进入提升"重新判定(不推进 i)
|
||
in_lift = 0
|
||
need_header = 0
|
||
in_body = 0
|
||
continue
|
||
if blank != 0 or ind > 0:
|
||
# 进片段体(每行 4 空格;与块路径逐字同口径)
|
||
pos = _repl_cat_str(ReplSrcBuf, _REPL_FRAG_SRC_CAP, pos, " ")
|
||
pos = _repl_cat(ReplSrcBuf, _REPL_FRAG_SRC_CAP, pos, body + i, n)
|
||
pos = _repl_cat_str(ReplSrcBuf, _REPL_FRAG_SRC_CAP, pos, "\n")
|
||
if pos < 0:
|
||
return 2
|
||
i += n + 1
|
||
continue
|
||
# 顶格非空行
|
||
if body[i] == '@':
|
||
in_lift = 1
|
||
need_header = 1
|
||
in_body = 0
|
||
lp2: int = int(string.strlen(ReplLiftBuf))
|
||
lp2 = _repl_cat(ReplLiftBuf, _REPL_LIFT_CAP, lp2, body + i, n)
|
||
lp2 = _repl_cat_str(ReplLiftBuf, _REPL_LIFT_CAP, lp2, "\n")
|
||
if lp2 < 0:
|
||
return 2
|
||
i += n + 1
|
||
continue
|
||
if _repl_line_has_kw(body + i, n, 0, "def") != 0:
|
||
rc_h: int = _repl_lift_def_header(body + i, n, name_buf, _REPL_FN_NAME_MAX)
|
||
if rc_h != 0:
|
||
return rc_h
|
||
if _repl_lift_add_name(name_buf) != 0:
|
||
return 1
|
||
in_lift = 1
|
||
need_header = 0
|
||
in_body = 1
|
||
i += n + 1
|
||
continue
|
||
if _repl_line_has_kw(body + i, n, 0, "async") != 0 \
|
||
and _repl_line_has_kw(body + i, n, 6, "def") != 0:
|
||
stdio.printf("M3: 不支持 async def 会话函数(协程 ABI 与 M3 签名 token "
|
||
"不相容,请改写成普通 def):%s\n", _repl_show_line(body + i, n))
|
||
return 1
|
||
pos = _repl_cat_str(ReplSrcBuf, _REPL_FRAG_SRC_CAP, pos, " ")
|
||
pos = _repl_cat(ReplSrcBuf, _REPL_FRAG_SRC_CAP, pos, body + i, n)
|
||
pos = _repl_cat_str(ReplSrcBuf, _REPL_FRAG_SRC_CAP, pos, "\n")
|
||
if pos < 0:
|
||
return 2
|
||
i += n + 1
|
||
if need_header != 0:
|
||
stdio.printf("M3: 装饰器之后缺少 def 头行(片段末尾)\n")
|
||
return 1
|
||
ReplWrapPos = pos
|
||
return 0
|
||
|
||
|
||
# 多行块的包裹装配(提升 + 包裹):返回 0=成功;1=规则拒绝(已打印);2=缓冲不足
|
||
def _repl_build_frag_src_multi(name: str, body: bytes) -> int:
|
||
global ReplWrapPos
|
||
if name is None or body is None:
|
||
return 2
|
||
if ReplLiftBuf is None or ReplLiftNames is None or ReplSrcBuf is None:
|
||
return 2
|
||
# 提升缓冲与名表水位已由调用方 _build_frag_src 每片段清零(单行路径也必须清零,
|
||
# 故清零点在调用方而非此处);这里只负责装配
|
||
# 片段函数头(与单行模板同一形态);片段体随后逐行 4 空格
|
||
viperlib.snprintf(ReplSrcBuf, _REPL_FRAG_SRC_CAP,
|
||
"export def %s(%s: bytes) -> int:\n",
|
||
name, GenTypes.REPL_CTX_PARAM)
|
||
ReplWrapPos = int(string.strlen(ReplSrcBuf))
|
||
rc: int = _repl_split_body(body)
|
||
if rc != 0:
|
||
return rc
|
||
pos: int = ReplWrapPos
|
||
pos = _repl_cat_str(ReplSrcBuf, _REPL_FRAG_SRC_CAP, pos, " return 0\n")
|
||
if pos < 0:
|
||
return 2
|
||
# 提升出的模块级 def 追加在片段入口函数之后(同为模块级,顺序无关)
|
||
lift_len: int = int(string.strlen(ReplLiftBuf))
|
||
if lift_len > 0:
|
||
pos = _repl_cat(ReplSrcBuf, _REPL_FRAG_SRC_CAP, pos, ReplLiftBuf, lift_len)
|
||
if pos < 0:
|
||
return 2
|
||
ReplWrapPos = pos
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# M3 - 会话函数表:宿主持有(写表 + 写 ctx 指针槽),Gen 只读
|
||
#
|
||
# 时序(见 ReplExecFragment):
|
||
# ① 定义片段:提升 → 翻译 → **注册前校验**(本节的 _repl_precheck_session_funcs,
|
||
# 在 llc/加载之前 ⇒ 失败则整片段不执行)→ 加载 DLL → GetProcAddress(裸名)
|
||
# → 写 ctx 槽 → 追加/更新表条目;
|
||
# ② 调用片段:翻译期命中表 → 生成"从槽 load 地址 + 按表内签名间接调用"。
|
||
# 一致性:表条目与 ctx 槽只在"定义片段 DLL 加载成功且符号取址成功"后一起写;
|
||
# 失败路径由 ReplSavedFuncCount 水位回滚(与 ReplSavedVarCount 同思路)。
|
||
# ============================================================
|
||
|
||
# 会话函数表按名查找(宿主侧;与 Gen 侧 repl_find_func_entry 同口径)
|
||
def _repl_func_entry_find(name: str) -> rawptr[GenTypes.ReplFuncEntry]:
|
||
"""1=命中条目;None=未登记/表未建"""
|
||
if ReplCtxObj is None or ReplCtxObj.FuncTable is None or name is None:
|
||
return None
|
||
es: sizet = GenTypes.ReplFuncEntry.__sizeof__()
|
||
i: int
|
||
for i in range(ReplCtxObj.FuncCount):
|
||
e: rawptr[GenTypes.ReplFuncEntry] = rawptr[GenTypes.ReplFuncEntry](uint64(ReplCtxObj.FuncTable) + uint64(i) * uint64(es))
|
||
if e.Name is not None and string.strcmp(e.Name, name) == 0:
|
||
return e
|
||
return None
|
||
|
||
|
||
# 提升出的第 idx 个函数名(NUL 结尾);越界返回 None
|
||
def _repl_lift_name(idx: int) -> str:
|
||
if ReplLiftNames is None or idx < 0 or idx >= ReplLiftNameCount:
|
||
return None
|
||
return ReplLiftNames + idx * _REPL_FN_NAME_MAX
|
||
|
||
|
||
# 深拷贝到 PermPool(会话表条目名等跨片段存活的数据)
|
||
def _repl_perm_dup(s: str) -> str:
|
||
if s is None or _perm_pool is None:
|
||
return None
|
||
n: sizet = string.strlen(s)
|
||
b: str = _perm_pool.alloc(n + 1)
|
||
if b is None:
|
||
return None
|
||
string.strcpy(b, s)
|
||
return b
|
||
|
||
|
||
# 签名解析结果 → 人类可读串("((int, str) -> int)"式;写进调用方缓冲)
|
||
def _repl_sig_to_text(tag: int, bits: int, pc: int) -> str:
|
||
"""把 ReplSigResult + ReplSigTags/Bits 渲染成 "(P1, P2) -> R"(PermPool 缓冲)"""
|
||
if ReplSigText is None:
|
||
return "(?)"
|
||
pos: int = _repl_cat_str(ReplSigText, _REPL_SIG_TEXT_CAP, 0, "(")
|
||
i: int = 0
|
||
while i < pc:
|
||
if i > 0:
|
||
pos = _repl_cat_str(ReplSigText, _REPL_SIG_TEXT_CAP, pos, ", ")
|
||
pos = _repl_cat_str(ReplSigText, _REPL_SIG_TEXT_CAP, pos,
|
||
_repl_tag_text(int(ReplSigTags[i]), int(ReplSigBits[i])))
|
||
i += 1
|
||
pos = _repl_cat_str(ReplSigText, _REPL_SIG_TEXT_CAP, pos, ") -> ")
|
||
pos = _repl_cat_str(ReplSigText, _REPL_SIG_TEXT_CAP, pos, _repl_tag_text(tag, bits))
|
||
if pos < 0:
|
||
return "(?)"
|
||
return ReplSigText
|
||
|
||
|
||
# 注册前校验(在 llc/加载之前):逐个提升函数解析 IR 签名,判
|
||
# ① EH 出参 ② 类型不受支持 ③ 首形参不是 ctx ④ 形参过多 ⑤ 未找到 define
|
||
# ⑥ 重定义签名不一致 ⑦ 表容量不足。任一项失败 ⇒ 打印原因并返回 1(整片段不执行)。
|
||
def _repl_precheck_session_funcs() -> int:
|
||
if ReplLiftNameCount <= 0:
|
||
return 0
|
||
if ReplCtxObj is None or ReplCtxObj.FuncTable is None or ReplSigResult is None:
|
||
stdio.printf("M3: 会话函数表/签名缓冲未初始化(会话初始化失败)\n")
|
||
return 1
|
||
new_cnt: int = 0
|
||
idx: int
|
||
for idx in range(ReplLiftNameCount):
|
||
nm: str = _repl_lift_name(idx)
|
||
if nm is None:
|
||
return 1
|
||
rc: int = _repl_ir_func_sig(nm, ReplSigResult)
|
||
if rc == 1:
|
||
stdio.printf("M3: 片段 IR 里找不到 '%s' 的 define 行(提升未生效?)\n", nm)
|
||
return 1
|
||
if rc == 2:
|
||
stdio.printf("M3: 会话函数 '%s' 的签名含不受支持的类型 '%s'\n"
|
||
" M3 支持 void / i1 / i8 / i16 / i32 / i64 / float / double / "
|
||
"i8*(str、bytes、rawptr);结构体值/数组/多级指针请改用指针参数"
|
||
"或等 M4\n",
|
||
nm, ReplSigResult.BadType)
|
||
return 1
|
||
if rc == 3:
|
||
stdio.printf("M3: 会话函数 '%s' 的签名含异常出参(%%__eh_msg_out__/"
|
||
"%%__eh_code_out__)——其函数体含 raise\n"
|
||
" M3 不支持跨片段异常传播:经函数指针的间接调用不会补这两个实参"
|
||
"(arity 不匹配);该片段整体不执行\n", nm)
|
||
return 1
|
||
if rc == 4:
|
||
stdio.printf("M3: 会话函数 '%s' 的 define 首形参不是 %s(提升模板与调用约定"
|
||
"不一致)\n", nm, GenTypes.REPL_CTX_PARAM)
|
||
return 1
|
||
if rc == 5:
|
||
stdio.printf("M3: 会话函数 '%s' 的形参超过 %d 个\n",
|
||
nm, GenTypes.REPL_FUNC_PARAM_MAX)
|
||
return 1
|
||
if rc != 0:
|
||
stdio.printf("M3: 会话函数 '%s' 的签名解析失败(rc=%d)\n", nm, rc)
|
||
return 1
|
||
old: rawptr[GenTypes.ReplFuncEntry] = _repl_func_entry_find(nm)
|
||
if old is None:
|
||
new_cnt += 1
|
||
continue
|
||
# 重定义:只允许**同签名**覆盖(换签名会让先前片段里已编好的调用点按旧 ABI
|
||
# 调新代码 ⇒ 静默 UB)。签名一致时只更新槽内地址(段号随之刷新)。
|
||
sig_same: int = 1
|
||
if old.RetTag != ReplSigResult.RetTag or old.RetBits != ReplSigResult.RetBits \
|
||
or old.ParamCount != ReplSigResult.ParamCount:
|
||
sig_same = 0
|
||
if sig_same != 0:
|
||
pi: int = 0
|
||
while pi < old.ParamCount:
|
||
if int(old.ParamTags[pi]) != int(ReplSigTags[pi]) \
|
||
or int(old.ParamBits[pi]) != int(ReplSigBits[pi]):
|
||
sig_same = 0
|
||
break
|
||
pi += 1
|
||
if sig_same == 0:
|
||
stdio.printf("M3: 会话函数 '%s' 重定义签名不一致(旧 %s,新 %s)\n"
|
||
" M3 允许同签名覆盖(只换地址);换签名会让先前片段里已编好的"
|
||
"调用点按旧 ABI 调新代码 —— 该片段整体不执行,请换名字\n",
|
||
nm,
|
||
_repl_entry_sig_text(old),
|
||
_repl_sig_to_text(ReplSigResult.RetTag, ReplSigResult.RetBits,
|
||
ReplSigResult.ParamCount))
|
||
return 1
|
||
if ReplCtxObj.FuncCount + new_cnt > ReplCtxObj.FuncCap:
|
||
stdio.printf("M3: 会话函数已达上限 %d 个(ctx 函数指针槽用尽),本次新增 %d 个;"
|
||
"请重启会话或减少会话函数\n",
|
||
ReplCtxObj.FuncCap, new_cnt)
|
||
return 1
|
||
return 0
|
||
|
||
|
||
# 已有条目的签名渲染(重定义冲突诊断用;写进 ReplSigText2)
|
||
def _repl_entry_sig_text(e: rawptr[GenTypes.ReplFuncEntry]) -> str:
|
||
if e is None or ReplSigText2 is None:
|
||
return "(?)"
|
||
pos: int = _repl_cat_str(ReplSigText2, _REPL_SIG_TEXT_CAP, 0, "(")
|
||
i: int = 0
|
||
while i < e.ParamCount:
|
||
if i > 0:
|
||
pos = _repl_cat_str(ReplSigText2, _REPL_SIG_TEXT_CAP, pos, ", ")
|
||
pos = _repl_cat_str(ReplSigText2, _REPL_SIG_TEXT_CAP, pos,
|
||
_repl_tag_text(int(e.ParamTags[i]), int(e.ParamBits[i])))
|
||
i += 1
|
||
pos = _repl_cat_str(ReplSigText2, _REPL_SIG_TEXT_CAP, pos, ") -> ")
|
||
pos = _repl_cat_str(ReplSigText2, _REPL_SIG_TEXT_CAP, pos,
|
||
_repl_tag_text(e.RetTag, e.RetBits))
|
||
if pos < 0:
|
||
return "(?)"
|
||
return ReplSigText2
|
||
|
||
|
||
# 注册(DLL 加载成功后调用):GetProcAddress(裸名) → 写 ctx 函数指针槽 → 追加/更新表
|
||
# 返回 0=成功;1=失败(已打印;调用方按 ReplSavedFuncCount 回滚表水位后放弃本片段)
|
||
def _repl_register_session_funcs(h_lib: win32base.HANDLE, frag_no: int) -> int:
|
||
if ReplLiftNameCount <= 0:
|
||
return 0
|
||
if ReplCtxObj is None or ReplCtxObj.FuncTable is None or ReplCtxBuf is None:
|
||
stdio.printf("M3: 会话函数表/ctx 未初始化\n")
|
||
return 1
|
||
idx: int
|
||
for idx in range(ReplLiftNameCount):
|
||
nm: str = _repl_lift_name(idx)
|
||
if nm is None:
|
||
return 1
|
||
# 导出符号名 = 裸名:定义端 `export def` ⇒ has_export=1 不加 SHA1 前缀
|
||
# (FuncGenDef.vp:482),且 REPL 片段 ModuleSha1=None 使 _mangle_name 原样返回
|
||
# (FuncGenBase.vp:264);-shared 未用 DEF 文件 ⇒ 全部符号自动进导出表(M1 实测)
|
||
addr: VOIDPTR = win32base.GetProcAddress(h_lib, nm)
|
||
if addr is None:
|
||
stdio.printf("M3: 会话函数 '%s' 未出现在片段 DLL 的导出表"
|
||
"(GetProcAddress 返回空;本片段不执行)\n", nm)
|
||
return 1
|
||
ent: rawptr[GenTypes.ReplFuncEntry] = _repl_func_entry_find(nm)
|
||
if ent is None:
|
||
# 新建条目(签名已在校验阶段通过;此处重新解析以取回 scratch 里的 token)
|
||
rc: int = _repl_ir_func_sig(nm, ReplSigResult)
|
||
if rc != 0:
|
||
stdio.printf("M3: 会话函数 '%s' 的签名重新解析失败(rc=%d)\n", nm, rc)
|
||
return 1
|
||
j: int = ReplCtxObj.FuncCount
|
||
es: sizet = GenTypes.ReplFuncEntry.__sizeof__()
|
||
ent = rawptr[GenTypes.ReplFuncEntry](uint64(ReplCtxObj.FuncTable) + uint64(j) * uint64(es))
|
||
nm_c: str = _repl_perm_dup(nm)
|
||
nm_s: str = _repl_perm_dup(nm)
|
||
tags_b: bytes = _perm_pool.alloc(GenTypes.REPL_FUNC_PARAM_MAX)
|
||
bits_b: bytes = _perm_pool.alloc(GenTypes.REPL_FUNC_PARAM_MAX)
|
||
if nm_c is None or nm_s is None or tags_b is None or bits_b is None:
|
||
stdio.printf("M3: 会话函数条目内存分配失败('%s')\n", nm)
|
||
return 1
|
||
string.memset(tags_b, 0, GenTypes.REPL_FUNC_PARAM_MAX)
|
||
string.memset(bits_b, 0, GenTypes.REPL_FUNC_PARAM_MAX)
|
||
string.memcpy(tags_b, ReplSigTags, sizet(ReplSigResult.ParamCount))
|
||
string.memcpy(bits_b, ReplSigBits, sizet(ReplSigResult.ParamCount))
|
||
ent.Name = nm_c
|
||
ent.SymName = nm_s
|
||
ent.FragNo = frag_no
|
||
ent.SlotOff = GenTypes.REPL_FUNC_BASE + j * GenTypes.REPL_FUNC_SLOT_SIZE
|
||
ent.RetTag = ReplSigResult.RetTag
|
||
ent.RetBits = ReplSigResult.RetBits
|
||
ent.ParamCount = ReplSigResult.ParamCount
|
||
ent.ParamTags = tags_b
|
||
ent.ParamBits = bits_b
|
||
ReplCtxObj.FuncCount = j + 1
|
||
else:
|
||
# 重定义(同签名):只换地址与段号,槽偏移不变
|
||
ent.FragNo = frag_no
|
||
# 写 ctx 槽(低 8 字节=地址,高 8 字节保留 0)
|
||
sv: rawptr[ReplFuncSlotView] = rawptr[ReplFuncSlotView](uint64(ReplCtxBuf) + uint64(ent.SlotOff))
|
||
sv.Addr = addr
|
||
sv.Reserved = None
|
||
stdio.printf("会话函数 %s: 导出符号 %s → ctx 槽 +%d(定义片段 %d,签名 %s)\n",
|
||
nm, nm, ent.SlotOff, frag_no,
|
||
_repl_entry_sig_text(ent))
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# M3 - 会话函数签名解析(从片段 IR 的 define 行取定义端 ABI)
|
||
#
|
||
# 出处:定义片段 translate 后 dump 的 IR 里
|
||
# `define <ret> @<名>(i8* %__repl_ctx__[, <ty> %<p>]*)`
|
||
# 是定义端 ABI 的唯一真相(注解 → LLVM 类型由 codegen 解析)。宿主据此填
|
||
# ReplFuncEntry 的签名 token;调用片段用同一 token 重建同型签名 ⇒ 两侧一致。
|
||
# 只接受可往返重建的类型:i1/i8/i16/i32/i64、float、double、i8*(+ 返回 void)。
|
||
# ============================================================
|
||
|
||
# 把 [src, src+n) 截断拷进会话缓冲并 NUL 结尾(供 strcmp/%s):缓冲区 1
|
||
def _repl_span_str(src: bytes, n: int) -> str:
|
||
if ReplTySpan is None or src is None:
|
||
return None
|
||
m: int = n
|
||
if m > _REPL_FN_TY_CAP - 1:
|
||
m = _REPL_FN_TY_CAP - 1
|
||
if m < 0:
|
||
m = 0
|
||
string.memcpy(ReplTySpan, src, sizet(m))
|
||
ReplTySpan[m] = 0
|
||
return ReplTySpan
|
||
|
||
|
||
# LLVM 类型串 → 打包 token(tag*1000 + bits);0=不支持
|
||
def _repl_llvm_ty_token(ty: str) -> int:
|
||
if ty is None:
|
||
return 0
|
||
if string.strcmp(ty, "i1") == 0:
|
||
return GenTypes.REPL_TY_INT * 1000 + 1
|
||
if string.strcmp(ty, "i8") == 0:
|
||
return GenTypes.REPL_TY_INT * 1000 + 8
|
||
if string.strcmp(ty, "i16") == 0:
|
||
return GenTypes.REPL_TY_INT * 1000 + 16
|
||
if string.strcmp(ty, "i32") == 0:
|
||
return GenTypes.REPL_TY_INT * 1000 + 32
|
||
if string.strcmp(ty, "i64") == 0:
|
||
return GenTypes.REPL_TY_INT * 1000 + 64
|
||
if string.strcmp(ty, "float") == 0:
|
||
return GenTypes.REPL_TY_FLOAT * 1000 + 32
|
||
if string.strcmp(ty, "double") == 0:
|
||
return GenTypes.REPL_TY_FLOAT * 1000 + 64
|
||
if string.strcmp(ty, "i8*") == 0:
|
||
return GenTypes.REPL_TY_STR * 1000 + 8
|
||
return 0
|
||
|
||
|
||
# token → 显示名(仅用于日志/诊断)
|
||
def _repl_tag_text(tag: int, bits: int) -> str:
|
||
if tag == GenTypes.REPL_TY_VOID:
|
||
return "void"
|
||
if tag == GenTypes.REPL_TY_INT:
|
||
if bits == 1:
|
||
return "bool"
|
||
if bits == 8:
|
||
return "char"
|
||
if bits == 16:
|
||
return "int16"
|
||
if bits == 32:
|
||
return "int"
|
||
if bits == 64:
|
||
return "int64"
|
||
return "int?"
|
||
if tag == GenTypes.REPL_TY_FLOAT:
|
||
if bits == 32:
|
||
return "float"
|
||
if bits == 64:
|
||
return "double"
|
||
return "float?"
|
||
if tag == GenTypes.REPL_TY_STR:
|
||
return "str"
|
||
return "?"
|
||
|
||
|
||
# 在 IR 文本里定位符号 name 的 define 行:命中返回 '(' 的**字节偏移**
|
||
#(并把 "define " 的偏移写进 ReplIrDefOff),未命中返回 -1。
|
||
# 全程用整数偏移 + 逐字节扫描(不借 strchr/strstr 的返回指针,避免 str/bytes
|
||
# 指针类型分叉带来的编译期不确定性);只做正向扫描。
|
||
def _repl_ir_find_define_at(ir: bytes, name: str) -> int:
|
||
global ReplIrDefOff
|
||
ReplIrDefOff = -1
|
||
if ir is None or name is None:
|
||
return -1
|
||
kw: str = "define "
|
||
nl: int = int(string.strlen(name))
|
||
if nl <= 0:
|
||
return -1
|
||
i: int = 0
|
||
while ir[i] != 0:
|
||
hit: int = 1
|
||
k: int = 0
|
||
while k < 7:
|
||
if ir[i + k] == 0 or ir[i + k] != kw[k]:
|
||
hit = 0
|
||
break
|
||
k += 1
|
||
if hit == 0:
|
||
i += 1
|
||
continue
|
||
# i+7 起是返回类型,直到 '@';要求 '@' 与 '(' 在同一行内(同 define 行)
|
||
j: int = i + 7
|
||
while ir[j] != 0 and ir[j] != '@' and ir[j] != '\n':
|
||
j += 1
|
||
if ir[j] != '@':
|
||
i += 1
|
||
continue
|
||
n0: int = j + 1
|
||
n1: int = n0
|
||
while ir[n1] != 0 and ir[n1] != '(' and ir[n1] != '\n':
|
||
n1 += 1
|
||
if ir[n1] != '(' or n1 - n0 != nl:
|
||
i += 1
|
||
continue
|
||
if string.strncmp(ir + n0, name, nl) == 0:
|
||
ReplIrDefOff = i
|
||
return n1
|
||
i += 1
|
||
return -1
|
||
|
||
|
||
# 解析会话函数的 define 行签名 → ReplSigResult + ReplSigTags/ReplSigBits
|
||
# 返回 0=成功;1=未找到 define;2=类型不受支持;3=含 EH 出参;4=首形参不是 ctx;
|
||
# 5=形参过多(> REPL_FUNC_PARAM_MAX)
|
||
def _repl_ir_func_sig(name: str, out: rawptr[ReplSigOut]) -> int:
|
||
if name is None or out is None or ReplIrBuf is None:
|
||
return 1
|
||
out.RetTag = GenTypes.REPL_TY_VOID
|
||
out.RetBits = 0
|
||
out.ParamCount = 0
|
||
out.EHTag = 0
|
||
out.Bad = 0
|
||
out.BadType = None
|
||
ir: bytes = ReplIrBuf
|
||
lp: int = _repl_ir_find_define_at(ir, name)
|
||
if lp < 0 or ReplIrDefOff < 0:
|
||
return 1
|
||
# ① 返回类型:'define ' 之后到 '@'(去首尾空白)
|
||
at: int = ReplIrDefOff + 7
|
||
while ir[at] != 0 and ir[at] != '@':
|
||
at += 1
|
||
if ir[at] != '@' or at >= lp:
|
||
return 1
|
||
ret_f: int = ReplIrDefOff + 7
|
||
ret_e: int = at
|
||
while ret_f < ret_e and (ir[ret_f] == ' ' or ir[ret_f] == '\t'):
|
||
ret_f += 1
|
||
while ret_e > ret_f and (ir[ret_e - 1] == ' ' or ir[ret_e - 1] == '\t'):
|
||
ret_e -= 1
|
||
ret_str: str = _repl_span_str(ir + ret_f, ret_e - ret_f)
|
||
if ret_str is None:
|
||
return 2
|
||
if string.strcmp(ret_str, "void") == 0:
|
||
out.RetTag = GenTypes.REPL_TY_VOID
|
||
out.RetBits = 0
|
||
else:
|
||
rt: int = _repl_llvm_ty_token(ret_str)
|
||
if rt == 0:
|
||
out.Bad = 1
|
||
out.BadType = ret_str
|
||
return 2
|
||
out.RetTag = rt // 1000
|
||
out.RetBits = rt % 1000
|
||
# ② 形参表:'(' 到匹配的 ')'(按括号深度配平;IR 类型里可能有 {..} / [..])
|
||
d: int = 0
|
||
rp: int = lp
|
||
while ir[rp] != 0:
|
||
if ir[rp] == '(':
|
||
d += 1
|
||
elif ir[rp] == ')':
|
||
d -= 1
|
||
if d == 0:
|
||
break
|
||
rp += 1
|
||
if ir[rp] != ')':
|
||
return 1
|
||
# ③ 逐个形参段(按 ',' 切;段内形态 "类型 [%名字]")
|
||
# 类型 span = [ps+ty_f, ps+ty_n);名字 span = [ps+nm_f, ps+nm_e)(相对段起点)
|
||
ps: int = lp + 1
|
||
cur: int = ps
|
||
pi: int = 0
|
||
while 1:
|
||
is_sep: int = 0
|
||
if cur >= rp:
|
||
is_sep = 1
|
||
elif ir[cur] == ',':
|
||
is_sep = 1
|
||
if is_sep == 0:
|
||
cur += 1
|
||
continue
|
||
seg_n: int = cur - ps
|
||
pc: int = -1
|
||
k: int = 0
|
||
while k < seg_n:
|
||
if ir[ps + k] == '%':
|
||
pc = k
|
||
k += 1
|
||
ty_n: int = seg_n
|
||
if pc >= 0:
|
||
ty_n = pc
|
||
ty_f: int = 0
|
||
while ty_f < ty_n and (ir[ps + ty_f] == ' ' or ir[ps + ty_f] == '\t'):
|
||
ty_f += 1
|
||
while ty_n > ty_f and (ir[ps + ty_n - 1] == ' ' or ir[ps + ty_n - 1] == '\t'):
|
||
ty_n -= 1
|
||
nm_f: int = -1
|
||
nm_e: int = -1
|
||
if pc >= 0:
|
||
nm_f = pc + 1
|
||
nm_e = seg_n
|
||
while nm_f < nm_e and (ir[ps + nm_f] == ' ' or ir[ps + nm_f] == '\t'):
|
||
nm_f += 1
|
||
while nm_e > nm_f and (ir[ps + nm_e - 1] == ' ' or ir[ps + nm_e - 1] == '\t'):
|
||
nm_e -= 1
|
||
if nm_e <= nm_f:
|
||
nm_f = -1
|
||
if pi == 0:
|
||
# 首形参固定为 ctx:类型 i8*、名字逐字等于 __repl_ctx__
|
||
# 名字 span [nm_f, nm_e) 是**去掉前导 '%'** 的(nm_f = pc + 1,见上"名字 span"
|
||
# 口径),故期望串就是 ctx 形参名本身。此处**不能**再拼 '%':拼出来的
|
||
# "%__repl_ctx__" 长 13,而 span 长 12 ⇒ 长度判据恒不等,任何合法提升函数
|
||
# 都被误判 rc=4(`会话函数 'f' 的 define 首形参不是 __repl_ctx__`),
|
||
# M3 定义片段必在注册前校验处失败(提升装配其实是对的)。
|
||
ty_str0: str = _repl_span_str(ir + ps + ty_f, ty_n - ty_f)
|
||
if ty_str0 is None or string.strcmp(ty_str0, "i8*") != 0 or nm_f < 0:
|
||
return 4
|
||
c0: int = _repl_cat_str(ReplTySpan3, _REPL_FN_TY_CAP, 0, GenTypes.REPL_CTX_PARAM)
|
||
if c0 < 0:
|
||
return 4
|
||
if nm_e - nm_f != int(string.strlen(ReplTySpan3)) \
|
||
or string.strncmp(ir + ps + nm_f, ReplTySpan3, nm_e - nm_f) != 0:
|
||
return 4
|
||
else:
|
||
# EH 出参(raise 语义):define 形参名 %__eh_msg_out__/%__eh_code_out__
|
||
if nm_f >= 0:
|
||
c1: int = _repl_cat_str(ReplTySpan3, _REPL_FN_TY_CAP, 0, "%")
|
||
c1 = _repl_cat(ReplTySpan3, _REPL_FN_TY_CAP, c1, ir + ps + nm_f, nm_e - nm_f)
|
||
if c1 >= 0:
|
||
if string.strcmp(ReplTySpan3, "%__eh_msg_out__") == 0 \
|
||
or string.strcmp(ReplTySpan3, "%__eh_code_out__") == 0:
|
||
out.EHTag = 1
|
||
return 3
|
||
ty_str: str = _repl_span_str(ir + ps + ty_f, ty_n - ty_f)
|
||
if ty_str is None:
|
||
return 2
|
||
if out.ParamCount >= GenTypes.REPL_FUNC_PARAM_MAX:
|
||
return 5
|
||
tk: int = _repl_llvm_ty_token(ty_str)
|
||
if tk == 0:
|
||
out.Bad = 1
|
||
out.BadType = _repl_span_str(ir + ps + ty_f, ty_n - ty_f)
|
||
return 2
|
||
ReplSigTags[out.ParamCount] = tk // 1000
|
||
ReplSigBits[out.ParamCount] = tk % 1000
|
||
out.ParamCount = out.ParamCount + 1
|
||
pi += 1
|
||
if cur >= rp:
|
||
break
|
||
ps = cur + 1
|
||
cur = ps
|
||
return 0
|
||
|
||
|
||
|
||
# ============================================================
|
||
# 装配片段包裹源码到 ReplSrcBuf(片段体恒为**函数体**)。
|
||
# 单行(体不含 '\n'):走既有 snprintf 模板,**逐字不变**;
|
||
# 多行(块):M3 起走 _repl_build_frag_src_multi —— 顶层 def 提升为模块级
|
||
# export def(原文不缩进),其余行逐行加 4 空格进片段体,随后片段入口函数,
|
||
# 最后追加提升出的模块级 def。
|
||
# 为什么逐行处理:片段体缩进由模板给出,原来单行是"整行前加 4 空格";多行若只给
|
||
# 第一行加 4 空格,第 2 行起顶格 ⇒ IndentationError。故每行独立加 4 空格,
|
||
# 而每行**原有的相对缩进原样保留**(块内嵌套层级因此不变)。
|
||
# 为什么单行/多行分两条:单行路径要求与改动前逐字一致(同一 snprintf、同一模板、
|
||
# 同一输出),多行路径才需要逐行装配与提升;两者互不干扰。
|
||
# 返回 0=已写入 ReplSrcBuf;1=提升规则拒绝(原因已打印);2=缓冲不足。
|
||
def _build_frag_src(name: str, body: str) -> int:
|
||
"""装配包裹源码:单行=既有模板(逐字不变);多行=提升 + 逐行 4 空格"""
|
||
global ReplLiftNameCount
|
||
if name is None or body is None:
|
||
return 2
|
||
# 每片段装配前清零上一片段留下的提升状态(名表水位 + 提升缓冲):
|
||
# **单行路径不提升任何函数**,若不清零,上一片段提升出的名字会被本片段的
|
||
# 注册前校验(_repl_precheck_session_funcs 按 ReplLiftNameCount 逐名遍历)当成
|
||
# "本片段提升的函数"去本片段 IR 里找 define ⇒ 必然找不到 ⇒ 该片段被误拒。
|
||
# 典型症状:定义片段成功后的**第一个调用片段**(如 `f(41)`)报
|
||
# `M3: 片段 IR 里找不到 'f' 的 define 行(提升未生效?)`。
|
||
# 多行路径随后按需追加提升内容(_repl_split_body 写、_repl_build_frag_src_multi 读)。
|
||
ReplLiftNameCount = 0
|
||
ReplLiftBuf[0] = 0
|
||
ReplLiftNames[0] = 0
|
||
if string.strchr(body, '\n') is None:
|
||
# 单行:既有模板(ctx 形参 bytes ⇒ i8*;export ⇒ 不加 SHA1 前缀)
|
||
viperlib.snprintf(ReplSrcBuf, _REPL_FRAG_SRC_CAP,
|
||
"export def %s(%s: bytes) -> int:\n %s\n return 0\n",
|
||
name, GenTypes.REPL_CTX_PARAM, body)
|
||
return 0
|
||
# 多行:提升顶层 def + 逐行装配(M3;见 _repl_split_body 的规则)
|
||
return _repl_build_frag_src_multi(name, body)
|
||
|
||
|
||
# 执行一份片段源码(单行与整块同一入口):先按**未包裹**源码解析一次(诊断行号
|
||
# 对用户可读、且与单行路径同款消息),再交给 ReplExecFragment 包裹/翻译/执行。
|
||
# 行为与改动前 run 里的单行逻辑逐语句一致(moved,非新增)。
|
||
def _exec_repl_src(mb: memhub.MemBuddy*, src: str) -> int:
|
||
"""解析 + 执行一份片段源码;0=已处理(含各类诊断已打印)"""
|
||
if src is None:
|
||
return 1
|
||
tree: ast.AST* = ast.parse(src, mb)
|
||
if tree is None:
|
||
el: int = ast.get_parse_err_line()
|
||
ec: int = ast.get_parse_err_col()
|
||
em: str = ast.get_parse_err_msg()
|
||
if em is None:
|
||
em = "(未知)"
|
||
stdio.printf("ParseError: 第 %d 行第 %d 列: %s\n", el, ec, em)
|
||
return 0
|
||
if _count_top_stmts(tree) <= 0:
|
||
stdio.printf("已解析: 0 个顶层语句(跳过)\n")
|
||
return 0
|
||
# M2/M3 执行通路:整块/整行作为**一个**片段函数体翻译执行;失败只打印原因
|
||
# (或经 longjmp 回到恢复点),不终止进程
|
||
rc_frag: int = ReplExecFragment(mb, src)
|
||
if rc_frag != 0:
|
||
# 非 longjmp 的失败(提升规则拒绝、签名校验失败、llc/链接失败、
|
||
# LoadLibraryA/GetProcAddress 为空、导出签名不符等):片段内部已逐条打印
|
||
# 原因(ReplExecFragment 的各失败分支都有 printf),此处只回滚会话表水位
|
||
# (变量表 Count + 函数表 FuncCount),保证"失败后表与 ctx 槽一致"。
|
||
if ReplCtxObj is not None:
|
||
ReplCtxObj.Count = ReplSavedVarCount
|
||
ReplCtxObj.FuncCount = ReplSavedFuncCount
|
||
# M4:会话基线(CDefine/CTypedef)水位回滚。基线只在片段成功时追加增量
|
||
# (ReplBaselineCapture),故此处截断对正常路径是恒等操作;它保证"失败片段
|
||
# 绝不改变基线"这一不变式在将来捕获点位置变化时仍然成立。
|
||
ReplBaselineTruncate(ReplSavedCDefineCount, ReplSavedCTypedefCount)
|
||
return 0
|
||
|
||
|
||
# 执行一个片段(任意顶层语句:表达式/赋值/注解赋值/增强赋值/...)。
|
||
# 返回 0=成功(已按 result_kind 回显);非 0=失败(已打印原因,未崩)。
|
||
# 注意:翻译期 fatal_error / llc 失败会经 VLogger.error → longjmp 回到 run 的恢复点,
|
||
# 此时本函数不返回;失败片段占用的符号序号已提前递增(名字/文件名不复用)。
|
||
def ReplExecFragment(mb: memhub.MemBuddy*, src: str) -> int:
|
||
"""片段 → DLL → 调用 → ctx 回显;0=成功"""
|
||
global ReplFragFn, ReplFragNo, ReplSavedVarCount, ReplSavedFuncCount
|
||
global ReplSavedCDefineCount, ReplSavedCTypedefCount
|
||
if mb is None or src is None:
|
||
return 1
|
||
if ReplTmpDir is None or ReplNameBuf is None or ReplSrcBuf is None:
|
||
stdio.printf("REPL: 会话缓冲未初始化\n")
|
||
return 1
|
||
if ReplSrcPath is None or ReplIrBuf is None or ReplNeedleBuf is None:
|
||
stdio.printf("REPL: 会话缓冲未初始化\n")
|
||
return 1
|
||
if ReplCtxBuf is None or ReplCtxObj is None or ReplVarTable is None:
|
||
stdio.printf("REPL: ctx 会话内存未初始化\n")
|
||
return 1
|
||
|
||
# ⓪ 每片段重新武装恢复点 + 会话表水位快照
|
||
# - set_repl_jmpbuf 幂等重注册:jmp_buf 生命周期 = 整个会话(PermPool,只在
|
||
# run 退出时 clear_repl_jmpbuf),中途不清 ⇒ 覆盖整条流水线(翻译 → 写 .ll
|
||
# → llc → clang++ → LoadLibrary → GetProcAddress → 调用 → 回显);
|
||
# - 真正的"武装"是 run 在本函数调用前的 _setjmp(jb, None)(恢复点存于同一
|
||
# 存活栈帧);此处重注册只是保证注册值不因任何路径丢失。
|
||
# - 水位快照供恢复分支回滚:失败片段可能已 repl_ensure_entry(Count+1)后才
|
||
# 在 repl_seal/后续阶段 fail-fast,若不回滚会把半成品条目留给下一片段。
|
||
# - M3 同时快照会话函数表水位(FuncCount):定义片段的登记是"逐函数"的,
|
||
# 中途失败(如第 2 个符号取址失败)必须整体回滚,否则表里留下半批登记。
|
||
VLogger.set_repl_jmpbuf(ReplJmpBuf)
|
||
ReplSavedVarCount = ReplCtxObj.Count
|
||
ReplSavedFuncCount = ReplCtxObj.FuncCount
|
||
# M4:会话基线水位(CDefine/CTypedef)——失败片段不得把增量带进基线,
|
||
# 与变量表/函数表水位同思路(成功才由 ReplBaselineCapture 追加增量)。
|
||
ReplSavedCDefineCount = ReplBaseCDefineCount
|
||
ReplSavedCTypedefCount = ReplBaseCTypedefCount
|
||
|
||
# ① 取号并立即递增:即使后续失败(含 longjmp)也不复用号,保证会话内名字/文件唯一
|
||
n: int = ReplFragNo
|
||
ReplFragNo = n + 1
|
||
viperlib.snprintf(ReplNameBuf, _REPL_NAME_CAP, "__repl_frag_%d", n)
|
||
|
||
# ② 剥行首空白:片段整体作为函数体,缩进由模板统一给出(否则与模板缩进叠加
|
||
# 会产生 IndentationError)。多行块时该步只作用于**第一行**的行首空白
|
||
# (块内其余行由 ③ 的逐行装配统一加 4 空格、原相对缩进保留)。
|
||
body: str = src
|
||
if body is not None:
|
||
while body[0] == ' ' or body[0] == '\t':
|
||
body = body + 1
|
||
|
||
# ③ 包裹模板:ctx 形参(bytes ⇒ i8*,名与 GenTypes.REPL_CTX_PARAM 同源)
|
||
# export → 不加 SHA1 前缀(parser 改写返回注解为 <T>|t.CExport;
|
||
# FuncGenDef 命中 has_export → 用裸名 define)
|
||
# 单行 = 既有模板逐字不变;多行(块)= M3 提升顶层 def + 逐行缩进 4 空格
|
||
# (见 _repl_split_body / _repl_build_frag_src_multi;只缩进第一行会让
|
||
# 第 2 行起顶格 ⇒ IndentationError)
|
||
# 返回:0=成功;1=提升规则拒绝(原因已由提升函数打印);2=缓冲不足
|
||
rc_build: int = _build_frag_src(ReplNameBuf, body)
|
||
if rc_build == 2:
|
||
stdio.printf("片段 %s: 包裹源码装配失败(缓冲 %d 字节不足)\n",
|
||
ReplNameBuf, _REPL_FRAG_SRC_CAP)
|
||
return 1
|
||
if rc_build != 0:
|
||
# 提升规则拒绝(async def / raise / 重名 / 签名非单行 / 自带 ctx 形参…):
|
||
# 整片段不执行(无任何部分副作用),会话继续
|
||
return 1
|
||
|
||
# ④ 包裹源码落盘(仅用于报错回显源行:fatal_error 经 _read_source_line 读该文件)
|
||
viperlib.snprintf(ReplSrcPath, _REPL_PATH_CAP, "%s/%s.vp", ReplTmpDir, ReplNameBuf)
|
||
sf: rawptr[fileio.File] = fileio.File(ReplSrcPath, fileio.MODE.W)
|
||
if not sf.closed:
|
||
sf.write(ReplSrcBuf, string.strlen(ReplSrcBuf))
|
||
sf.close()
|
||
|
||
# ⑤ 每片段重置模块级翻译状态(对齐 Phase1Main.vp:1027-1032 / BuildPipeline.vp:155-168)
|
||
# temp_dir 供依赖 .vpi 注入使用:优先用工程临时目录(含 includes 存根),
|
||
# 无工程配置时退回本会话目录
|
||
vpi_dir: str = Config.TempDir
|
||
if vpi_dir is None:
|
||
vpi_dir = ReplTmpDir
|
||
GenApi.set_temp_dir(vpi_dir)
|
||
# CallGenBase.TempDir/TempDirLen(跨模块返回类型 .vpi/.text.ll 查找、CExport 判定
|
||
# 的目录)只由 SetTempDir 设置(CallGenBase.vp:824-828);批处理在 RunPhase1 开头
|
||
# 设置(Phase1Main.vp:332),REPL 此前漏设 ⇒ 跨模块解析读不到 .vpi。与上面的
|
||
# set_temp_dir 同值、幂等,每片段设置一次。
|
||
GenApi.SetTempDir(vpi_dir)
|
||
GenApi.set_current_file_type(ReplSrcPath)
|
||
GenApi.set_current_file_imports(ReplSrcPath)
|
||
GenApi.clear_trace()
|
||
GenApi.clear_cdefine_constants()
|
||
GenApi.clear_ctypedefs()
|
||
# 可见性用**宽松模式(strict=0,全部已注册结构体可见)**:片段的类型来自
|
||
# 会话(先前片段定义/注册的类)而非"本文件 import",严格模式(批处理的每文件
|
||
# 隔离口径)会把它们全判为不可见 → 裸类名注解解析失败。宽松模式使
|
||
# `p: Point = ...` 在定义过 Point 的后续片段里可解析。
|
||
GenApi.reset_visible_structs(mb, 0)
|
||
# 结构体注册走"永久分配"路径(对齐 Phase 1b/Phase2 的 StdlibAlloc=1):
|
||
# 结构体表体/条目名/字段类型进 PermPool,否则它们随本片段 mb.reset_to 回收,
|
||
# 后续片段的 find_struct/get_struct_type 读悬空内存(M1 无结构体,故未设)。
|
||
GenApi.set_stdlib_alloc(1)
|
||
# M4:CDefine/CTypedef 的"会话基线"回灌。上面两个 clear_* 把表指针置 None
|
||
# (表语义是"整个会话一份",见 M4 设计)⇒ 此处把基线条目重新登记进本片段池:
|
||
# 片段内看到的是"会话基线(先前片段 / 先前 import 带进来的常量与类型别名)
|
||
# ∪ 本片段新增";本片段新增由成功后 ReplBaselineCapture 追加进基线。
|
||
ReplBaselineRestore(mb)
|
||
|
||
# ⑥ 解析包裹源码。池选择(M4):片段源码含 "class " 时用**永久池**解析——
|
||
# 片段里的 class 定义会被翻译进会话(结构体 + 方法 + 泛型模板表),其中
|
||
# **泛型模板**会把 ClassDef **节点本身**存进永久表(GenericClassNodes),
|
||
# 供后续片段特化时重读模板;若节点在本片段池,后续片段特化即读已回收内存。
|
||
# 永久池解析让"模板节点"与"模板表"同寿命(代价:含 class 的片段 AST 常驻,
|
||
# 量级 KB/片段)。不含 class 的片段仍用 mb,行为与 M2/M3 逐字一致。
|
||
parse_pool: memhub.MemBuddy* = mb
|
||
if _perm_pool is not None:
|
||
if string.strstr(ReplSrcBuf, "class ") is not None:
|
||
parse_pool = _perm_pool
|
||
tree: ast.AST* = ast.parse(ReplSrcBuf, parse_pool)
|
||
if tree is None:
|
||
el: int = ast.get_parse_err_line()
|
||
ec: int = ast.get_parse_err_col()
|
||
em: str = ast.get_parse_err_msg()
|
||
if em is None:
|
||
em = "(未知)"
|
||
stdio.printf("片段 %s: 包裹源码解析失败(第 %d 行第 %d 列: %s)\n",
|
||
ReplNameBuf, el, ec, em)
|
||
return 1
|
||
|
||
# ⑦ 翻译为完整模块 IR(Translator 本体在 mb)
|
||
# ModuleSha1 = 会话 sha1(M4):片段内定义的类型名恒定 "<sha1>.<Type>",
|
||
# 与后续片段的查找用同一键(类型跨片段可见的唯一基础);
|
||
# `export def`(片段入口 / 会话函数)走 has_export 分支取**裸名**
|
||
# (FuncGenDef.vp:482-485,经 _mangle_func_name 亦不前缀),故 M1-M3 的
|
||
# 裸名约定不受影响(片段入口名/会话函数名/GetProcAddress/探针全部照旧)。
|
||
# ReplCtx 置位 = 打开 REPL 模式(会话变量按 ctx 槽寻址);翻译后清空。
|
||
# 每个片段用新建的 Translator ⇒ longjmp 中断也不会把 ReplCtx 残留给下一片段。
|
||
tr: rawptr[HandlesTranslator.Translator] = mb.alloc(HandlesTranslator.Translator.__sizeof__())
|
||
if tr is None:
|
||
stdio.printf("片段 %s: Translator 分配失败\n", ReplNameBuf)
|
||
return 1
|
||
tr.__before_init__()
|
||
tr.__init__()
|
||
tr.ModuleSha1 = ReplSessionSha1
|
||
tr._declare_only = 0
|
||
tr.ReplCtx = ReplCtxObj
|
||
rc_tr: int = tr.translate(tree)
|
||
tr.ReplCtx = None
|
||
if rc_tr != 0:
|
||
stdio.printf("片段 %s: 翻译失败\n", ReplNameBuf)
|
||
return 1
|
||
tr.dump_ir(ReplIrBuf, _REPL_IR_CAP, llvmlite.OUTPUT_FULL)
|
||
|
||
# ⑦' M4:本片段是否**定义**了会话前缀符号(`@"<session sha1>.…"` = 类方法/
|
||
# 构造/类全局)?是 ⇒ 其 import lib 必须产出并登记,供后续片段链接期解析
|
||
# 这些符号(片段 DLL 之间没有隐式链接)。用 "@\"" + sha1 + "." 作探针
|
||
# (def/global 的命名起点;类型定义行是 %"…" 形态,不会误命中)。
|
||
needs_implib: int = 0
|
||
if ReplSessionSha1 is not None and ReplNeedleBuf is not None:
|
||
viperlib.snprintf(ReplNeedleBuf, _REPL_NEEDLE_CAP, "@\"%s.", ReplSessionSha1)
|
||
if string.strstr(ReplIrBuf, ReplNeedleBuf) is not None:
|
||
needs_implib = 1
|
||
|
||
# ⑧ 导出符号探针:签名必须是 (i8* %__repl_ctx__) 且返回 i32。
|
||
# 不含该形参 ⇒ 包裹模板/调用约定不一致,或片段含 raise 使函数被追加
|
||
# __eh_msg_out__/__eh_code_out__ 两个出参——此时宿主按单参调用是未定义行为,
|
||
# 故直接明确报错(不加载、不调用)。
|
||
viperlib.snprintf(ReplNeedleBuf, _REPL_NEEDLE_CAP, "define i32 @%s(i8* %%%s",
|
||
ReplNameBuf, GenTypes.REPL_CTX_PARAM)
|
||
if string.strstr(ReplIrBuf, ReplNeedleBuf) is None:
|
||
stdio.printf("片段 %s: 未生成约定的导出函数(期望 `define i32 @%s(i8* %%__repl_ctx__)`);"
|
||
"含 raise 的片段会追加异常出参,M2 不支持\n",
|
||
ReplNameBuf, ReplNameBuf)
|
||
return 1
|
||
|
||
# ⑧' 会话函数签名校验(M3):在 llc/加载**之前**判完(EH 出参 / 类型不受支持 /
|
||
# 首形参非 ctx / 形参过多 / 找不到 define / 重定义签名不一致 / 表容量不足)
|
||
# ⇒ 任一不通过就整片段不执行(无部分副作用;ctx 槽与表都不动)。
|
||
# ReplLiftNameCount 为 0(本片段没有提升任何会话函数)时立即返回 0。
|
||
if _repl_precheck_session_funcs() != 0:
|
||
return 1
|
||
|
||
# ⑨ 写 .ll(复用 BuildPipeline:切片路径 + ensure_dir)
|
||
ir_len: sizet = string.strlen(ReplIrBuf)
|
||
if BuildPipeline.write_ir_to_file(ReplIrBuf, ir_len, ReplTmpDir, ReplNameBuf) != 0:
|
||
stdio.printf("片段 %s: 写 .ll 失败(目录 %s)\n", ReplNameBuf, ReplTmpDir)
|
||
return 1
|
||
ir_path: str = BuildPipeline.build_sliced_path(ReplTmpDir, ReplNameBuf, "ll")
|
||
if ir_path is None:
|
||
stdio.printf("片段 %s: .ll 路径构造失败\n", ReplNameBuf)
|
||
return 1
|
||
|
||
# ⑩ llc → .obj(复用既有机制;失败路径内部 VLogger.error 报 module/exit/cmd 后 longjmp)
|
||
cc_cmd: str = Config.CompilerCmd
|
||
if cc_cmd is None:
|
||
cc_cmd = "llc"
|
||
cc_flags: str = Config.CompilerFlags
|
||
if cc_flags is None:
|
||
cc_flags = "-filetype=obj -relocation-model=pic"
|
||
if BuildPipeline.compile_ll_to_obj(ir_path, ReplTmpDir, ReplNameBuf, cc_cmd, cc_flags) != 0:
|
||
# 走不到这里的 llc 失败:compile_ll_to_obj 内部 VLogger.error 已打印 module/exit/cmd
|
||
# 并 longjmp(BuildPipeline.vp:373-375)。能返回非 0 的只有其内部"分配失败"分支
|
||
# (obj_path/cmd 缓冲为 NULL,BuildPipeline.vp:315/367)⇒ 补一行明示,避免静默回提示符。
|
||
stdio.printf("片段 %s: llc 编译 .obj 失败(命令/路径缓冲分配失败,见上方诊断)\n",
|
||
ReplNameBuf)
|
||
return 1
|
||
|
||
# ⑪ clang++ -shared → .dll(命令拼法对齐 BuildPipeline.link_obj_to_exe 的
|
||
# "cmd ... -o 输出 输入";拉起步子复用 BuildPipeline._run_cmd_wait)
|
||
obj_path: str = BuildPipeline.build_sliced_path(ReplTmpDir, ReplNameBuf, "obj")
|
||
dll_path: str = BuildPipeline.build_sliced_path(ReplTmpDir, ReplNameBuf, "dll")
|
||
if obj_path is None or dll_path is None:
|
||
stdio.printf("片段 %s: .obj/.dll 路径构造失败\n", ReplNameBuf)
|
||
return 1
|
||
linker_cmd: str = Config.LinkerCmd
|
||
if linker_cmd is None:
|
||
linker_cmd = "clang++"
|
||
cmd: bytes = mb.alloc(_REPL_CMD_CAP)
|
||
if cmd is None:
|
||
stdio.printf("片段 %s: 命令行缓冲分配失败\n", ReplNameBuf)
|
||
return 1
|
||
# 跨模块符号(`<sha1>.<函数>` / `<sha1>.<全局>`)由宿主 exe 定义:把宿主导出的
|
||
# import lib 加进命令行 ⇒ 片段 DLL 的导入表指向宿主 exe,符号在链接期解析、
|
||
# 加载期由同一进程内已加载的宿主满足(同一份 includes 代码与数据,无状态重复)。
|
||
# ReplHostImplib 为 None(宿主未按导出模式构建)时保持旧口径:只链片段自身 obj,
|
||
# 此时跨模块调用会在链接期报 undefined symbol(不终止会话,见下方提示)。
|
||
# M4 追加:① 本片段产出 import lib(--out-implib)供后续片段链接;
|
||
# ② 把先前片段登记的 import lib 全部带上(本片段引用其定义的类/方法)。
|
||
implib_path: str = mb.alloc(_REPL_PATH_CAP)
|
||
if implib_path is None:
|
||
stdio.printf("片段 %s: import lib 路径缓冲分配失败\n", ReplNameBuf)
|
||
return 1
|
||
viperlib.snprintf(implib_path, _REPL_PATH_CAP, "%s.dll.a", dll_path)
|
||
pos_c: int = _repl_cat_str(cmd, _REPL_CMD_CAP, 0, linker_cmd)
|
||
pos_c = _repl_cat_str(cmd, _REPL_CMD_CAP, pos_c, " -shared -o ")
|
||
pos_c = _repl_cat_str(cmd, _REPL_CMD_CAP, pos_c, dll_path)
|
||
pos_c = _repl_cat_str(cmd, _REPL_CMD_CAP, pos_c, " ")
|
||
pos_c = _repl_cat_str(cmd, _REPL_CMD_CAP, pos_c, obj_path)
|
||
if ReplHostImplib is not None:
|
||
pos_c = _repl_cat_str(cmd, _REPL_CMD_CAP, pos_c, " ")
|
||
pos_c = _repl_cat_str(cmd, _REPL_CMD_CAP, pos_c, ReplHostImplib)
|
||
pos_c = _repl_cmd_append_implibs(cmd, _REPL_CMD_CAP, pos_c)
|
||
if pos_c >= 0:
|
||
pos_c = _repl_cat_str(cmd, _REPL_CMD_CAP, pos_c, " -Wl,--out-implib=")
|
||
pos_c = _repl_cat_str(cmd, _REPL_CMD_CAP, pos_c, implib_path)
|
||
if pos_c < 0:
|
||
stdio.printf("片段 %s: 链接命令行超过 %d 字节(已登记的片段 import lib 共 %d "
|
||
"个)——本片段不执行;可重启会话减少片段数\n",
|
||
ReplNameBuf, _REPL_CMD_CAP, ReplFragImplibCount)
|
||
return 1
|
||
link_rc: int = BuildPipeline._run_cmd_wait(cmd)
|
||
if link_rc != 0:
|
||
stdio.printf("片段 %s: 生成 .dll 失败 (exit=%d)\n 命令: %s\n",
|
||
ReplNameBuf, link_rc, cmd)
|
||
if ReplHostImplib is None:
|
||
stdio.printf(" 原因提示: 未找到宿主 import lib(<exe 名>.dll.a)——"
|
||
"跨模块符号(形如 <16位hex>.<函数名>)无法解析;"
|
||
"宿主需按 TransPyV/project.vpj 构建(含 -Wl,--export-all-symbols),"
|
||
"且 <exe 名>.dll.a 与 exe 同目录\n")
|
||
else:
|
||
stdio.printf(" 原因提示: 若输出含 undefined symbol 且形如 <16位hex>.<名>,"
|
||
"则该符号未被宿主定义/导出(宿主构建时未链入对应 includes 模块);"
|
||
"若该函数在源码里是 `export def`(CExport,裸名定义)却仍带前缀,"
|
||
"多为 include 存根 .vpi 缺失(见会话开头的 includes 扫描行)\n")
|
||
return 1
|
||
|
||
# ⑪' M4:若本片段 IR 里定义了会话前缀符号 ⇒ 登记其 import lib
|
||
# (链接已产出 <dll>.dll.a)。后续片段链接时自动带上它 ⇒ 片段 N 里
|
||
# `p = Point(1,2)` / `p.x` 之外的**方法调用**(`p.get()`)能解析到片段 1
|
||
# 的 `<session sha1>.Point.get` 定义(构造/类全局同理)。
|
||
# 登记失败(上限/文件缺失)只告警:更晚的片段对这些符号会在链接期
|
||
# undefined(会话继续,见 ReplFragImplibCount 的上限说明)。
|
||
if needs_implib != 0:
|
||
if _is_file(implib_path) != 0 and ReplFragImplibRecord(implib_path) != 0:
|
||
stdio.printf("片段 %s: 已登记片段 import lib(后续片段可解析本片段定义的"
|
||
"类型/方法符号): %s\n", ReplNameBuf, implib_path)
|
||
else:
|
||
stdio.printf("片段 %s: 片段 import lib 未登记(已登记 %d 个,上限 %d;或文件"
|
||
"缺失)——更晚的片段可能解析不到本片段定义的类型/方法符号\n",
|
||
ReplNameBuf, ReplFragImplibCount, _REPL_FRAG_IMPLIB_MAX)
|
||
|
||
# ⑫ 动态加载 + 取地址(LoadLibraryA/GetProcAddress 见 includes/w32/win32base.vp:65/68)
|
||
h_lib: win32base.HANDLE = win32base.LoadLibraryA(dll_path)
|
||
if h_lib is None:
|
||
stdio.printf("片段 %s: LoadLibraryA 失败: %s\n", ReplNameBuf, dll_path)
|
||
if ReplHostImplib is not None:
|
||
stdio.printf(" 原因提示: 片段 DLL 的导入表指向宿主 exe(%s);"
|
||
"若 GetLastError 是 127(找不到指定过程)/ 加载器 0xC0000139,"
|
||
"多为宿主与该 import lib 非同一次构建产物、或宿主缺该导出;"
|
||
"进程镜像名(%s)与 import lib 记录名不一致时同样会失败\n",
|
||
ReplHostImplib, dll_path)
|
||
return 1
|
||
fp_addr: VOIDPTR = win32base.GetProcAddress(h_lib, ReplNameBuf)
|
||
if fp_addr is None:
|
||
stdio.printf("片段 %s: GetProcAddress 返回空(导出表中无该符号): %s\n",
|
||
ReplNameBuf, dll_path)
|
||
return 1
|
||
|
||
# ⑫' 会话函数注册(M3):DLL 已加载 ⇒ GetProcAddress(裸名) 取地址 → 写 ctx
|
||
# 函数指针槽 → 追加/更新会话函数表。放在**调用片段入口之前**:失败则本片段
|
||
# 的语句一律不执行(只留下已加载但未被引用的 DLL),且由水位回滚丢弃半批登记。
|
||
# 本片段内对同名函数的调用仍走直接调用(模块内有 define)⇒ 注册只服务后续片段。
|
||
if _repl_register_session_funcs(h_lib, n) != 0:
|
||
if ReplCtxObj is not None:
|
||
ReplCtxObj.FuncCount = ReplSavedFuncCount
|
||
return 1
|
||
|
||
# ⑬ 调用前复位结果区(kind=none),并刷新 var_count/last_frag(供检视/调试);
|
||
# 存入 t.Callable 槽并间接调用(函数地址必须存活到调用之后,故不 FreeLibrary:
|
||
# 会话模型下模块常驻,供后续片段复用)
|
||
head: rawptr[GenTypes.ReplResult] = _ctx_head()
|
||
if head is None:
|
||
return 1
|
||
head.Kind = GenTypes.REPL_RES_NONE
|
||
head.VarCount = ReplCtxObj.Count
|
||
head.LastFrag = n
|
||
ReplFragFn = fp_addr
|
||
# 实参类型即构造出的调用签名形参类型(bytes ⇒ i8*),与片段定义一致
|
||
r_rc: int = ReplFragFn(ReplCtxBuf)
|
||
|
||
# ⑭ 从 ctx 结果区读回并回显(片段返回值只作状态码)
|
||
if _repl_echo_result(ReplNameBuf, head) != 0:
|
||
stdio.printf("片段 %s: 结果区读取失败\n", ReplNameBuf)
|
||
return 1
|
||
if r_rc != 0:
|
||
stdio.printf("片段 %s: 片段返回非零状态码 %d\n", ReplNameBuf, r_rc)
|
||
# ⑭' M4:片段**成功** ⇒ 把本片段新增的 CDefine/CTypedef 条目并入会话基线。
|
||
# 失败路径(早返回 / longjmp)不执行 ⇒ 基线天然不含失败片段的半成品;
|
||
# 截断(ReplBaselineTruncate)只是水位一致性的防御性保障。
|
||
ReplBaselineCapture()
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# run - M2 REPL 主循环
|
||
# ============================================================
|
||
def run(mb: memhub.MemBuddy*) -> int:
|
||
"""M2/M3 REPL:读一行/一整块 → 解析 → 编译为 DLL 并调用(会话变量经 ctx 槽)→ 回显;
|
||
行尾 ':' 进入块级续行(提示符 "... "),空行或 EOF 结束并整块执行一次;错误不终止进程。"""
|
||
global ReplJmpBuf, ReplSavedVarCount, ReplSavedFuncCount, ReplInBlock, ReplBlockLen
|
||
if mb is None or _perm_pool is None:
|
||
stdio.printf("REPL: 需要主池与 PermPool(由 main 注入 _perm_pool)\n")
|
||
return 1
|
||
|
||
# 解析表(关键字/运算符)建在 PermPool:它们是 ast 的模块级全局、仅首次建表;
|
||
# 若建在 mb 上会被每行 reset_to(mark) 释放 → 下一行解析 use-after-free 崩溃。
|
||
ast._init_tables(_perm_pool)
|
||
|
||
jb: rawptr = _perm_pool.alloc(_REPL_JMPBUF_SIZE)
|
||
if jb is None:
|
||
stdio.printf("REPL: jmp_buf 分配失败\n")
|
||
return 1
|
||
# 会话级注册:ReplJmpBuf 供每片段入口幂等重注册;VLogger 侧登记一次即可长期有效,
|
||
# 只在 run 退出时 clear(中途不清 —— 这是"覆盖整条流水线"的前提)。
|
||
ReplJmpBuf = jb
|
||
ReplSavedVarCount = 0
|
||
ReplSavedFuncCount = 0
|
||
VLogger.set_repl_jmpbuf(jb)
|
||
# jit_flag 由 main 在调用 run 前置 True(见 main.vp --repl 分支),run 退出时复位 False
|
||
lg: VLogger.Logger = VLogger.logger
|
||
|
||
# 恢复点必须"**先武装再使用**":ReplInitSession 及其调用(GetTempPathA / 建目录 /
|
||
# 一次性常驻分配)内部若走 VLogger.error(→ repl_abort → longjmp),jmp_buf 必须
|
||
# 已被 _setjmp 初始化过;否则是对**未初始化 jmp_buf** 的 longjmp(未定义行为,
|
||
# 表现为"启动期进程消失、提示符永不出现")。会话初始化是**唯一**必须成功的阶段
|
||
# (没有常驻缓冲/ctx 就没有片段通路),故这里恢复后按失败退出,不做"带伤继续"。
|
||
# (注:JmpBuf 尚未武装的窗口只有上面的 ast._init_tables —— 那时 _repl_jmpbuf 也
|
||
# 尚未注册,repl_abort 走 sys.exit(1),与批处理同口径。)
|
||
rc_init: int = _setjmp(jb, None)
|
||
if rc_init != 0:
|
||
stdio.printf("REPL: 会话初始化中断(诊断见上)—— 无常驻缓冲/ctx,REPL 无法启动\n")
|
||
stdio.fflush(None)
|
||
ReplJmpBuf = None
|
||
VLogger.clear_repl_jmpbuf()
|
||
if lg is not None:
|
||
lg.set_jit_flag(False)
|
||
return 1
|
||
|
||
# 会话初始化:临时目录 + 常驻缓冲 + ctx(均 PermPool,见 ReplInitSession)
|
||
if ReplInitSession() != 0:
|
||
stdio.printf("REPL: 会话初始化失败(临时目录/常驻缓冲/ctx)\n")
|
||
ReplJmpBuf = None
|
||
VLogger.clear_repl_jmpbuf()
|
||
if lg is not None:
|
||
lg.set_jit_flag(False)
|
||
return 1
|
||
|
||
h_in: win32base.HANDLE = w32file.GetStdHandle(w32file.STD_INPUT_HANDLE)
|
||
line: bytes = stdlib.malloc(_REPL_LINE_CAP)
|
||
if line is None:
|
||
stdio.printf("REPL: 行缓冲分配失败\n")
|
||
ReplJmpBuf = None
|
||
VLogger.clear_repl_jmpbuf()
|
||
if lg is not None:
|
||
lg.set_jit_flag(False)
|
||
return 1
|
||
|
||
stdio.printf("TransPyV REPL (M2/M3/M4) - 会话变量 + 会话函数(跨片段)+ ctx 回传结果;\n")
|
||
stdio.printf(" 支持表达式(1+2)、定义(x = 1 / x: int = 5)、引用(x + 1)、\n")
|
||
stdio.printf(" 增强赋值(x += 2)、取址(p = &x)等;空行跳过,Ctrl+Z 或 EOF 退出\n")
|
||
stdio.printf(" 多行块:行尾 ':' 进入续行(提示符 ...),空行结束并整块执行一次\n")
|
||
stdio.printf(" 跨片段函数:块里顶格的 def 会被提升为片段模块的 export def(裸名导出),\n")
|
||
stdio.printf(" 后续片段可直接调用(如 def f(x: int) -> int: 换行 +4 空格 return x + 1);\n")
|
||
stdio.printf(" 限制:签名须单行、不支持 async def、函数体内不支持 raise、不支持偏函数\n")
|
||
stdio.printf(" 跨片段类型(M4):块里的 class 会被翻译进会话(类型名 %s.<类名>),\n",
|
||
ReplSessionSha1)
|
||
stdio.printf(" 后续片段可实例化、读写字段、调用方法;import 的 includes 模块\n")
|
||
stdio.printf(" 其类同样注册进会话(缺 .vpi 存根时该模块的类不可用,会话继续)\n")
|
||
stdio.printf(" 片段临时目录: %s\n", ReplTmpDir)
|
||
stdio.fflush(None)
|
||
|
||
# === 会话级 includes 初始化(只做一次):扫描 + 注册 SHA1 + 预生成 .vpi
|
||
# + 泛型模板注册 + 逐模块类注册 ===
|
||
# 会话开始即武装一次恢复点:includes 扫描/存根生成/模板注册内部若 fail-fast
|
||
# (VLogger.error → repl_abort → longjmp),必须落到本帧,否则跳到未武装的
|
||
# jmp_buf 是未定义行为。恢复后按宽容口径继续进入读行循环。
|
||
# 【硬保证】提示符必现:includes 的任何失败(含"类注册整体失败")都只影响
|
||
# includes 类的可用性,绝不阻止进入读行循环 —— 本段所有出口都不 return。
|
||
inc_mark: sizet = mb.mark()
|
||
rc_inc: int = _setjmp(jb, None)
|
||
if rc_inc != 0:
|
||
stdio.printf("REPL: includes 初始化中断(已注册 %d 个模块、存根 %d 个):"
|
||
"import includes 模块可能失败,其余片段不受影响\n",
|
||
ReplIncMods, ReplIncVpiNew)
|
||
mb.reset_to(inc_mark)
|
||
else:
|
||
# 扫描结果/注册表都在 stdlib(create_scan_result 用 stdlib.malloc),
|
||
# 扫描期的 mb 分配(sha1 十六进制串等)在注册后即可回收。
|
||
# M4:本调用内部还会做"includes 类注册"(逐模块 _setjmp,见
|
||
# ReplInitIncludesClasses 的退出约定)。
|
||
ReplInitIncludes(mb)
|
||
mb.reset_to(inc_mark)
|
||
# ⚠ 必须在 run 的帧上重新武装恢复点(注意:**不能**把这几行包成辅助函数——
|
||
# _setjmp 留下的跳转目标属于"调用它的那个帧",包成函数后该帧立刻返回,
|
||
# 目标又变成死帧)。ReplInitIncludes 内部的逐模块 _setjmp
|
||
# (ReplInitIncludesClasses)已把目标指向那些**已返回**的帧;此处重设之后,
|
||
# 下面的收尾与整个读行循环都在本帧的恢复点保护下 —— 这正是"includes 类注册
|
||
# 之后提示符消失"(longjmp 到已返回的帧 → 未定义行为)的修复点。
|
||
rc_arm: int = _setjmp(jb, None)
|
||
if rc_arm != 0:
|
||
stdio.printf("REPL: includes 类布局收尾中断(诊断见上):布局注册可能不完整,"
|
||
"会话继续\n")
|
||
else:
|
||
# 收尾:收敛登记"父类在后续遍才就绪"的类 + 逐类告警拒绝 + 汇总。放在
|
||
# **重武装之后**:其内部可能经 GenApi.register_struct → VLogger.error 触发
|
||
# longjmp,此刻必须落在本帧(否则就是上面那个死帧问题)。
|
||
VpiReader.ReplStubLayoutFinish()
|
||
# 关闭"存根布局恢复"必须无条件执行:ReplStubLayoutMode 不归零会让片段翻译
|
||
# 误走 REPL 存根路径(本函数只置标志,无失败路径)。
|
||
VpiReader.ReplStubLayoutEnd()
|
||
|
||
# === 宿主导出符号(跨模块调用):定位宿主 exe 的 import lib(只读探测) ===
|
||
# 命中 ⇒ 片段链接时把它加进命令行(片段 DLL 从宿主导入 includes 符号,共用同一份
|
||
# 代码与数据);未命中 ⇒ 只告警,不带 import lib 链接(跨模块调用会在链接期失败,
|
||
# 由 ReplExecFragment 打印明确原因,会话继续)。
|
||
if ReplFindHostImplib() != 0:
|
||
stdio.printf("宿主符号导入库: %s(片段可跨模块调用 includes 函数/全局)\n",
|
||
ReplHostImplib)
|
||
else:
|
||
stdio.printf("宿主符号导入库: 未找到 <exe 名>.dll.a;片段调用 includes 模块级"
|
||
"函数会在链接期报 undefined symbol(会话不终止)。请用 "
|
||
"TransPyV/project.vpj(已声明 -Wl,--export-all-symbols)构建宿主,"
|
||
"并让 exe 与 .dll.a 同目录\n")
|
||
stdio.fflush(None)
|
||
|
||
# === 读行循环:单行片段 + 块级续行 ===
|
||
# 走到这里 = 初始化阶段的**全部**失败路径都已消化(includes 类注册失败/超预算
|
||
# 只降级功能,不影响可用性)⇒ 提示符必然出现。日志里这一行是"REPL 起来了"的
|
||
# 硬判据(紧随其后即 ">>> ")。
|
||
stdio.printf("REPL: 初始化完成,进入读行循环\n")
|
||
stdio.fflush(None)
|
||
# 块状态(ReplInBlock/ReplBlockLen)在每次"块结束/失败/EOF/溢出"后一律归零:
|
||
# ① 空行/EOF 结束续行 → 执行整块后归零;② 执行期 longjmp → 恢复分支归零;
|
||
# ③ 块缓冲溢出 → 丢弃整块并归零。
|
||
# 归零是"残块不污染下一行"的依据:下一次 _block_append 从索引 0 覆盖写并重写 NUL,
|
||
# 旧字节不可达(且新块解析只经 NUL 结尾串,不读旧长度之后的内容)。
|
||
ReplInBlock = 0
|
||
ReplBlockLen = 0
|
||
while 1:
|
||
# 提示符:续行 "... "、正常 ">>> "(与既有 ">>> " 同一打印点、同一刷新时机)
|
||
if ReplInBlock != 0:
|
||
stdio.printf("... ")
|
||
else:
|
||
stdio.printf(">>> ")
|
||
stdio.fflush(None)
|
||
n: int = _read_line(h_in, line, _REPL_LINE_CAP)
|
||
eof_hit: int = 0
|
||
if n < 0: # EOF:先按"终止行"处理,再退出
|
||
eof_hit = 1
|
||
n = 0
|
||
# 每行独立:先取水位,再 _setjmp(武装本片段/本块的恢复点,覆盖整条流水线);
|
||
# 解析/翻译/llc/加载/调用期的任何 longjmp 都回收到 mark 并继续。
|
||
mark: sizet = mb.mark()
|
||
rc: int = _setjmp(jb, None)
|
||
if rc != 0:
|
||
# longjmp 返回:诊断已由 VLogger.error / fatal_error 等打印(均以换行
|
||
# 结尾),此处只清块状态(残块不得留给下一行输入)+ 回滚会话表水位
|
||
# (丢弃失败片段的半成品登记)+ 回收本行分配,随后直接打印下一个提示符
|
||
# ——不再额外打印"已从错误恢复"。块状态归零对单行路径是恒等写入。
|
||
ReplInBlock = 0
|
||
ReplBlockLen = 0
|
||
if ReplCtxObj is not None:
|
||
ReplCtxObj.Count = ReplSavedVarCount
|
||
ReplCtxObj.FuncCount = ReplSavedFuncCount
|
||
# M4:会话基线(CDefine/CTypedef)水位回滚(与上面的表水位同思路)
|
||
ReplBaselineTruncate(ReplSavedCDefineCount, ReplSavedCTypedefCount)
|
||
mb.reset_to(mark)
|
||
if eof_hit != 0:
|
||
break
|
||
continue
|
||
if eof_hit != 0:
|
||
# EOF 结束:续行中且缓冲非空 ⇒ 先把已缓存块执行掉(等价空行终止);
|
||
# 否则走既有退出路径(打印换行后退出)
|
||
if ReplInBlock != 0 and ReplBlockLen > 0:
|
||
_exec_repl_src(mb, ReplBlockBuf)
|
||
ReplInBlock = 0
|
||
ReplBlockLen = 0
|
||
mb.reset_to(mark)
|
||
stdio.printf("\n")
|
||
break
|
||
if ReplInBlock != 0:
|
||
if n == 0:
|
||
# 空行结束续行:整块作为**一个**片段执行一次;块内错误只打印、会话存活
|
||
if ReplBlockLen > 0:
|
||
_exec_repl_src(mb, ReplBlockBuf)
|
||
ReplInBlock = 0
|
||
ReplBlockLen = 0
|
||
mb.reset_to(mark)
|
||
continue
|
||
# 续行中:每行都入缓冲;不因行尾 ':' 反复切换,也不做缩进分析
|
||
if _block_append(line, n) != 0:
|
||
stdio.printf("REPL: 块输入超过块缓冲上界 %d 字节:本块已丢弃,"
|
||
"回到 >>> 继续(可减少块体积或分块执行)\n",
|
||
_REPL_BLOCK_CAP)
|
||
ReplInBlock = 0
|
||
ReplBlockLen = 0
|
||
mb.reset_to(mark)
|
||
continue
|
||
# ==== 以下为既有单行路径(未进入续行时逐语句不变)====
|
||
if n == 0: # 空行:跳过
|
||
mb.reset_to(mark)
|
||
continue
|
||
if _line_starts_block(line, n) != 0:
|
||
# 行尾 ':' ⇒ 进入续行:该行进缓冲、本行不执行,提示符变 "... "。
|
||
# 首行不可能溢出(行缓冲上界 4095 < 块缓冲上界 8191):append 失败即
|
||
# 按下方单行路径处理,不进入续行状态。
|
||
if _block_append(line, n) == 0:
|
||
ReplInBlock = 1
|
||
mb.reset_to(mark)
|
||
continue
|
||
_exec_repl_src(mb, line)
|
||
mb.reset_to(mark)
|
||
|
||
stdlib.free(line)
|
||
# 会话结束才清空恢复点:中途从不清(保证流水线全程可 longjmp)
|
||
ReplJmpBuf = None
|
||
VLogger.clear_repl_jmpbuf()
|
||
if lg is not None:
|
||
lg.set_jit_flag(False)
|
||
return 0
|