R14:rawptr[X] -> X*(56 文件 / 834 行改动);保留 6 处 rawptr[t.CPtr](存纯裸指针的规范例外)。 修复:class X(t.REnum) 的变体字段写成自引用 X* 时,字段解析在变体扫描期经 ensure_forward_class 建了一个 opaque 前向占位;末尾 register_struct 按 Ty 指针去重会再建第二个同名 entry,而 find_struct 取首个(占位,0 字段)→ X.__sizeof__() == 0,报「已注册但大小为 0(字段链表未填充)」。改为注册前若命中同模块前向占位则就地填充(与 ensure_forward_class 文档口径一致)。TPC 无此问题,属 TPV 侧缺口。 验证:自举三重重编 SUCCESS;全量 77/77 顺序回归通过(修复前卡在 LLvmLiteTest)。
2108 lines
104 KiB
Plaintext
2108 lines
104 KiB
Plaintext
import t
|
||
from stdint import *
|
||
import stdio
|
||
import stdlib
|
||
import string
|
||
import viperlib
|
||
import mlirlite
|
||
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 - interactive loop: wraps each fragment as an export def and JIT-compiles it to a DLL.
|
||
# Session variables/results are shared via ctx memory; errors recover via setjmp/longjmp.
|
||
|
||
# Input line buffer capacity (incl. NUL terminator).
|
||
define _REPL_LINE_CAP = 4096
|
||
|
||
# Block input buffer capacity (session-level PermPool, incl. NUL): the raw user lines
|
||
# appended while in continuation mode, joined by '\n' with no trailing newline.
|
||
define _REPL_BLOCK_CAP = 8192
|
||
|
||
# Measured jmp_buf size on mingw-w64 x86_64: _JBLEN=16 times _JBTYPE (16B) = 256 bytes.
|
||
# Confirmed with a C probe (sizeof(jmp_buf) == 256).
|
||
define _REPL_JMPBUF_SIZE = 256
|
||
|
||
# setjmp/longjmp on mingw-w64 x86_64: _setjmp takes a frame pointer, so the setjmp
|
||
# macro expands to _setjmp(buf, NULL); passing None is fine for non-SEH use.
|
||
extern def _setjmp(buf: rawptr, frame: rawptr) -> int
|
||
extern def longjmp(buf: rawptr, val: int)
|
||
|
||
# PermPool (injected by main): jmp_buf must outlive the session, so mb.reset_to must not reclaim it.
|
||
_perm_pool: memhub.MemBuddy*
|
||
|
||
|
||
# ============================================================
|
||
# M2/M3 constants: wrapped fragment source capacity covers the single-line, block and lifted paths.
|
||
define _REPL_FRAG_SRC_CAP = 26624
|
||
# Lifted module-level def source buffer capacity (incl. NUL).
|
||
define _REPL_LIFT_CAP = 12288
|
||
# Max session functions lifted per fragment (<= GenTypes.REPL_FUNC_CAP; excess is an error).
|
||
define _REPL_LIFT_MAX = 16
|
||
# Session function name length limit (ReplLiftNames slot width = limit + 1).
|
||
define _REPL_FN_NAME_MAX = 64
|
||
# Session function name table capacity (_REPL_LIFT_MAX fixed-width slots).
|
||
define _REPL_FN_NAMES_CAP = 1024
|
||
# Diagnostic display buffer (truncates non-NUL-terminated lines/names for %s).
|
||
define _REPL_DIAG_CAP = 128
|
||
# Signature rendering buffer capacity ("(int, str) -> int" is well under 96).
|
||
define _REPL_SIG_TEXT_CAP = 192
|
||
# Fragment symbol name / probe buffer capacity.
|
||
define _REPL_NAME_CAP = 64
|
||
define _REPL_NEEDLE_CAP = 128
|
||
# Temporary file path buffer capacity (directory/source paths).
|
||
define _REPL_PATH_CAP = 512
|
||
# Full module IR text capacity (matches App/main.vp IR_BUF_SIZE).
|
||
define _REPL_IR_CAP = 262144
|
||
# GetTempPathA output buffer capacity (MAX_PATH).
|
||
define _REPL_TMP_CAP = 260
|
||
# External command (clang++ -shared) line capacity. M4 grew it 1024 -> 16384 because the
|
||
# link line also carries prior fragments' import libs, else their symbols stay undefined.
|
||
define _REPL_CMD_CAP = 16384
|
||
# includes source read buffer (matches Phase1/BuildPipeline SRC_BUF_SIZE).
|
||
define _REPL_SRC_BUF_CAP = 1048576
|
||
# .vpi stub buffer (matches BuildPipeline.PYI_BUF_SIZE).
|
||
define _REPL_VPI_BUF_CAP = 262144
|
||
# ---- M4: session sha1 / fragment import lib / session baseline (CDefine + CTypedef) ----
|
||
# Max prior fragment import lib slots (a fragment takes one if its IR mentions a session symbol).
|
||
define _REPL_FRAG_IMPLIB_MAX = 128
|
||
# Fragment import lib record array capacity (slots x 8-byte pointers).
|
||
define _REPL_FRAG_IMPLIB_ARR_CAP = 1024
|
||
# Session baseline (CDefine) entry limit; replayed when each fragment rebuilds its table.
|
||
define _REPL_BASE_CDEFINE_MAX = 512
|
||
# Session baseline (CTypedef) entry limit.
|
||
define _REPL_BASE_CTYPEDEF_MAX = 512
|
||
# includes class registration (done once at session init): max modules injected via vpi.
|
||
define _REPL_INC_CLS_MOD_MAX = 512
|
||
# Convergence passes for includes class registration (field types may reference peers).
|
||
define _REPL_INC_CLS_PASSES = 3
|
||
# Struct table watermark guard for includes class registration (STRUCT_MAX = 512).
|
||
define _REPL_INC_CLS_STRUCT_GUARD = 448
|
||
# Per-module warning line limit for includes class registration; the rest are only counted.
|
||
define _REPL_INC_CLS_WARN_MAX = 3
|
||
# Startup time budget (ms) for includes class registration: multi-pass vpi injection is the
|
||
# main session-start cost, so on overrun we stop and let later fragments import on demand.
|
||
define _REPL_INC_CLS_BUDGET_MS = 20000
|
||
# Temporary LLVM module name for includes class registration (.vpi injection target).
|
||
define _REPL_INC_MOD_NAME = "repl_inc_init"
|
||
# ctx session buffer size = header 64 + 64 var slots x 16 + 32 func slots x 16 = 1600.
|
||
# Must stay in sync with the GenTypes.REPL_CTX_SIZE / REPL_* layout constants.
|
||
define _REPL_CTX_SIZE = 1600
|
||
# Template project file name (searched in cwd then exe dir when --project is absent).
|
||
define _REPL_PROJECT_FILE = "repl.vpj"
|
||
# cwd candidate suffix: GetCurrentDirectoryA omits the trailing separator, so add '/'.
|
||
define _REPL_PROJECT_SEP_FILE = "/repl.vpj"
|
||
# Longest length of the two suffixes plus NUL headroom (candidate buffer check).
|
||
define _REPL_PROJECT_PROBE_CAP = 16
|
||
|
||
|
||
# ============================================================
|
||
# ReplFloatView - float view of the ctx header (F at offset 16 reinterpreted as double).
|
||
novirtual class ReplFloatView:
|
||
"""Float view of the ctx header (result read as a double at +16)."""
|
||
Magic: int
|
||
Version: int
|
||
Kind: int
|
||
Reserved0: int
|
||
F: double
|
||
|
||
|
||
# ============================================================
|
||
# ReplFuncSlotView - ctx function-pointer slot (16B: address + reserved), same on both sides.
|
||
novirtual class ReplFuncSlotView:
|
||
"""ctx function-pointer slot (Addr = entry address in the defining fragment DLL)."""
|
||
Addr: VOIDPTR
|
||
Reserved: VOIDPTR
|
||
|
||
|
||
# ============================================================
|
||
# ReplSigOut - parsed session function signature (host reads the fragment's model ABI).
|
||
novirtual class ReplSigOut:
|
||
"""Parsed signature result (registration is rejected when Bad/EHTag is non-zero)."""
|
||
RetTag: int # GenTypes.REPL_TY_VOID / INT / FLOAT / STR
|
||
RetBits: int
|
||
ParamCount: int # excludes the leading ctx param
|
||
EHTag: int # 1=define has %__eh_msg_out__/%__eh_code_out__ (raise semantics)
|
||
Bad: int # 1=contains an unsupported type token
|
||
BadType: str # unsupported type string (points into IR text; for printing only)
|
||
|
||
|
||
# ============================================================
|
||
# Template project lookup: tries cwd/repl.vpj then exe-dir/repl.vpj, writing diagnostics.
|
||
def ReplFindProject(Out: bytes, Cap: int, Diag: bytes, DiagCap: int) -> int:
|
||
"""Locate repl.vpj in cwd then the exe directory: 1=found (writes Out), 0=miss (writes Diag)."""
|
||
if Out is None or Diag is None: return 0
|
||
if Cap < 64 or DiagCap < 64: return 0
|
||
# Separate buffers per candidate so both paths appear in the diagnostic text.
|
||
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)
|
||
# Defensive: GetCurrentDirectoryA writes are undefined on overflow, so force a 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 失败)")
|
||
# 2) exe-dir/repl.vpj (GetModuleFileNameA(NULL) returns our own full path).
|
||
n_exe: ULONG = w32file.GetModuleFileNameA(None, c2, ULONG(Cap))
|
||
if n_exe > 0:
|
||
el: int = int(n_exe)
|
||
# Defensive: on truncation the return equals Cap and the string may lack a NUL.
|
||
c2[Cap - 1] = 0
|
||
if el < Cap:
|
||
c2[el] = 0
|
||
# Locate the last separator: c2[0..dlen) is the directory prefix (with separator);
|
||
# dlen=0 means no directory part, which the OS resolves against cwd.
|
||
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 失败)")
|
||
# Not found in either place: emit diagnostics (needs --project + both searched paths).
|
||
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
|
||
|
||
|
||
# ============================================================
|
||
# Session state: permanent buffers in PermPool; per-fragment strings in mb; t.Callable slots.
|
||
ReplFragFn: callable[[rawptr], int] = None
|
||
# Fragment sequence number: module-level and monotonic, so names/files are never reused.
|
||
ReplFragNo: int = 1
|
||
# Session temp dir %TEMP%\TransPyV_repl\<pid> (keeps user projects clean; pid isolates sessions).
|
||
ReplTmpDir: bytes = None
|
||
# Fragment symbol name __repl_frag_<n>.
|
||
ReplNameBuf: bytes = None
|
||
# Wrapped fragment source (for parsing/translation).
|
||
ReplSrcBuf: bytes = None
|
||
# On-disk path of the wrapped source (real file for error echo).
|
||
ReplSrcPath: bytes = None
|
||
# Block input buffer (session PermPool, _REPL_BLOCK_CAP): user lines joined by '\n',
|
||
# allocated once rather than per line.
|
||
ReplBlockBuf: bytes = None
|
||
# Current block buffer byte count (0 = empty; NUL is always at ReplBlockBuf[ReplBlockLen]).
|
||
ReplBlockLen: int = 0
|
||
# Continuation state: 1 = caching a multi-line block ("... " prompt); 0 = normal (">>> ").
|
||
ReplInBlock: int = 0
|
||
# Full module IR text (dump_ir OUTPUT_FULL output buffer).
|
||
ReplIrBuf: bytes = None
|
||
# IR probe for the export signature "llvm.func @<name>(%__repl_ctx__: !llvm.ptr) -> i32".
|
||
ReplNeedleBuf: bytes = None
|
||
# ctx session buffer (shared by host and fragment; byte view, passed as the fragment's first arg).
|
||
ReplCtxBuf: bytes = None
|
||
# Session context object (read by Gen: tables/caps/param name/perm pool/scratch entry).
|
||
ReplCtxObj: GenTypes.ReplCtx* = None
|
||
# Session variable table (PermPool, ReplCtxObj.Table).
|
||
ReplVarTable: GenTypes.ReplVarEntry* = None
|
||
# Scratch entry (PermPool): classifies bare-expression results only; no session slot or table entry.
|
||
ReplScratch: GenTypes.ReplVarEntry* = None
|
||
# Recovery-point jmp_buf (PermPool, lives for the session): run _setjmp's into it and
|
||
# VLogger.repl_abort longjmps back from any pipeline depth; re-registered per fragment.
|
||
ReplJmpBuf: rawptr = None
|
||
# Session table watermark (ReplCtxObj.Count) at fragment start; rolled back on failure to
|
||
# drop half-registered entries (repl_ensure_entry Count+1 but repl_seal errored).
|
||
ReplSavedVarCount: int = 0
|
||
|
||
# ==== includes session-init results (once per session; for summary and recovery branches) ====
|
||
# Registered includes module count (StubMerger SHA1 table entries).
|
||
ReplIncMods: int = 0
|
||
# includes scan time (ms).
|
||
ReplIncMs: int = 0
|
||
# Newly generated .vpi stubs.
|
||
ReplIncVpiNew: int = 0
|
||
# Reused existing .vpi stubs.
|
||
ReplIncVpiReuse: int = 0
|
||
# .vpi generation failures (read/parse/write).
|
||
ReplIncVpiFail: int = 0
|
||
|
||
# ==== Host export symbols (cross-module calls) ====
|
||
# Host exe import lib path (<exe>.dll.a; PermPool, probed once); fragment link lines use it.
|
||
ReplHostImplib: bytes = None
|
||
|
||
# ==== M3 session functions (cross-fragment functions) ====
|
||
# Lifted module-level def source (session PermPool; length cleared at each fragment start).
|
||
ReplLiftBuf: bytes = None
|
||
# Lifted session function name table (PermPool, _REPL_FN_NAME_MAX x _REPL_LIFT_MAX slots).
|
||
ReplLiftNames: bytes = None
|
||
# Number of session functions lifted by this fragment.
|
||
ReplLiftNameCount: int = 0
|
||
# Signature token scratch (PermPool, GenTypes.REPL_FUNC_PARAM_MAX bytes each; reused per function).
|
||
ReplSigTags: bytes = None
|
||
ReplSigBits: bytes = None
|
||
# Signature result struct (PermPool, reused; named to avoid the ReplSigOut class).
|
||
ReplSigResult: ReplSigOut* = None
|
||
# Session function table watermark (ReplCtxObj.FuncCount) at fragment start; restored on
|
||
# failure to drop half-registered entries (same idea as ReplSavedVarCount, see ⓪).
|
||
ReplSavedFuncCount: int = 0
|
||
# Diagnostic line/name display buffer (PermPool, _REPL_DIAG_CAP; %s cannot print non-NUL lines).
|
||
ReplLineShow: bytes = None
|
||
# def-header name scratch (PermPool, _REPL_FN_NAME_MAX).
|
||
ReplMsgName: bytes = None
|
||
# Signature render buffers x2 (PermPool, _REPL_SIG_TEXT_CAP): new vs existing entry.
|
||
ReplSigText: bytes = None
|
||
ReplSigText2: bytes = None
|
||
# Fragment body (wrapper) write position (_repl_split_body writes, _repl_build_frag_src_multi reads).
|
||
ReplWrapPos: int = 0
|
||
|
||
# ==== M4: session module sha1 (the cross-fragment unique key for type names) ====
|
||
# 16-hex-char string (copy of GenTypes.REPL_SESSION_SHA1), used as every fragment's ModuleSha1.
|
||
ReplSessionSha1: bytes = None
|
||
|
||
# ==== M4: prior fragment import libs (resolution source for cross-fragment method symbols) ====
|
||
# Fragment link lines add prior fragments' import libs so their class/method symbols resolve.
|
||
ReplFragImplibs: sizet* = None
|
||
ReplFragImplibCount: int = 0
|
||
|
||
# ==== M4: session baseline (CDefine/CTypedef survive across fragments) ====
|
||
# Replayed each fragment after clear_*; only successful fragments append; failures truncate.
|
||
ReplBaseCDefineNames: sizet* = None
|
||
ReplBaseCDefineValues: sizet* = None
|
||
ReplBaseCDefineKinds: sizet* = None # 8 bytes per entry: 0=int / 1=double / 2=str
|
||
ReplBaseCDefineCount: int = 0
|
||
ReplBaseCTypedefNames: sizet* = None
|
||
ReplBaseCTypedefTypeStrs: sizet* = None
|
||
ReplBaseCTypedefCount: int = 0
|
||
# Baseline watermark at fragment start (truncated back to it on failure/rollback).
|
||
ReplSavedCDefineCount: int = 0
|
||
ReplSavedCTypedefCount: int = 0
|
||
|
||
# ==== M4: includes class registration (done once at session init) ====
|
||
# Counts of modules whose .vpi injected successfully (classes in the session StructTable) / failed / missing stub.
|
||
ReplIncClsMods: int = 0
|
||
ReplIncClsFail: int = 0
|
||
ReplIncClsMissing: int = 0
|
||
|
||
|
||
# ============================================================
|
||
# Private helpers.
|
||
|
||
# Whether path is an existing regular file (GetFileAttributesA: attributes only, no buffer).
|
||
def _is_file(path: str) -> int:
|
||
"""1=exists and is not a directory; 0=missing, a directory, or bad args."""
|
||
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
|
||
|
||
|
||
# ============================================================
|
||
# Host export symbols: locate the host exe import lib so fragments resolve includes symbols.
|
||
def ReplFindHostImplib() -> int:
|
||
"""Locate the host import lib (<exe>.dll.a): 1=found (writes ReplHostImplib), 0=not found."""
|
||
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)
|
||
# Defensive: on truncation the write may lack a NUL (same as ReplFindProject).
|
||
if el >= _REPL_PATH_CAP: el = _REPL_PATH_CAP - 1
|
||
exe_buf[el] = 0
|
||
# Split dir/file: dlen = directory prefix length (incl. separator).
|
||
dlen: int = 0
|
||
si: int = el
|
||
while si > 0:
|
||
si -= 1
|
||
ch: char = exe_buf[si]
|
||
if ch == '/' or ch == '\\':
|
||
dlen = si + 1
|
||
break
|
||
# Candidate 1: exe dir (exe_buf already has the directory prefix).
|
||
viperlib.snprintf(cand, _REPL_PATH_CAP, "%s%s.dll.a", exe_buf, exe_buf + dlen)
|
||
if _is_file(cand) != 0:
|
||
ReplHostImplib = cand
|
||
return 1
|
||
# Candidate 2: Config.OutputDir (REPL uses repl.vpj, host uses 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
|
||
|
||
|
||
# Read one line byte-by-byte: works for console (ENABLE_LINE_INPUT) and pipes.
|
||
def _read_line(h: win32base.HANDLE, buf: bytes, cap: int) -> int:
|
||
"""Read one line from h into buf (NUL-terminated); returns bytes read, -1 on EOF/error."""
|
||
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
|
||
# Do not use &(buf + total): its IR does an i32 load (4 bytes) so '\n'/'\r'
|
||
# never match; the byte index buf[total] is an i8 load.
|
||
ch: char = buf[total]
|
||
if ch == 10: # '\n': end of line
|
||
break
|
||
if ch == 13: # '\r': CRLF, drop and re-read in place
|
||
continue
|
||
total += 1
|
||
buf[total] = 0
|
||
if eof != 0 and total == 0: return -1
|
||
return total
|
||
|
||
|
||
# Count top-level statements (Module.children).
|
||
def _count_top_stmts(tree: ast.AST*) -> int:
|
||
"""Return the number of top-level statements in a Module (0 when children is empty)."""
|
||
if tree is None or tree.children is None: return 0
|
||
stmts: list[ast.AST*]* = tree.children
|
||
return int(len(stmts))
|
||
|
||
|
||
# ============================================================
|
||
# Block continuation: a line ending in ':' opens a block, ended by a blank line or EOF.
|
||
|
||
# Line test: after trimming trailing whitespace it ends with ':', or (M3) starts with '@'.
|
||
def _line_starts_block(line: str, n: int) -> int:
|
||
"""1=line opens a block buffer (ends with ':' / starts with '@'); 0=no."""
|
||
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
|
||
|
||
|
||
# Append one line to the block buffer: join lines with '\n' and write a trailing NUL.
|
||
def _block_append(line: str, n: int) -> int:
|
||
"""Append an n-byte line to the session block buffer; 0=ok, 1=out of capacity (buffer unchanged)."""
|
||
global ReplBlockLen
|
||
if ReplBlockBuf is None or line is None or n <= 0: return 1
|
||
sep: int = 0
|
||
if ReplBlockLen > 0:
|
||
sep = 1 # separator '\n' between lines
|
||
if ReplBlockLen + sep + n >= _REPL_BLOCK_CAP: # plus 1 byte for the trailing NUL
|
||
return 1 # capacity checked before writing, so no rollback
|
||
if ReplBlockLen > 0:
|
||
ReplBlockBuf[ReplBlockLen] = '\n'
|
||
ReplBlockLen += 1
|
||
string.memcpy(ReplBlockBuf + ReplBlockLen, line, sizet(n))
|
||
ReplBlockLen += n
|
||
ReplBlockBuf[ReplBlockLen] = 0
|
||
return 0
|
||
|
||
|
||
# ctx header view (int64 interpretation): host reads/writes magic/kind/result_a...
|
||
def _ctx_head() -> GenTypes.ReplResult*:
|
||
"""ctx header view; None when the session is not initialized."""
|
||
if ReplCtxBuf is None: return None
|
||
return GenTypes.ReplResult*(ReplCtxBuf)
|
||
|
||
|
||
# ctx header view (double interpretation of result_a): for echoing float results.
|
||
def _ctx_float_head() -> ReplFloatView*:
|
||
"""Float view of the ctx header; None when the session is not initialized."""
|
||
if ReplCtxBuf is None: return None
|
||
return ReplFloatView*(ReplCtxBuf)
|
||
|
||
|
||
# Result echo: read the ctx result area by result_kind and print it.
|
||
def _repl_echo_result(frag_name: str, res: GenTypes.ReplResult*) -> int:
|
||
"""Echo this fragment's result by result_kind; 0=handled (including "no result")."""
|
||
if frag_name is None or res is None: return 1
|
||
k: int = res.Kind
|
||
if k == GenTypes.REPL_RES_NONE:
|
||
# Declaration-only fragment (e.g. `x: int`): no result for this line.
|
||
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: 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 means a non-string pointer (print address); result_a == 0
|
||
# means an inline struct value (no address; type name needs the cross-fragment table, 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: session sha1 / fragment import lib / session baseline / includes class registration.
|
||
|
||
# Deep-copy a string into pool (caller supplies the pool; None on invalid input/alloc failure).
|
||
def _repl_str_dup(pool: memhub.MemBuddy*, s: str) -> str:
|
||
"""Deep-copy s into pool; None on failure."""
|
||
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
|
||
|
||
|
||
# Build the session sha1 string (PermPool; used as ModuleSha1 during fragment translation).
|
||
def ReplInitSessionSha1() -> int:
|
||
"""0=success; non-zero=permanent pool or allocation failure."""
|
||
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
|
||
|
||
|
||
# ---- Session baseline (CDefine/CTypedef): find / restore / append / truncate ----
|
||
|
||
def _repl_base_cdefine_find(name: str) -> int:
|
||
"""Find a baseline CDefine entry index by name; -1 when not found."""
|
||
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:
|
||
"""Restore the session baseline (CDefine + CTypedef) into the just-cleared tables; 0=success."""
|
||
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:
|
||
# String CDefine: GenApi has no wrapper, so call gen directly (same shape as
|
||
# the clear_cdefine_constants wrapper).
|
||
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:
|
||
"""Append baseline-missing CDefine/CTypedef entries from the current tables into the session baseline; 0=success."""
|
||
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:
|
||
"""Truncate the baseline watermarks back to the fragment-start values (failed deltas must not enter)."""
|
||
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
|
||
|
||
|
||
# ---- Fragment import lib: record / append to the link command line ----
|
||
|
||
def ReplFragImplibRecord(implib_path: str) -> int:
|
||
"""Record a fragment import lib (PermPool deep copy); 1=recorded, 0=not (limit/allocation failure)."""
|
||
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
|
||
|
||
|
||
# Append all recorded fragment import libs to the command buffer (space-separated);
|
||
# returns the new position (-1 = out of capacity, which the caller must treat as an error).
|
||
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 class registration (once per session; per-module isolation, warnings only) ----
|
||
|
||
def ReplInitIncludesClasses(mb: memhub.MemBuddy*, sr: IncludesScanner.ScanResult*, vpi_dir: str) -> int:
|
||
"""Inject each includes module's .vpi into a temp module so its classes enter the session StructTable; 0=done."""
|
||
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
|
||
# Per-module state (8 bytes/slot, PermPool): 0 = pending/retry, 1 = done (ok or unusable).
|
||
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)
|
||
# Injection target: temporary LLVM module + translator (annotation context only);
|
||
# struct registration uses permanent allocation so entries/fields/types outlive fragments.
|
||
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: mlirlite.LLVMModule* = mlirlite.new_module(_perm_pool, _REPL_INC_MOD_NAME)
|
||
tr_inc: 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__()
|
||
# Give the temp translator real state (func/symbol tables, name buffers, Pool): VpiReader
|
||
# reads trans._funcs and trans.Pool/imported_modules while injecting, else it derefs None.
|
||
tr_inc._init_state(_perm_pool)
|
||
tr_inc.ModuleSha1 = ReplSessionSha1
|
||
tr_inc._declare_only = 0
|
||
# ReplCtx marks the translator as in a REPL session: annotations from .vpi may reference
|
||
# unregistered classes, so TypeGenResolve builds opaque placeholders instead of failing.
|
||
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
|
||
# Startup budget (one time check per module; GetTickCount64 is cheap): on overrun
|
||
# stop, leaving the remaining modules to the on-demand import path.
|
||
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: IncludesScanner.FileEntry* = 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:
|
||
# Missing stub (host never built / includes dir lacks this module): its classes
|
||
# are unavailable, but the session continues (import degrades the same way).
|
||
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
|
||
# Set trans.ModuleSha1 to the current stub's source module sha1 (like batch), so bare
|
||
# class names and parent lookups resolve correctly during injection.
|
||
tr_inc.ModuleSha1 = ent.Sha1
|
||
# Per-module recovery point: a fatal only discards this module's injection; the struct
|
||
# table dedups by name+sha1, and only mb.reset_to(vm) is done on the fatal path.
|
||
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)
|
||
# No module succeeded this pass, so further passes add nothing; stop early.
|
||
if pass_ok == 0: break
|
||
# Summary (aggregated warnings): per-module warnings were capped, so print full counts.
|
||
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
|
||
|
||
|
||
# ============================================================
|
||
# Session init: create %TEMP%\TransPyV_repl\<pid>\ and allocate permanent buffers + ctx.
|
||
def ReplInitSession() -> int:
|
||
"""Create the temp dir plus PermPool resident buffers and ctx session memory; 0=success."""
|
||
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
|
||
global 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
|
||
# 1) %TEMP% prefix (GetTempPathA returns a trailing backslash; 0 means failure).
|
||
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
|
||
# 2) Session dir <TEMP>TransPyV_repl/<pid> (pid isolates sessions; paths use '/').
|
||
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
|
||
# 3) Permanent buffers (live for the session, not re-allocated per fragment).
|
||
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
|
||
# 3') M3 permanent buffers: lifted source / name table / sig token scratch / render buffers.
|
||
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)
|
||
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 ReplSigText is None or ReplSigText2 is None: return 1
|
||
ReplLiftBuf[0] = 0
|
||
ReplLiftNames[0] = 0
|
||
ReplLiftNameCount = 0
|
||
ReplWrapPos = 0
|
||
ReplSavedFuncCount = 0
|
||
# 4) ctx session memory (fragment's first arg): 64B header + 64x16B var slots + 32x16B func slots, zeroed.
|
||
ReplCtxBuf = _perm_pool.alloc(_REPL_CTX_SIZE)
|
||
if ReplCtxBuf is None: return 1
|
||
string.memset(ReplCtxBuf, 0, _REPL_CTX_SIZE)
|
||
# 5) Session variable table + scratch entry (PermPool; names deep-copied by Gen into the same pool).
|
||
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)
|
||
# Scratch entry name (readable in error messages: "<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
|
||
# 5') Session function table (M3; PermPool, host writes, Gen reads only) - entry array zeroed.
|
||
func_entry_size: sizet = GenTypes.ReplFuncEntry.__sizeof__()
|
||
ReplFuncTable: 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)
|
||
# 6) Session context object (the Gen-side switch: 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
|
||
# Session function table (M3): slices survive across fragments, so table/names/tokens are in PermPool.
|
||
ReplCtxObj.FuncTable = ReplFuncTable
|
||
ReplCtxObj.FuncCount = 0
|
||
ReplCtxObj.FuncCap = GenTypes.REPL_FUNC_CAP
|
||
ReplSavedFuncCount = 0
|
||
# 7) ctx fixed header: magic/version written once by the host (fragments read-only, no validation).
|
||
head: 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
|
||
# 8) M4 permanent state: session sha1 + fragment import lib table + session baseline (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 session init: scan dir -> register "module -> SHA1" -> pre-generate .vpi stubs.
|
||
def _repl_gen_includes_vpi(mb: memhub.MemBuddy*, sr: IncludesScanner.ScanResult*, vpi_dir: str) -> int:
|
||
"""Pre-generate .vpi stubs for the includes scan; returns the count of new stubs (-1=bad args)."""
|
||
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: IncludesScanner.FileEntry* = 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
|
||
# Existing stub -> reuse.
|
||
vf: 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
|
||
# Read source file (1MB cap matching Phase1/BuildPipeline SRC_BUF_SIZE).
|
||
f: 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
|
||
# Parse (all AST allocations stay within this file's mb window).
|
||
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
|
||
# Pass the absolute ent.Path as rel_path: vpi fatal_error reads source lines via it.
|
||
pos: sizet = StubConverter._GeneratePyiFromAst(mb, tree, ent.Path, pyi_buf, _REPL_VPI_BUF_CAP)
|
||
if pos > 0:
|
||
pf: 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:
|
||
"""Session includes init: scan, register SHA1, pre-generate .vpi, register generic templates, then per-module classes; 0=success."""
|
||
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 dir matches the fragment side: prefer the project temp dir, else the session dir.
|
||
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
|
||
# 1) Scan includes (no reachability: REPL has no SourceDir/deps graph, so scan all).
|
||
t0: uint64 = win32base.GetTickCount64()
|
||
sr: 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
|
||
# 2) Register "module -> SHA1" (same StubMerger global table batch consumers use).
|
||
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
|
||
# 3) Pre-generate .vpi stubs (count failures, do not abort the session).
|
||
_repl_gen_includes_vpi(mb, sr, vpi_dir)
|
||
stdio.printf("includes: 已注册 %d 个模块(扫描耗时 %d ms);" "存根 新生成 %d / 复用 %d / 失败 %d\n", n, ReplIncMs, ReplIncVpiNew, ReplIncVpiReuse, ReplIncVpiFail)
|
||
# 3'') Register session generic templates for real: reuse includes sources to register
|
||
# generic class templates (list/dict/Vector...) so annotations like list[...] specialize.
|
||
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")
|
||
# 3') M4.2: enable stub-layout recovery so vpi injection verifies and registers includes
|
||
# class layouts/vtable indices; also reset the generic-owner table for re-registration.
|
||
StubMergerStore.ResetGenericOwners()
|
||
VpiReader.ReplStubLayoutBegin(vpi_dir)
|
||
# 4) M4 class registration: inject each module's .vpi so its classes (with fields) enter the
|
||
# session StructTable, a prerequisite for cross-module construction/method calls.
|
||
ReplInitIncludesClasses(mb, sr, vpi_dir)
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# M2/M3: fragment execution path (stmt -> IR -> llc -> -shared DLL -> call -> ctx echo).
|
||
|
||
# M3 top-level def lifting: lift a top-level def into the fragment module as an export def,
|
||
# so the host can GetProcAddress it and later fragments call it through a ctx slot.
|
||
|
||
# Truncate a line (possibly non-NUL-terminated) into the session buffer for %s printing.
|
||
def _repl_show_line(p: bytes, n: int) -> str:
|
||
"""Copy a line fragment into a session buffer as a NUL-terminated string (truncated to 118 bytes)."""
|
||
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
|
||
|
||
|
||
# Line length (up to '\n' or NUL).
|
||
def _repl_line_len(p: bytes) -> int:
|
||
n: int = 0
|
||
while p[n] != '\n' and p[n] != 0: n += 1
|
||
return n
|
||
|
||
|
||
# Count leading whitespace bytes.
|
||
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
|
||
|
||
|
||
# Whether [ind, n) starts with keyword kw (kw followed by whitespace).
|
||
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
|
||
|
||
|
||
# Append a byte span to dst (keeping the trailing NUL); returns new length, -1 = out of capacity.
|
||
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
|
||
|
||
|
||
# Append a NUL-terminated string to dst; returns new length, -1 = out of capacity.
|
||
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)))
|
||
|
||
|
||
# Record a lifted function name (reserved-prefix check + same-fragment dedup + limit); 0=ok, 1=reject.
|
||
def _repl_lift_add_name(name: str) -> int:
|
||
global ReplLiftNameCount
|
||
if name is None or ReplLiftNames is None: return 1
|
||
# Reserved prefix: the fragment entry name ("__repl_frag_<n>") marks the fragment entry
|
||
# scope in Gen (session writes only work there), so users may not use it.
|
||
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
|
||
|
||
|
||
# Parse and rewrite one top-level def header into ReplLiftBuf as an export def with an
|
||
# injected ctx param; name written to name_out. Returns 0=ok/1=rejected/2=buffer full.
|
||
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 # skip "def"
|
||
while p < n and (line[p] == ' ' or line[p] == '\t'): p += 1
|
||
# Name (identifier characters).
|
||
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 generic params [T]: skip balanced brackets (translation maps T to 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
|
||
# Matching ')'.
|
||
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
|
||
# ':' must be on the same line (as must the return annotation).
|
||
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
|
||
# Whether the parameter list is empty.
|
||
empty: int = 1
|
||
q3: int = lp + 1
|
||
while q3 < rp:
|
||
if line[q3] != ' ' and line[q3] != '\t':
|
||
empty = 0
|
||
break
|
||
q3 += 1
|
||
# Reject a user-supplied __repl_ctx__ param (would duplicate the injected first param).
|
||
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
|
||
# Emit: "export " + original header [0,lp] + injection + original header [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
|
||
|
||
|
||
# Split the fragment body: lifted blocks -> ReplLiftBuf (export def, unindented); other lines
|
||
# -> ReplSrcBuf (4 spaces each); records lifted names. 0=ok/1=rejected/2=buffer full.
|
||
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 = inside a lifted block
|
||
need_header: int = 0 # saw a decorator line, no def header yet
|
||
in_body: int = 0 # past the def header (indented lines are the body; check 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:
|
||
# Blank line: stays in the lift block (blank body lines are indentation-neutral).
|
||
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
|
||
# Dedented non-blank line ends the lift block; re-evaluate this line (do not advance i).
|
||
in_lift = 0
|
||
need_header = 0
|
||
in_body = 0
|
||
continue
|
||
if blank != 0 or ind > 0:
|
||
# Into the fragment body (4 spaces per line, same as the block path).
|
||
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
|
||
# Dedented non-blank line.
|
||
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
|
||
|
||
|
||
# Assemble a multi-line block wrapper (lift + wrap): 0=ok/1=rejected/2=buffer full.
|
||
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
|
||
# Lift buffer/name table watermarks are cleared per fragment by _build_frag_src; here we only
|
||
# assemble: emit the fragment function header, then body lines each get 4 spaces.
|
||
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
|
||
# Append lifted module-level defs after the fragment entry function (module scope, order-free).
|
||
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 session function table: host owns it (writes table + ctx slots); Gen reads only.
|
||
|
||
# Find a session function table entry by name (host side; same as Gen's repl_find_func_entry).
|
||
def _repl_func_entry_find(name: str) -> GenTypes.ReplFuncEntry*:
|
||
"""1=entry found; None=not registered or table not built."""
|
||
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: GenTypes.ReplFuncEntry* = 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
|
||
|
||
|
||
# Name of the idx-th lifted function (NUL-terminated); None if out of range.
|
||
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
|
||
|
||
|
||
# Deep-copy into PermPool (session table entry names etc. that outlive a fragment).
|
||
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
|
||
|
||
|
||
# Render signature result -> human-readable "(P1, P2) -> R" (into a PermPool buffer).
|
||
def _repl_sig_to_text(tag: int, bits: int, pc: int) -> str:
|
||
"""Render ReplSigResult + ReplSigTags/Bits as "(P1, P2) -> R" (PermPool buffer)."""
|
||
if ReplSigText is None: return "(?)"
|
||
pos: int = _repl_cat_str(ReplSigText, _REPL_SIG_TEXT_CAP, 0, "(")
|
||
for i in range(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])))
|
||
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
|
||
|
||
|
||
# Pre-registration check (before llc/load): per lifted function verify signature, EH params,
|
||
# type support, ctx first param, param count, redefinition, capacity - else abort the fragment.
|
||
def _repl_precheck_session_funcs(mod: mlirlite.LLVMModule*) -> 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_model_func_sig(mod, nm, ReplSigResult)
|
||
if rc == 1:
|
||
stdio.printf("M3: 定义片段模型里找不到函数 '%s'(提升未生效?)\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: GenTypes.ReplFuncEntry* = _repl_func_entry_find(nm)
|
||
if old is None:
|
||
new_cnt += 1
|
||
continue
|
||
# Redefinition: only a same-signature overwrite is allowed (a different signature would
|
||
# make earlier fragments call new code with the old ABI -> silent UB). Same sig: update addr.
|
||
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:
|
||
for pi in range(old.ParamCount):
|
||
if int(old.ParamTags[pi]) != int(ReplSigTags[pi]) or int(old.ParamBits[pi]) != int(ReplSigBits[pi]):
|
||
sig_same = 0
|
||
break
|
||
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
|
||
|
||
|
||
# Render an existing entry's signature (for redefinition-conflict diagnostics; into ReplSigText2).
|
||
def _repl_entry_sig_text(e: GenTypes.ReplFuncEntry*) -> str:
|
||
if e is None or ReplSigText2 is None: return "(?)"
|
||
pos: int = _repl_cat_str(ReplSigText2, _REPL_SIG_TEXT_CAP, 0, "(")
|
||
for i in range(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])))
|
||
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
|
||
|
||
|
||
# Register after a successful DLL load: GetProcAddress(bare name) -> write ctx slot ->
|
||
# append/update the table. 0=ok; 1=failed (caller rolls back by ReplSavedFuncCount and aborts).
|
||
def _repl_register_session_funcs(h_lib: win32base.HANDLE, frag_no: int, mod: mlirlite.LLVMModule*) -> 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
|
||
# Exported symbol is the bare name: `export def` sets has_export without a SHA1 prefix,
|
||
# and -shared without a DEF file exports all symbols automatically.
|
||
addr: VOIDPTR = win32base.GetProcAddress(h_lib, nm)
|
||
if addr is None:
|
||
stdio.printf("M3: 会话函数 '%s' 未出现在片段 DLL 的导出表" "(GetProcAddress 返回空;本片段不执行)\n", nm)
|
||
return 1
|
||
ent: GenTypes.ReplFuncEntry* = _repl_func_entry_find(nm)
|
||
if ent is None:
|
||
# New entry (signature already validated; re-parse to recover the scratch tokens).
|
||
rc: int = _repl_model_func_sig(mod, 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 = 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:
|
||
# Redefinition (same signature): only swap address and fragment number; slot offset unchanged.
|
||
ent.FragNo = frag_no
|
||
# Write the ctx slot (low 8 bytes = address, high 8 bytes reserved 0).
|
||
sv: ReplFuncSlotView* = 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 session function signature parsing: read the definition-site ABI from the model.
|
||
|
||
|
||
# token -> display name (for logs/diagnostics only).
|
||
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 "?"
|
||
|
||
|
||
# LLVMType* -> packed token (tag*1000 + bits); 0 = unsupported. Supported set matches the
|
||
# old string version: i1/i8/i16/i32/i64, float/double, Ptr(Int8) (str/bytes/rawptr).
|
||
def _repl_ty_ptr_token(ty: mlirlite.LLVMType*) -> int:
|
||
if ty is None: return 0
|
||
match ty:
|
||
case mlirlite.LLVMType.Int(bits):
|
||
if bits == 1: return GenTypes.REPL_TY_INT * 1000 + 1
|
||
if bits == 8: return GenTypes.REPL_TY_INT * 1000 + 8
|
||
if bits == 16: return GenTypes.REPL_TY_INT * 1000 + 16
|
||
if bits == 32: return GenTypes.REPL_TY_INT * 1000 + 32
|
||
if bits == 64: return GenTypes.REPL_TY_INT * 1000 + 64
|
||
return 0
|
||
case mlirlite.LLVMType.Float(bits):
|
||
if bits == 32: return GenTypes.REPL_TY_FLOAT * 1000 + 32
|
||
if bits == 64: return GenTypes.REPL_TY_FLOAT * 1000 + 64
|
||
return 0
|
||
case mlirlite.LLVMType.Ptr(pointee):
|
||
match pointee:
|
||
case mlirlite.LLVMType.Int(bits):
|
||
if bits == 8: return GenTypes.REPL_TY_STR * 1000 + 8
|
||
return 0
|
||
case _:
|
||
return 0
|
||
case _:
|
||
return 0
|
||
|
||
|
||
# Diagnostic: LLVMType* -> readable name (for unsupported-signature errors); none if unnamed.
|
||
def _repl_ty_name(ty: mlirlite.LLVMType*) -> str:
|
||
if ty is None: return "(none)"
|
||
nm: str = mlirlite.get_struct_name(ty)
|
||
if nm is not None: return nm
|
||
return "(unsupported)"
|
||
|
||
|
||
# Take a session function signature from the translation model (not by parsing IR define lines).
|
||
# Codes: 0=ok; 1=not found; 2=unsupported type; 3=EH params; 4=first param not ctx; 5=too many params.
|
||
def _repl_model_func_sig(mod: mlirlite.LLVMModule*, name: str, out: ReplSigOut*) -> int:
|
||
if name is None or out is None or mod 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
|
||
fn: mlirlite.Function* = GenApi.find_func_in_module(mod, name)
|
||
if fn is None: return 1
|
||
# 1) Return type.
|
||
rt_ty: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(fn)
|
||
is_void: int = 0
|
||
if rt_ty is None: is_void = 1
|
||
else:
|
||
match rt_ty:
|
||
case mlirlite.LLVMType.Void():
|
||
is_void = 1
|
||
case _:
|
||
is_void = 0
|
||
if is_void != 0:
|
||
out.RetTag = GenTypes.REPL_TY_VOID
|
||
out.RetBits = 0
|
||
else:
|
||
rt: int = _repl_ty_ptr_token(rt_ty)
|
||
if rt == 0:
|
||
out.Bad = 1
|
||
out.BadType = _repl_ty_name(rt_ty)
|
||
return 2
|
||
out.RetTag = rt // 1000
|
||
out.RetBits = rt % 1000
|
||
# 2) Params: the first is ctx (Ptr(Int8) named __repl_ctx__), the rest are business params.
|
||
pi: int = 0
|
||
p_cur: mlirlite.Param* = mlirlite.function_get_param_head(fn)
|
||
while p_cur is not None:
|
||
p_ty: mlirlite.LLVMType* = mlirlite.param_get_ty(p_cur)
|
||
p_nm: str = mlirlite.param_get_name(p_cur)
|
||
# Param names may or may not carry a leading '%' (the printer adds it on demand); strip it.
|
||
bare_nm: str = p_nm
|
||
if bare_nm is not None and bare_nm[0] == '%': bare_nm = bare_nm + 1
|
||
if pi == 0:
|
||
if _repl_ty_ptr_token(p_ty) != GenTypes.REPL_TY_STR * 1000 + 8: return 4
|
||
if bare_nm is None or string.strcmp(bare_nm, GenTypes.REPL_CTX_PARAM) != 0: return 4
|
||
else:
|
||
# EH param (raise semantics).
|
||
if bare_nm is not None:
|
||
if string.strcmp(bare_nm, "__eh_msg_out__") == 0 or string.strcmp(bare_nm, "__eh_code_out__") == 0:
|
||
out.EHTag = 1
|
||
return 3
|
||
if out.ParamCount >= GenTypes.REPL_FUNC_PARAM_MAX: return 5
|
||
tk: int = _repl_ty_ptr_token(p_ty)
|
||
if tk == 0:
|
||
out.Bad = 1
|
||
out.BadType = _repl_ty_name(p_ty)
|
||
return 2
|
||
ReplSigTags[out.ParamCount] = tk // 1000
|
||
ReplSigBits[out.ParamCount] = tk % 1000
|
||
out.ParamCount = out.ParamCount + 1
|
||
pi += 1
|
||
p_cur = mlirlite.param_get_next(p_cur)
|
||
return 0
|
||
|
||
|
||
|
||
# ============================================================
|
||
# Assemble the wrapped fragment source into ReplSrcBuf (the fragment body is always a function body).
|
||
def _build_frag_src(name: str, body: str) -> int:
|
||
"""Assemble the wrapped fragment source: single line uses the template verbatim; multi-line lifts and indents by 4."""
|
||
global ReplLiftNameCount
|
||
if name is None or body is None: return 2
|
||
# Clear the previous fragment's lifting state before assembling (single-line path lifts
|
||
# nothing; a stale name count would make the precheck wrongly reject this fragment).
|
||
ReplLiftNameCount = 0
|
||
ReplLiftBuf[0] = 0
|
||
ReplLiftNames[0] = 0
|
||
if string.strchr(body, '\n') is None:
|
||
# Single line: existing template (ctx param bytes => i8*; export => no SHA1 prefix).
|
||
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
|
||
# Multi-line: lift top-level defs + assemble line by line (M3; rules in _repl_split_body).
|
||
return _repl_build_frag_src_multi(name, body)
|
||
|
||
|
||
# Execute one fragment source (single-line and block share this entry): parse the unwrapped
|
||
# source first (readable diagnostics), then hand it to ReplExecFragment to wrap/translate/run.
|
||
def _exec_repl_src(mb: memhub.MemBuddy*, src: str) -> int:
|
||
"""Parse and execute one fragment source; 0=handled (diagnostics already printed)."""
|
||
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 execution path: whole block/line runs as one fragment function body; failures only
|
||
# print the reason (or longjmp back to the recovery point), never kill the process.
|
||
rc_frag: int = ReplExecFragment(mb, src)
|
||
if rc_frag != 0:
|
||
# Non-longjmp failures already printed their reason inside the fragment; here only roll
|
||
# back the session table watermarks so the tables and ctx stay consistent.
|
||
if ReplCtxObj is not None:
|
||
ReplCtxObj.Count = ReplSavedVarCount
|
||
ReplCtxObj.FuncCount = ReplSavedFuncCount
|
||
# M4: roll back the session baseline (CDefine/CTypedef) watermarks; a no-op on the normal
|
||
# path, it keeps the "failed fragment never changes the baseline" invariant.
|
||
ReplBaselineTruncate(ReplSavedCDefineCount, ReplSavedCTypedefCount)
|
||
return 0
|
||
|
||
|
||
# Execute one fragment (any top-level statement: expr/assign/ann-assign/aug-assign/...).
|
||
# Returns 0=ok (echoed by result_kind); non-zero=failed. Fatal errors longjmp to run's point.
|
||
def ReplExecFragment(mb: memhub.MemBuddy*, src: str) -> int:
|
||
"""Fragment -> DLL -> call -> ctx echo; 0=success."""
|
||
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
|
||
|
||
# 0) Re-arm the recovery point and snapshot session table watermarks so a failed fragment's
|
||
# half-registered entries can be rolled back; set_repl_jmpbuf is idempotent.
|
||
VLogger.set_repl_jmpbuf(ReplJmpBuf)
|
||
ReplSavedVarCount = ReplCtxObj.Count
|
||
ReplSavedFuncCount = ReplCtxObj.FuncCount
|
||
# M4: session baseline watermarks (CDefine/CTypedef); a failed fragment must not add
|
||
# increments, same idea as the var/func watermarks (only success calls ReplBaselineCapture).
|
||
ReplSavedCDefineCount = ReplBaseCDefineCount
|
||
ReplSavedCTypedefCount = ReplBaseCTypedefCount
|
||
|
||
# 1) Take the number and increment it immediately: names/files stay unique even after a failure.
|
||
n: int = ReplFragNo
|
||
ReplFragNo = n + 1
|
||
viperlib.snprintf(ReplNameBuf, _REPL_NAME_CAP, "__repl_frag_%d", n)
|
||
|
||
# 2) Strip leading whitespace (the template indents the body; stacking would error). For
|
||
# blocks only the first line is affected; step 3 indents the rest uniformly.
|
||
body: str = src
|
||
if body is not None:
|
||
while body[0] == ' ' or body[0] == '\t': body = body + 1
|
||
|
||
# 3) Wrap: ctx param (bytes => i8*); export => no SHA1 prefix. Single line uses the existing
|
||
# template; multi-line lifts top-level defs. Returns 0=ok/1=rejected/2=buffer full.
|
||
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:
|
||
# Lift rule rejection (async def / raise / duplicate / non-single-line signature / own
|
||
# ctx param...): the whole fragment is skipped (no partial side effects), session continues.
|
||
return 1
|
||
|
||
# 4) Write the wrapped source to disk (used only for error echo: fatal_error reads this file).
|
||
viperlib.snprintf(ReplSrcPath, _REPL_PATH_CAP, "%s/%s.vp", ReplTmpDir, ReplNameBuf)
|
||
sf: fileio.File* = fileio.File(ReplSrcPath, fileio.MODE.W)
|
||
if not sf.closed:
|
||
sf.write(ReplSrcBuf, string.strlen(ReplSrcBuf))
|
||
sf.close()
|
||
|
||
# 5) Reset per-fragment module-level translation state (mirrors Phase1Main/BuildPipeline);
|
||
# temp_dir is used for .vpi injection: prefer the project temp dir, else the session dir.
|
||
vpi_dir: str = Config.TempDir
|
||
if vpi_dir is None: vpi_dir = ReplTmpDir
|
||
GenApi.set_temp_dir(vpi_dir)
|
||
# CallGenBase.TempDir/TempDirLen (cross-module .vpi/.text.ll lookup, CExport checks) is only
|
||
# set by SetTempDir; set it here once per fragment, same value as 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()
|
||
# Visibility uses loose mode (strict=0, all registered structs visible): fragment types come
|
||
# from the session, so strict isolation would hide them and fail annotation resolution.
|
||
GenApi.reset_visible_structs(mb, 0)
|
||
# Struct registration uses permanent allocation (like Phase1b/Phase2 StdlibAlloc=1): table,
|
||
# entry names and field types go into PermPool, else later fragments read freed memory.
|
||
GenApi.set_stdlib_alloc(1)
|
||
# M4: replay the CDefine/CTypedef session baseline; the clear_* above emptied the tables, and
|
||
# this fragment's additions are captured on success by ReplBaselineCapture.
|
||
ReplBaselineRestore(mb)
|
||
|
||
# 6) Parse the wrapped source. Fragments containing "class " use the permanent pool (generic
|
||
# templates store the ClassDef node for later specialization); others use mb as before.
|
||
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
|
||
|
||
# 7) Translate to full module IR. ModuleSha1 = session sha1 (types are "<sha1>.<Type>");
|
||
# ReplCtx enables REPL mode and is cleared after translation.
|
||
tr: 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, mlirlite.OUTPUT_FULL)
|
||
|
||
# 7') M4: if this fragment defines session-prefixed symbols ("<sha1>."), produce and record
|
||
# its import lib so later fragments can resolve them at link time.
|
||
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
|
||
|
||
# 8) Export-symbol probe: the signature must be "(%__repl_ctx__: !llvm.ptr) -> i32"; otherwise
|
||
# the wrapper/calling convention is wrong (or raise added EH params), so abort.
|
||
viperlib.snprintf(ReplNeedleBuf, _REPL_NEEDLE_CAP, "llvm.func @%s(%%%s: !llvm.ptr) -> i32", ReplNameBuf, GenTypes.REPL_CTX_PARAM)
|
||
if string.strstr(ReplIrBuf, ReplNeedleBuf) is None:
|
||
stdio.printf("片段 %s: 未生成约定的导出函数(期望 `llvm.func @%s(%%__repl_ctx__: !llvm.ptr) -> i32`);" "含 raise 的片段会追加异常出参,M2 不支持\n", ReplNameBuf, ReplNameBuf)
|
||
return 1
|
||
|
||
# 8') Session function signature check (M3), before llc/load: any failure skips the whole
|
||
# fragment; returns 0 immediately when no functions were lifted.
|
||
if _repl_precheck_session_funcs(tr.Module) != 0: return 1
|
||
|
||
# 9) Write .ll (reusing BuildPipeline: sliced path + 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
|
||
|
||
# 10) llc -> .obj (reusing existing machinery; VLogger.error longjmps on failure).
|
||
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:
|
||
# This branch is only reached on compile_ll_to_obj's internal allocation failure (real llc
|
||
# failures longjmp), so print a note instead of silently returning to the prompt.
|
||
stdio.printf("片段 %s: llc 编译 .obj 失败(命令/路径缓冲分配失败,见上方诊断)\n", ReplNameBuf)
|
||
return 1
|
||
|
||
# 11) clang++ -shared -> .dll (command shape matches BuildPipeline.link_obj_to_exe; process
|
||
# launch reuses 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
|
||
# Cross-module symbols come from the host exe, so add the host import lib (if any) to the link
|
||
# line; M4 also adds this fragment's --out-implib and prior fragments' import libs.
|
||
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
|
||
|
||
# 11') M4: record this fragment's import lib (if it defined session-prefixed symbols) so later
|
||
# fragments resolve its class/method symbols; recording failure only warns.
|
||
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)
|
||
|
||
# 12) Dynamic load + address lookup (LoadLibraryA/GetProcAddress).
|
||
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
|
||
|
||
# 12') Session function registration (M3), before calling the entry so a failure skips this
|
||
# fragment's statements; in-fragment calls to the same name still use direct calls.
|
||
if _repl_register_session_funcs(h_lib, n, tr.Module) != 0:
|
||
if ReplCtxObj is not None: ReplCtxObj.FuncCount = ReplSavedFuncCount
|
||
return 1
|
||
|
||
# 13) Reset the result area (kind=none) and refresh var_count/last_frag; call indirectly (the
|
||
# module stays resident, so no FreeLibrary).
|
||
head: 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
|
||
# The argument type is the constructed call signature's param type (bytes => i8*).
|
||
r_rc: int = ReplFragFn(ReplCtxBuf)
|
||
|
||
# 14) Read back from the ctx result area and echo (the fragment return is only a status code).
|
||
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)
|
||
# 14') M4: on success, merge this fragment's new CDefine/CTypedef entries into the session
|
||
# baseline; failure paths skip this, so the baseline never holds a failed fragment's leftovers.
|
||
ReplBaselineCapture()
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# run - the M2/M3 REPL main loop.
|
||
def run(mb: memhub.MemBuddy*) -> int:
|
||
"""M2/M3 REPL: read a line/block, parse, compile to a DLL and call it (session vars via ctx slots), then echo; errors never kill the process."""
|
||
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
|
||
|
||
# Parse tables (keywords/operators) go in PermPool: they are ast module-level globals built
|
||
# once; on mb they would be freed by each line's reset_to(mark) and crash the next parse.
|
||
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
|
||
# Session-level registration: ReplJmpBuf is re-registered per fragment; the VLogger side is set
|
||
# once and cleared only on run exit (that is what covers the whole pipeline).
|
||
ReplJmpBuf = jb
|
||
ReplSavedVarCount = 0
|
||
ReplSavedFuncCount = 0
|
||
VLogger.set_repl_jmpbuf(jb)
|
||
# jit_flag is set True by main before calling run (see main.vp --repl); run resets it to False.
|
||
lg: VLogger.Logger = VLogger.logger
|
||
|
||
# Arm the recovery point before use: session init may longjmp via VLogger.error, and an
|
||
# un-armed jmp_buf would be UB; init is the only stage that must succeed, so exit on failure.
|
||
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
|
||
|
||
# Session init: temp dir + permanent buffers + ctx (all PermPool; see 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)
|
||
|
||
# === Session-level includes init (once; all failures only degrade includes classes) ===
|
||
# The recovery point must land in this frame, so a fail-fast longjmp continues into the loop.
|
||
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:
|
||
# Scan results/registry live in stdlib, so mb allocations can be reclaimed after this call,
|
||
# which also performs includes class registration (per-module _setjmp).
|
||
ReplInitIncludes(mb)
|
||
mb.reset_to(inc_mark)
|
||
# Re-arm the recovery point on run's own frame (not a helper: _setjmp's target belongs to the
|
||
# calling frame); this puts the finish step and read loop back under this frame's point.
|
||
rc_arm: int = _setjmp(jb, None)
|
||
if rc_arm != 0:
|
||
stdio.printf("REPL: includes 类布局收尾中断(诊断见上):布局注册可能不完整," "会话继续\n")
|
||
else:
|
||
# Finish: converge classes whose parents became ready in later passes + per-class warnings.
|
||
# Done after re-arming so any longjmp lands in this frame (the dead-frame issue above).
|
||
VpiReader.ReplStubLayoutFinish()
|
||
# Disabling stub-layout recovery must be unconditional: a non-zero flag would make fragment
|
||
# translation take the REPL stub path (this function only sets a flag, no failure path).
|
||
VpiReader.ReplStubLayoutEnd()
|
||
|
||
# === Host export symbols: locate the host exe import lib ===
|
||
# If found, fragment link lines include it; otherwise warn and continue (calls may fail to link).
|
||
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)
|
||
|
||
# === Read loop: single-line fragments + block continuation ===
|
||
# All init failures were absorbed, so the prompt is guaranteed to appear from here on.
|
||
stdio.printf("REPL: 初始化完成,进入读行循环\n")
|
||
stdio.fflush(None)
|
||
# Block state is zeroed after every block end/failure/EOF/overflow, so a stale block cannot
|
||
# pollute the next line (the next append overwrites from index 0).
|
||
ReplInBlock = 0
|
||
ReplBlockLen = 0
|
||
while 1:
|
||
# Prompt: "... " while in a block, otherwise ">>> " (same print point and flush timing).
|
||
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: treat as a terminating line, then exit
|
||
eof_hit = 1
|
||
n = 0
|
||
# Per line: snapshot watermarks, then _setjmp (arming this fragment's recovery point for the
|
||
# whole pipeline); any longjmp reclaims mark and continues.
|
||
mark: sizet = mb.mark()
|
||
rc: int = _setjmp(jb, None)
|
||
if rc != 0:
|
||
# longjmp return: diagnostics were already printed; clear block state, roll back session
|
||
# watermarks and reclaim this line's allocations, then print the next prompt.
|
||
ReplInBlock = 0
|
||
ReplBlockLen = 0
|
||
if ReplCtxObj is not None:
|
||
ReplCtxObj.Count = ReplSavedVarCount
|
||
ReplCtxObj.FuncCount = ReplSavedFuncCount
|
||
# M4: roll back the session baseline (CDefine/CTypedef) watermarks (same idea as above).
|
||
ReplBaselineTruncate(ReplSavedCDefineCount, ReplSavedCTypedefCount)
|
||
mb.reset_to(mark)
|
||
if eof_hit != 0: break
|
||
continue
|
||
if eof_hit != 0:
|
||
# EOF: while in a block with buffered content, execute the cached block first (like a
|
||
# blank line); otherwise take the existing exit path (print a newline then exit).
|
||
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:
|
||
# Blank line ends continuation: run the whole block once; block errors only print.
|
||
if ReplBlockLen > 0: _exec_repl_src(mb, ReplBlockBuf)
|
||
ReplInBlock = 0
|
||
ReplBlockLen = 0
|
||
mb.reset_to(mark)
|
||
continue
|
||
# In continuation: every line is buffered; no state flips on ':' and no indent analysis.
|
||
if _block_append(line, n) != 0:
|
||
stdio.printf("REPL: 块输入超过块缓冲上界 %d 字节:本块已丢弃," "回到 >>> 继续(可减少块体积或分块执行)\n", _REPL_BLOCK_CAP)
|
||
ReplInBlock = 0
|
||
ReplBlockLen = 0
|
||
mb.reset_to(mark)
|
||
continue
|
||
# ==== Existing single-line path (unchanged when not in continuation) ====
|
||
if n == 0: # blank line: skip
|
||
mb.reset_to(mark)
|
||
continue
|
||
if _line_starts_block(line, n) != 0:
|
||
# Trailing ':' enters continuation: buffer this line without executing it. The first line
|
||
# cannot overflow (line cap < block cap), so append failure falls through below.
|
||
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)
|
||
# Clear the recovery point only at session end (never mid-session, so the pipeline stays protected).
|
||
ReplJmpBuf = None
|
||
VLogger.clear_repl_jmpbuf()
|
||
if lg is not None: lg.set_jit_flag(False)
|
||
return 0
|