refactor(diag): 编译期 traceback 抽为 Diag 模块,单条诊断携带完整调用链
- 新增 Diag.DiagEntry 与 new_entry/begin/set_current/add_frame/emit/free_entry,方向由 DIAG_DIR_* 决定 - TypeGenErr.fatal_error 改走 DiagEntry:填调用链 + 当前帧,emit(UP) 后 free + finish - 顶层函数体/嵌套函数体/方法体三处翻译入口各 push/pop 一帧(def 位置),报错可回溯所在函数
This commit is contained in:
1 parent
c791bd5e46
commit
6769cf0335
5 files changed
+336
-112
No files matched your search
@@ -0,0 +1,281 @@
|
||||
import t, c
|
||||
from stdint import *
|
||||
import string
|
||||
import stdio
|
||||
import stdlib
|
||||
import w32.fileio as fileio
|
||||
import lib.core.VLogger as VLogger
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Diag - 编译期诊断(traceback 系统):源码行读取 + 统一打印 + 终止策略
|
||||
# ============================================================
|
||||
# 抽出来的原因:格式化 / 源码行读取 / 终止策略原先都散在 TypeGenErr 里(连同它的
|
||||
# TraceCount 调用栈遍历),导致"多错误栈"(错误收集 + 每单元去重 + 上限)无处安放。
|
||||
# 现在 TypeGenErr / GenApi 的 `fatal_error` / `dump_parse_traceback` 全部**委托**到这里;
|
||||
# 保留在 TypeGenErr 里的只有两件"它才知道"的东西:
|
||||
# ① 调用栈帧的遍历(`TypeGenBase._get_trace_frame`);
|
||||
# ② 当前文件名(`TypeGenResolve.get_current_file`)。
|
||||
#
|
||||
# **行为与抽离前逐字一致**:报文顺序、缩进、caret 规则、fflush、repl_abort 全不变。
|
||||
# 诊断模型:**单条 + 立即终止**(不收集多条错误)—— 但单条自带**整条调用链**(DiagEntry),
|
||||
# 由调用方(TypeGenErr)填好 entry 后 emit,方向由 DIAG_DIR_* 决定。所有打印都拆成独立的
|
||||
# emit_*,便于在不改调用点的前提下调整格式。
|
||||
# ============================================================
|
||||
|
||||
|
||||
# ---- traceback 打印方向 ----
|
||||
define DIAG_DIR_UP = 0 # 外→内:最外层触发点在上,错误点最后(Python "most recent call last")
|
||||
define DIAG_DIR_DOWN = 1 # 内→外:错误点最先,越往外越下
|
||||
define DIAG_MAX_FRAMES = 32 # 调用链上限(与 TypeGenMixin.TRACE_MAX 对齐)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# DiagEntry - 单条编译期诊断(= 一次 traceback 要打印的全部内容)
|
||||
# ============================================================
|
||||
# **不是**错误收集容器:任何时刻只处理"出错的那一条"(打印完即终止),
|
||||
# 但这一条自带**整条调用链** —— 编译器逐层下潜进各源码上下文
|
||||
# (import 触发依赖加载 / 泛型特化 / 函数体翻译)时的触发点,
|
||||
# 用于"从错误发生点向外展示触发过程"。
|
||||
# 所有字符串都用 stdlib 深拷贝:编译期池会回绕,不能借用池内指针。
|
||||
class DiagEntry:
|
||||
File: str # 当前帧(错误点)文件
|
||||
Line: int # 当前帧行号
|
||||
ColStart: int # caret 起始列(1-based;0 = 不画)
|
||||
ColEnd: int # caret 结束列
|
||||
Category: str # LifetimeError / SyntaxError / TypeError ...
|
||||
Msg: str # 消息
|
||||
Hint: str # 可选提示(可 None)
|
||||
FrameCount: int # 调用链帧数
|
||||
FrameFiles: sizet* # 调用链每帧文件名(深拷贝)
|
||||
FrameLines: sizet* # 调用链每帧行号(与 FrameFiles 同容量)
|
||||
Cap: int # 内部:FrameFiles/FrameLines 容量(不参与打印)
|
||||
|
||||
|
||||
# dup_str - 深拷贝到 stdlib 堆(诊断需跨池回绕点存活)
|
||||
def dup_str(s: str) -> str:
|
||||
if s is None: return None
|
||||
n: sizet = string.strlen(s)
|
||||
b: str = str(stdlib.malloc(n + 1))
|
||||
if b is None: return None
|
||||
string.strncpy(b, s, n)
|
||||
b[n] = '\0'
|
||||
return b
|
||||
|
||||
|
||||
# new_entry - 分配一条诊断(调用方持有;打印完用 free_entry 释放)
|
||||
def new_entry(max_frames: int) -> DiagEntry*:
|
||||
ent: DiagEntry* = (DiagEntry*)(stdlib.malloc(DiagEntry.__sizeof__()))
|
||||
if ent is None: return None
|
||||
if max_frames <= 0: max_frames = 1
|
||||
ent.File = None
|
||||
ent.Line = 0
|
||||
ent.ColStart = 0
|
||||
ent.ColEnd = 0
|
||||
ent.Category = None
|
||||
ent.Msg = None
|
||||
ent.Hint = None
|
||||
ent.FrameCount = 0
|
||||
ent.Cap = max_frames
|
||||
ent.FrameFiles = (sizet*)(stdlib.malloc(sizet(max_frames) * 8))
|
||||
ent.FrameLines = (sizet*)(stdlib.malloc(sizet(max_frames) * 8))
|
||||
return ent
|
||||
|
||||
|
||||
# begin - 初始化一条诊断(随后用 add_frame/set_current 填调用链与当前位置)
|
||||
def begin(ent: DiagEntry*, category: str, msg: str, hint: str = None) -> int:
|
||||
if ent is None: return 0
|
||||
ent.Category = dup_str(category)
|
||||
ent.Msg = dup_str(msg)
|
||||
ent.Hint = dup_str(hint)
|
||||
ent.File = None
|
||||
ent.Line = 0
|
||||
ent.ColStart = 0
|
||||
ent.ColEnd = 0
|
||||
ent.FrameCount = 0
|
||||
return 0
|
||||
|
||||
|
||||
# set_current - 设置当前帧(错误点)位置
|
||||
def set_current(ent: DiagEntry*, file_name: str, lineno: int, col_start: int, col_end: int) -> int:
|
||||
if ent is None: return 0
|
||||
ent.File = dup_str(file_name)
|
||||
ent.Line = lineno
|
||||
ent.ColStart = col_start
|
||||
ent.ColEnd = col_end
|
||||
return 0
|
||||
|
||||
|
||||
# add_frame - 追加调用链一帧(按"自外向内"顺序追加)
|
||||
def add_frame(ent: DiagEntry*, file_name: str, lineno: int) -> int:
|
||||
if ent is None or ent.FrameFiles is None or ent.FrameLines is None: return 0
|
||||
if ent.FrameCount >= ent.Cap: return 0
|
||||
ent.FrameFiles[ent.FrameCount] = sizet(dup_str(file_name))
|
||||
ent.FrameLines[ent.FrameCount] = sizet(lineno)
|
||||
ent.FrameCount += 1
|
||||
return 0
|
||||
|
||||
|
||||
# emit - 打印一条诊断:头 → 调用链(按 direction) → 当前帧(caret) → 类别: 消息
|
||||
def emit(ent: DiagEntry*, direction: int) -> int:
|
||||
if ent is None: return 0
|
||||
emit_header()
|
||||
if ent.FrameFiles is not None and ent.FrameLines is not None:
|
||||
if direction == DIAG_DIR_DOWN:
|
||||
i_d: int = ent.FrameCount - 1
|
||||
while i_d >= 0:
|
||||
emit_frame(str(ent.FrameFiles[i_d]), int(ent.FrameLines[i_d]))
|
||||
i_d -= 1
|
||||
else:
|
||||
i_u: int = 0
|
||||
while i_u < ent.FrameCount:
|
||||
emit_frame(str(ent.FrameFiles[i_u]), int(ent.FrameLines[i_u]))
|
||||
i_u += 1
|
||||
emit_current(ent.File, ent.Line, ent.ColStart, ent.ColEnd)
|
||||
emit_category(ent.Category, ent.Msg)
|
||||
return 0
|
||||
|
||||
|
||||
# free_entry - 释放一条诊断(含深拷贝的字符串)
|
||||
def free_entry(ent: DiagEntry*) -> int:
|
||||
if ent is None: return 0
|
||||
if ent.File is not None: stdlib.free(ent.File)
|
||||
if ent.Category is not None: stdlib.free(ent.Category)
|
||||
if ent.Msg is not None: stdlib.free(ent.Msg)
|
||||
if ent.Hint is not None: stdlib.free(ent.Hint)
|
||||
if ent.FrameFiles is not None:
|
||||
i_f: int = 0
|
||||
while i_f < ent.FrameCount:
|
||||
if ent.FrameFiles[i_f] != 0: stdlib.free(str(ent.FrameFiles[i_f]))
|
||||
i_f += 1
|
||||
stdlib.free(ent.FrameFiles)
|
||||
if ent.FrameLines is not None: stdlib.free(ent.FrameLines)
|
||||
stdlib.free(ent)
|
||||
return 0
|
||||
|
||||
|
||||
# read_source_line - 读源文件第 lineno 行,供 traceback 展示
|
||||
def read_source_line(file_path: str, lineno: int) -> str:
|
||||
"""读第 lineno 行;返回 stdlib 分配的缓冲(调用方 free),读不到返回 None"""
|
||||
if file_path is None or lineno <= 0: return None
|
||||
f: fileio.File* = fileio.File(file_path, fileio.MODE.R)
|
||||
if f.closed: return None
|
||||
# Allocate by actual file size: a fixed 256KB buffer truncated large files so late line
|
||||
# numbers were unreadable. The 64MB cap guards against reading abnormally huge files.
|
||||
fsize: LONGLONG = f.size()
|
||||
if fsize <= 0 or fsize > 67108864:
|
||||
f.close()
|
||||
return None
|
||||
src_size: sizet = sizet(fsize)
|
||||
buf: bytes = stdlib.malloc(src_size + 1)
|
||||
if buf is None:
|
||||
f.close()
|
||||
return None
|
||||
bytes_read: LONG = f.read_all(buf, ULONG(src_size + 1))
|
||||
f.close()
|
||||
if bytes_read <= 0:
|
||||
stdlib.free(buf)
|
||||
return None
|
||||
# Scan line by line for the target lineno
|
||||
cur_line: int = 1
|
||||
line_start: sizet = 0
|
||||
for i in range(bytes_read):
|
||||
if buf[i] == '\n':
|
||||
if cur_line == lineno:
|
||||
line_len: sizet = i - line_start
|
||||
if line_len > 0 and buf[line_start + line_len - 1] == '\r': line_len -= 1
|
||||
line_buf: bytes = stdlib.malloc(line_len + 1)
|
||||
if line_buf is not None:
|
||||
string.strncpy(line_buf, buf + line_start, line_len)
|
||||
line_buf[line_len] = '\0'
|
||||
stdlib.free(buf)
|
||||
return line_buf
|
||||
stdlib.free(buf)
|
||||
return None
|
||||
cur_line += 1
|
||||
line_start = i + 1
|
||||
stdlib.free(buf)
|
||||
return None
|
||||
|
||||
|
||||
# emit_header - traceback 头(解析失败与语义错误共用同一行文案)
|
||||
def emit_header() -> int:
|
||||
stdio.printf("Traceback (most recent call last, compile-time error):\n")
|
||||
return 0
|
||||
|
||||
|
||||
# emit_frame - 调用栈里的一帧(只印位置与源码行,不印 caret)
|
||||
def emit_frame(file_name: str, lineno: int) -> int:
|
||||
if file_name is None: return 0
|
||||
if lineno > 0:
|
||||
stdio.printf(' File "%s", line %d\n', file_name, lineno)
|
||||
src: str = read_source_line(file_name, lineno)
|
||||
if src is not None:
|
||||
stdio.printf(" %s\n", src)
|
||||
stdlib.free(src)
|
||||
else: stdio.printf(' File "%s"\n', file_name)
|
||||
return 0
|
||||
|
||||
|
||||
# emit_current - 当前帧(带 caret 列范围;行读不出时印警告)
|
||||
def emit_current(file_name: str, lineno: int, col_start: int, col_end: int) -> int:
|
||||
# Sanity defense: read the line first to verify lineno belongs to the current file.
|
||||
# If unreadable the file/line is untrustworthy (e.g. reused AST); print a warning instead.
|
||||
src_line: str = None
|
||||
line_ok: int = 1
|
||||
if lineno > 0 and string.strcmp(file_name, "<unknown>") != 0:
|
||||
src_line = read_source_line(file_name, lineno)
|
||||
if src_line is None: line_ok = 0
|
||||
if line_ok == 0:
|
||||
stdio.printf(' File "%s", line %d (!! cannot read that source line: file does not exist or line number out of range; the error may come from a reused AST tree, rely on the error message)\n', file_name, lineno)
|
||||
else:
|
||||
stdio.printf(' File "%s", line %d\n', file_name, lineno)
|
||||
if src_line is not None:
|
||||
stdio.printf(" %s\n", src_line)
|
||||
# Column-range marker: use ^ under the source line to mark the error range
|
||||
if col_start > 0 and col_end >= col_start:
|
||||
stdio.printf(" ")
|
||||
j: int
|
||||
for j in range(col_end):
|
||||
if j >= col_start - 1 and j < col_end: stdio.printf("^")
|
||||
else: stdio.printf(" ")
|
||||
stdio.printf("\n")
|
||||
stdlib.free(src_line)
|
||||
return 0
|
||||
|
||||
|
||||
# emit_category - `类别: 消息`,随后立即 fflush
|
||||
def emit_category(err_type: str, msg: str) -> int:
|
||||
if err_type is None or err_type[0] == 0: err_type = "SyntaxError"
|
||||
stdio.printf("%s: %s\n", err_type, msg)
|
||||
# Force flush before exiting: stdout redirected to a file is fully buffered, so terminating
|
||||
# directly drops the whole traceback (observed: 2.log kept only one VLogger line).
|
||||
stdio.fflush(None)
|
||||
return 0
|
||||
|
||||
|
||||
# emit_parse - 解析失败的单帧 traceback(Python 风格,单个 ^ 指向 token 起始列)
|
||||
def emit_parse(file_path: str, err_line: int, err_col: int, err_msg: str) -> int:
|
||||
emit_header()
|
||||
if file_path is None or file_path[0] == 0: file_path = "<unknown>"
|
||||
if err_line > 0:
|
||||
stdio.printf(' File "%s", line %d\n', file_path, err_line)
|
||||
src: str = read_source_line(file_path, err_line)
|
||||
if src is not None:
|
||||
stdio.printf(" %s\n", src)
|
||||
# Column marker: put ^ under the error token's start column (parser col_offset is
|
||||
# 0-based, so 4-space indent + err_col spaces lands on the token's first char)
|
||||
if err_col > 0:
|
||||
stdio.printf(" ")
|
||||
for _ in range(err_col): stdio.printf(" ")
|
||||
stdio.printf("^\n")
|
||||
stdlib.free(src)
|
||||
else: stdio.printf(' File "%s"\n', file_path)
|
||||
if err_msg is None or err_msg[0] == 0: err_msg = "syntax error"
|
||||
stdio.printf("SyntaxError: %s\n", err_msg)
|
||||
return 0
|
||||
|
||||
|
||||
# finish - 终止策略:REPL 里 longjmp 回循环(报文已印、进程存活),批处理里 sys.exit(1)
|
||||
def finish() -> None:
|
||||
VLogger.repl_abort()
|
||||
@@ -402,7 +402,12 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
|
||||
if stmt is None: continue
|
||||
if stmt.kind() == ast.ASTKind.FunctionDef:
|
||||
fd: ast.FunctionDef* = ast.FunctionDef*(stmt)
|
||||
if fd is not None and fd.name is not None: self._translate_method(trans, fd, struct_ty, class_name)
|
||||
if fd is not None and fd.name is not None:
|
||||
# 方法体翻译 = 下潜进一个源码上下文:推一帧(def 位置),供 traceback 回溯。
|
||||
tr_mf: str = self.Gen.TypeGenResolve.get_current_file()
|
||||
self.Gen.TypeGenBase.push_trace(tr_mf, fd.lineno)
|
||||
self._translate_method(trans, fd, struct_ty, class_name)
|
||||
self.Gen.TypeGenBase.pop_trace()
|
||||
|
||||
# If there is a vtable, generate it before __before_init__ (which reads VTableMethodCount);
|
||||
# check via oop_entry directly to avoid cross-module same-name lookups.
|
||||
|
||||
@@ -48,7 +48,15 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
|
||||
elif k == ast.ASTKind.AnnAssign: return self.Gen.AssignGen.translate_annassign(trans, node)
|
||||
elif k == ast.ASTKind.FunctionDef:
|
||||
# nested function def: hoist to a top-level function and build a closure
|
||||
return self.Gen.FuncGen.translate_nested_function_def(trans, node)
|
||||
# 函数体翻译 = 下潜进一个源码上下文:推一帧(def 位置),供 traceback 回溯。
|
||||
nfd_t: ast.FunctionDef* = ast.FunctionDef*(node)
|
||||
tr_nf: str = self.Gen.TypeGenResolve.get_current_file()
|
||||
tr_nl: int = 0
|
||||
if nfd_t is not None: tr_nl = nfd_t.lineno
|
||||
self.Gen.TypeGenBase.push_trace(tr_nf, tr_nl)
|
||||
rc_nf: int = self.Gen.FuncGen.translate_nested_function_def(trans, node)
|
||||
self.Gen.TypeGenBase.pop_trace()
|
||||
return rc_nf
|
||||
elif k == ast.ASTKind.Return: return self.Gen.StmtGenCif.translate_return(trans, node)
|
||||
elif k == ast.ASTKind.If: return self.Gen.StmtGenCif.translate_if(trans, node)
|
||||
elif k == ast.ASTKind.While:
|
||||
|
||||
@@ -67,7 +67,12 @@ novirtual class TranslatorGenMixin:
|
||||
if fd_tc is not None and fd_tc.type_params is not None: continue
|
||||
# Phase 1a declare mode registers only CExport/State names; Phase 1b fully translates bodies.
|
||||
if trans._declare_only == 0:
|
||||
# 函数体翻译 = 编译器下潜进一个源码上下文:推一帧(def 位置),
|
||||
# 让函数体内部的错误在 traceback 里回溯到"正在翻译哪个函数"(调用链)。
|
||||
tr_f: str = self.Gen.TypeGenResolve.get_current_file()
|
||||
self.Gen.TypeGenBase.push_trace(tr_f, fd_tc.lineno)
|
||||
added: int = self.Gen.FuncGenDef.translate_function_def(trans, child)
|
||||
self.Gen.TypeGenBase.pop_trace()
|
||||
added_total += added
|
||||
elif trans._declare_only == 1:
|
||||
self._register_cexport_from_funcdef(trans, child)
|
||||
|
||||
@@ -12,6 +12,7 @@ import stdlib
|
||||
import sys
|
||||
import w32.fileio as fileio
|
||||
import lib.core.VLogger as VLogger
|
||||
import lib.core.Diag as Diag
|
||||
import lib.Stub.StubMerger as StubMerger
|
||||
import lib.core.CompilerFactory as CompilerFactory
|
||||
import lib.core.Gen.Translator as HT
|
||||
@@ -26,90 +27,21 @@ novirtual class TypeGenErr(TypeGenBase.TypeGenBase):
|
||||
"""Error reporting / source-line reading / type inference (fatal_error/infer_* etc.)"""
|
||||
|
||||
def _read_source_line(self, file_path: str, lineno: int) -> str:
|
||||
"""Read line lineno from the source file"""
|
||||
if file_path is None or lineno <= 0: return None
|
||||
f: fileio.File* = fileio.File(file_path, fileio.MODE.R)
|
||||
if f.closed: return None
|
||||
# Allocate by actual file size: a fixed 256KB buffer truncated large files so late line
|
||||
# numbers were unreadable. The 64MB cap guards against reading abnormally huge files.
|
||||
fsize: LONGLONG = f.size()
|
||||
if fsize <= 0 or fsize > 67108864:
|
||||
f.close()
|
||||
return None
|
||||
src_size: sizet = sizet(fsize)
|
||||
buf: bytes = stdlib.malloc(src_size + 1)
|
||||
if buf is None:
|
||||
f.close()
|
||||
return None
|
||||
bytes_read: LONG = f.read_all(buf, ULONG(src_size + 1))
|
||||
f.close()
|
||||
if bytes_read <= 0:
|
||||
stdlib.free(buf)
|
||||
return None
|
||||
# Scan line by line for the target lineno
|
||||
cur_line: int = 1
|
||||
line_start: sizet = 0
|
||||
for i in range(bytes_read):
|
||||
if buf[i] == '\n':
|
||||
if cur_line == lineno:
|
||||
line_len: sizet = i - line_start
|
||||
if line_len > 0 and buf[line_start + line_len - 1] == '\r': line_len -= 1
|
||||
line_buf: bytes = stdlib.malloc(line_len + 1)
|
||||
if line_buf is not None:
|
||||
string.strncpy(line_buf, buf + line_start, line_len)
|
||||
line_buf[line_len] = '\0'
|
||||
stdlib.free(buf)
|
||||
return line_buf
|
||||
stdlib.free(buf)
|
||||
return None
|
||||
cur_line += 1
|
||||
line_start = i + 1
|
||||
stdlib.free(buf)
|
||||
return None
|
||||
"""Read line lineno from the source file (delegates to the Diag module)"""
|
||||
return Diag.read_source_line(file_path, lineno)
|
||||
def read_source_line(self, file_path: str, lineno: int) -> str:
|
||||
"""Public wrapper: read line lineno from the source file for traceback display"""
|
||||
return self._read_source_line(file_path, lineno)
|
||||
def dump_parse_traceback(self, file_path: str) -> None:
|
||||
"""On parse failure, print a Python-style traceback (like fatal_error)"""
|
||||
err_line: int = ast.get_parse_err_line()
|
||||
err_col: int = ast.get_parse_err_col()
|
||||
err_msg: str = ast.get_parse_err_msg()
|
||||
stdio.printf("Traceback (most recent call last, compile-time error):\n")
|
||||
if file_path is None or file_path[0] == 0: file_path = "<unknown>"
|
||||
if err_line > 0:
|
||||
stdio.printf(' File "%s", line %d\n', file_path, err_line)
|
||||
src: str = self._read_source_line(file_path, err_line)
|
||||
if src is not None:
|
||||
stdio.printf(" %s\n", src)
|
||||
# Column marker: put ^ under the error token's start column (parser col_offset is
|
||||
# 0-based, so 4-space indent + err_col spaces lands on the token's first char)
|
||||
if err_col > 0:
|
||||
stdio.printf(" ")
|
||||
for _ in range(err_col): stdio.printf(" ")
|
||||
stdio.printf("^\n")
|
||||
stdlib.free(src)
|
||||
else: stdio.printf(' File "%s"\n', file_path)
|
||||
if err_msg is None or err_msg[0] == 0: err_msg = "syntax error"
|
||||
stdio.printf("SyntaxError: %s\n", err_msg)
|
||||
# 注:返回类型必须显式给一个**真实类型**(int),不能留空、也不能用 None。
|
||||
# 本方法是"必定终止"的 fail-fast,从不返回;但留空/None 都会让跨模块 call site 解析出
|
||||
# 退化类型 i0,命中 CallGenCall.vp 的 i0 守卫 → _fail_method_ret_ty("fatal_error") 中止编译。
|
||||
# 给 int 后解析为 i32,守卫不再命中;因从不返回,该类型不会被真正使用。
|
||||
"""On parse failure, print a Python-style traceback (delegates to the Diag module)"""
|
||||
Diag.emit_parse(file_path, ast.get_parse_err_line(), ast.get_parse_err_col(), ast.get_parse_err_msg())
|
||||
# 注:返回类型必须显式给一个**真实类型**(int),不能留空、也不能用 None。
|
||||
# 本方法是"必定终止"的 fail-fast,从不返回;但留空/None 都会让跨模块 call site 解析出
|
||||
# 退化类型 i0,命中 CallGenCall.vp 的 i0 守卫 → _fail_method_ret_ty("fatal_error") 中止编译。
|
||||
# 给 int 后解析为 i32,守卫不再命中;因从不返回,该类型不会被真正使用。
|
||||
def fatal_error(self, node: ast.AST*, msg: str, err_type: str = "SyntaxError", col_start: int = 0, col_end: int = 0) -> int:
|
||||
"""Report an error in Python style and exit (supports multiple frames, error type, column range)."""
|
||||
stdio.printf("Traceback (most recent call last, compile-time error):\n")
|
||||
# Print the call stack frames (bottom to top)
|
||||
for i in range(self.Gen.TypeGenBase.TraceCount):
|
||||
frame: GenTypes.TraceFrame* = self.Gen.TypeGenBase._get_trace_frame(i)
|
||||
if frame is not None and frame.file_name is not None:
|
||||
if frame.lineno > 0:
|
||||
stdio.printf(' File "%s", line %d\n', frame.file_name, frame.lineno)
|
||||
src: str = self._read_source_line(frame.file_name, frame.lineno)
|
||||
if src is not None:
|
||||
stdio.printf(" %s\n", src)
|
||||
stdlib.free(src)
|
||||
else: stdio.printf(' File "%s"\n', frame.file_name)
|
||||
# Print the current frame (position corresponding to node)
|
||||
"""Report an error in Python style and exit (single diagnostic carrying its full call chain)."""
|
||||
# 当前帧(错误点)位置:优先 node,其次调用栈最内层有行号的帧,最后 current_file。
|
||||
lineno: int = 0
|
||||
if node is not None: lineno = node.lineno
|
||||
file_name: str = self.Gen.TypeGenResolve.get_current_file()
|
||||
@@ -125,39 +57,32 @@ novirtual class TypeGenErr(TypeGenBase.TypeGenBase):
|
||||
lineno = tfb.lineno
|
||||
break
|
||||
ti_b -= 1
|
||||
# Sanity defense: read the line first to verify node.lineno belongs to the current file.
|
||||
# If unreadable the file/line is untrustworthy (e.g. reused AST); print a warning instead.
|
||||
src_line: str = None
|
||||
line_ok: int = 1
|
||||
if lineno > 0 and string.strcmp(file_name, "<unknown>") != 0:
|
||||
src_line = self._read_source_line(file_name, lineno)
|
||||
if src_line is None: line_ok = 0
|
||||
if line_ok == 0:
|
||||
stdio.printf(' File "%s", line %d (!! cannot read that source line: file does not exist or line number out of range; the error may come from a reused AST tree, rely on the error message)\n', file_name, lineno)
|
||||
else:
|
||||
stdio.printf(' File "%s", line %d\n', file_name, lineno)
|
||||
if src_line is not None:
|
||||
stdio.printf(" %s\n", src_line)
|
||||
# Column-range marker: use ^ under the source line to mark the error range
|
||||
if col_start > 0 and col_end >= col_start:
|
||||
stdio.printf(" ")
|
||||
j: int
|
||||
for j in range(col_end):
|
||||
if j >= col_start - 1 and j < col_end: stdio.printf("^")
|
||||
else: stdio.printf(" ")
|
||||
stdio.printf("\n")
|
||||
stdlib.free(src_line)
|
||||
# Output the error type + message
|
||||
if err_type is None or err_type[0] == 0: err_type = "SyntaxError"
|
||||
stdio.printf("%s: %s\n", err_type, msg)
|
||||
# Force flush before exiting: stdout redirected to a file is fully buffered, so terminating
|
||||
# directly drops the whole traceback (observed: 2.log kept only one VLogger line).
|
||||
stdio.fflush(None)
|
||||
|
||||
# 单条诊断(自带到错误的完整调用链);打印交给 Diag。
|
||||
ent: Diag.DiagEntry* = Diag.new_entry(Diag.DIAG_MAX_FRAMES)
|
||||
if ent is None:
|
||||
# 极端情况(分配失败):退回单帧输出,保证错误不被静默吞掉。
|
||||
Diag.emit_header()
|
||||
Diag.emit_current(file_name, lineno, col_start, col_end)
|
||||
Diag.emit_category(err_type, msg)
|
||||
Diag.finish()
|
||||
return 0
|
||||
|
||||
Diag.begin(ent, err_type, msg)
|
||||
# 调用链帧(按"自外向内"顺序)**由本类遍历**(只有它拿得到 TraceFrame),填入 entry。
|
||||
for i in range(self.Gen.TypeGenBase.TraceCount):
|
||||
frame: GenTypes.TraceFrame* = self.Gen.TypeGenBase._get_trace_frame(i)
|
||||
if frame is not None and frame.file_name is not None:
|
||||
Diag.add_frame(ent, frame.file_name, frame.lineno)
|
||||
Diag.set_current(ent, file_name, lineno, col_start, col_end)
|
||||
# 默认"向上":最外层触发点在上、错误点最后(Python most recent call last)。
|
||||
Diag.emit(ent, Diag.DIAG_DIR_UP)
|
||||
Diag.free_entry(ent)
|
||||
# Termination convergence: in REPL, longjmp back to the loop via VLogger.repl_abort (the
|
||||
# traceback is printed and the process survives); in batch this exit is sys.exit(1) unchanged.
|
||||
VLogger.repl_abort()
|
||||
# 不可达:repl_abort 要么 longjmp 回 REPL 循环、要么 sys.exit(1)。
|
||||
# 显式 return 只为满足上面声明的 -> int 返回类型(避免"缺返回路径")。
|
||||
Diag.finish()
|
||||
# 不可达:finish 要么 longjmp 回 REPL 循环、要么 sys.exit(1)。
|
||||
# 显式 return 只为满足上面声明的 -> int 返回类型(避免"缺返回路径")。
|
||||
return 0
|
||||
|
||||
# 同 fatal_error:跨模块调用需要**真实**返回类型,否则解析出 i0 会命中守卫中止编译。
|
||||
|
||||
Reference in new issue
Block a user