249 lines
9.4 KiB
Plaintext
249 lines
9.4 KiB
Plaintext
import t, c
|
|
from stdint import *
|
|
import stdio
|
|
import stdlib
|
|
import memhub
|
|
import sys
|
|
import win32.win32console as win32cmd
|
|
import win32.win32file as win32file
|
|
import win32.win32base as win32base
|
|
|
|
|
|
# VLogger - native logger with colored Windows console output.
|
|
# Format: ├ LEVEL: message or ├ LEVEL[category]: message (aligned with TPC lib/core/VLogger.py).
|
|
|
|
# Error recovery (REPL/JIT): after error, longjmp back to the REPL setjmp point.
|
|
# mingw-w64 x86_64: longjmp is void longjmp(jmp_buf, int); setjmp expands to _setjmp(buf, NULL).
|
|
extern def longjmp(buf: rawptr, val: int)
|
|
|
|
|
|
# REPL recovery-point pointer: registered at session start, cleared at session end.
|
|
# Covers the whole fragment pipeline; batch mode never sets it, so it stays None and exits via sys.exit(1).
|
|
_repl_jmpbuf: rawptr = None
|
|
|
|
|
|
# repl_abort - the single "terminate after diagnosis" exit in VPC.
|
|
# Caller already printed the diagnostic; returns to REPL via longjmp if a recovery point is set, else sys.exit(1).
|
|
def repl_abort() -> int:
|
|
"""Unified exit after diagnosis: longjmp to the REPL if a recovery point is set, else exit."""
|
|
if _repl_jmpbuf is not None:
|
|
stdio.fflush(None)
|
|
longjmp(_repl_jmpbuf, 1)
|
|
sys.exit(1)
|
|
return 0
|
|
|
|
|
|
def set_repl_jmpbuf(buf: t.CPtr) -> int:
|
|
"""Register the REPL jmp_buf; idempotent at each fragment entry."""
|
|
global _repl_jmpbuf
|
|
_repl_jmpbuf = buf
|
|
return 0
|
|
|
|
|
|
def clear_repl_jmpbuf() -> int:
|
|
"""Clear the registered jmp_buf at REPL session end."""
|
|
global _repl_jmpbuf
|
|
_repl_jmpbuf = None
|
|
return 0
|
|
|
|
# Log levels
|
|
enum LogLevel:
|
|
DEBUG, INFO, WARNING, ERROR, SUCCESS, CRITICAL
|
|
|
|
|
|
# Win32 console foreground color attributes
|
|
# 值用 WORD(...) 显式定宽为 uint16(与 _log 的 sym_color/level_color 形参一致)。
|
|
# 注意:仅写 `define X: WORD = 0x0002` 不够 —— define 的宽度由**右侧表达式的类型**
|
|
# 决定(win32file.vp 的 `define STD_OUTPUT_HANDLE: DWORD = DWORD(-10)` 同款),
|
|
# 否则常量仍是 i32 → 传给 WORD 形参即 32→16 隐式窄化。
|
|
define FOREGROUND_BLUE: WORD = WORD(0x0001)
|
|
define FOREGROUND_GREEN: WORD = WORD(0x0002)
|
|
define FOREGROUND_RED: WORD = WORD(0x0004)
|
|
define FOREGROUND_INTENSITY: WORD = WORD(0x0008)
|
|
define FOREGROUND_WHITE: WORD = WORD(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
|
|
define FOREGROUND_CYAN: WORD = WORD(FOREGROUND_BLUE | FOREGROUND_GREEN)
|
|
define FOREGROUND_YELLOW: WORD = WORD(FOREGROUND_RED | FOREGROUND_GREEN)
|
|
define FOREGROUND_MAGENTA: WORD = WORD(FOREGROUND_RED | FOREGROUND_BLUE)
|
|
|
|
# Win32 console background color attributes
|
|
define BACKGROUND_RED: WORD = WORD(0x0040)
|
|
define BACKGROUND_GREEN: WORD = WORD(0x0020)
|
|
define BACKGROUND_BLUE: WORD = WORD(0x0010)
|
|
define BACKGROUND_INTENSITY: WORD = WORD(0x0080)
|
|
define BACKGROUND_WHITE: WORD = WORD(BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE)
|
|
|
|
|
|
novirtual class Logger:
|
|
_level: int
|
|
_console_handle: win32base.HANDLE
|
|
_use_color: int
|
|
jit_flag: bool
|
|
__mbuddy__: memhub.MemBuddy*
|
|
|
|
def __init__(self, level: int = 1):
|
|
self._level = level
|
|
self._console_handle = win32file.GetStdHandle(win32file.STD_OUTPUT_HANDLE)
|
|
self._use_color = 1
|
|
self.jit_flag = False
|
|
self.__mbuddy__ = _mbuddy
|
|
|
|
def _set_color(self, attr: WORD) -> int:
|
|
uc: int = self._use_color
|
|
if uc:
|
|
ch: win32base.HANDLE = self._console_handle
|
|
ret: int = win32cmd.SetConsoleTextAttribute(ch, attr)
|
|
return ret
|
|
return 0
|
|
|
|
def _reset_color(self) -> int:
|
|
if self._use_color: return win32cmd.SetConsoleTextAttribute(self._console_handle, FOREGROUND_WHITE)
|
|
return 0
|
|
|
|
# _log - core log output with segmented coloring (prefix symbol / level tag / message).
|
|
# Format: ├ LEVEL: msg or ├ LEVEL[category]: msg.
|
|
def _log(self, level: int, prefix_sym: str, level_str: str, msg: str, sym_color: WORD, level_color: WORD, category: str = "") -> int:
|
|
# Color the prefix symbol
|
|
self._set_color(sym_color)
|
|
stdio.printf("%s ", prefix_sym)
|
|
# Color the level tag
|
|
self._set_color(level_color)
|
|
stdio.printf("%s", level_str)
|
|
self._reset_color()
|
|
# Optional category
|
|
if category is not None and category[0] != 0: stdio.printf(" [%s]", category)
|
|
stdio.printf(": %s\n", msg)
|
|
return 0
|
|
|
|
def debug(self, msg: str, category: str = "") -> int:
|
|
return self._log(LogLevel.DEBUG, "├", "DEBUG", msg, FOREGROUND_INTENSITY, FOREGROUND_INTENSITY, category)
|
|
|
|
def info(self, msg: str, category: str = "") -> int:
|
|
ret: int = self._log(LogLevel.INFO, "├", "INFO", msg, FOREGROUND_GREEN, WORD(FOREGROUND_GREEN | FOREGROUND_INTENSITY), category)
|
|
return ret
|
|
|
|
def warning(self, msg: str, category: str = "") -> int:
|
|
return self._log(LogLevel.WARNING, "├", "WARN", msg, FOREGROUND_YELLOW, WORD(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY), category)
|
|
|
|
def error(self, msg: str, category: str = "") -> int:
|
|
ret: int = self._log(LogLevel.ERROR, "├", "ERROR", msg, FOREGROUND_RED, WORD(FOREGROUND_RED | FOREGROUND_INTENSITY), category)
|
|
# JIT/REPL: diagnostic already printed; jump back to the REPL loop via the unified exit.
|
|
if self.jit_flag:
|
|
repl_abort()
|
|
return ret
|
|
# Batch mode: terminate immediately on error.
|
|
sys.exit(1)
|
|
return ret
|
|
|
|
def success(self, msg: str, category: str = "") -> int:
|
|
return self._log(LogLevel.SUCCESS, "├", "SUCCESS", msg, FOREGROUND_GREEN, WORD(FOREGROUND_GREEN | FOREGROUND_INTENSITY), category)
|
|
|
|
def critical(self, msg: str, category: str = "") -> int:
|
|
# CRITICAL uses the ╠ prefix (red-background white label, aligned with TPC style).
|
|
return self._log(LogLevel.CRITICAL, "╠", "CRITICAL", msg, FOREGROUND_RED, WORD(BACKGROUND_RED | FOREGROUND_WHITE | FOREGROUND_INTENSITY), category)
|
|
|
|
def set_level(self, level: int) -> int:
|
|
self._level = level
|
|
return 0
|
|
|
|
def set_jit_flag(self, v: bool) -> int:
|
|
"""Set the JIT/REPL flag: True makes error longjmp back to the REPL loop."""
|
|
self.jit_flag = v
|
|
return 0
|
|
|
|
# Advanced log methods.
|
|
|
|
def banner(self, msg: str) -> int:
|
|
"""Print a blue-highlighted section banner."""
|
|
self._set_color(FOREGROUND_BLUE | FOREGROUND_INTENSITY)
|
|
stdio.printf("\n=== %s ===\n\n", msg)
|
|
self._reset_color()
|
|
return 0
|
|
|
|
def compile_error(self, msg: str, file: str = "", line: int = 0) -> int:
|
|
"""Format a compile error with a red-background header, location and message."""
|
|
self._set_color(BACKGROUND_RED | FOREGROUND_WHITE | FOREGROUND_INTENSITY)
|
|
stdio.printf(" COMPILE ERROR ")
|
|
self._set_color(FOREGROUND_RED | FOREGROUND_INTENSITY)
|
|
stdio.printf("\n")
|
|
if file is not None:
|
|
if file[0] != 0 and line > 0: stdio.printf(" location: %s:%d\n", file, line)
|
|
elif file[0] != 0: stdio.printf(" file: %s\n", file)
|
|
stdio.printf(" error: %s\n", msg)
|
|
self._reset_color()
|
|
return 0
|
|
|
|
def compile_warning(self, msg: str, file: str = "", line: int = 0) -> int:
|
|
"""Format a compile warning with a yellow background, location and message."""
|
|
self._set_color(BACKGROUND_RED | BACKGROUND_GREEN | FOREGROUND_INTENSITY)
|
|
stdio.printf(" COMPILE WARNING ")
|
|
self._set_color(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY)
|
|
stdio.printf("\n")
|
|
if file is not None:
|
|
if file[0] != 0 and line > 0: stdio.printf(" location: %s:%d\n", file, line)
|
|
elif file[0] != 0: stdio.printf(" file: %s\n", file)
|
|
stdio.printf(" warning: %s\n", msg)
|
|
self._reset_color()
|
|
return 0
|
|
|
|
|
|
# Global mbuddy pointer (injected by lib.InitLib).
|
|
_mbuddy: memhub.MemBuddy*
|
|
|
|
# Global logger pointer
|
|
logger: Logger*
|
|
log: Logger*
|
|
|
|
def init_logger():
|
|
"""Initialize the global logger instance."""
|
|
global logger, log
|
|
raw: t.CPtr = _mbuddy.alloc(Logger.__sizeof__())
|
|
# if raw is None: return None (memory management still unclear)
|
|
logger = raw
|
|
logger.__before_init__()
|
|
logger.__init__(LogLevel.INFO)
|
|
log = logger
|
|
# if fmtbuffer is None: return None
|
|
|
|
|
|
# --nowarn-register switch: suppresses Phase A dependency WARN output (output only).
|
|
_nowarn_register: int = 0
|
|
|
|
# Module-level convenience functions (aligned with TPC lib.core.VLogger; no get_logger() needed).
|
|
|
|
def info(msg: str, category: str = "") -> int:
|
|
"""Log at INFO level."""
|
|
if log is not None: return log.info(msg, category)
|
|
return 0
|
|
|
|
|
|
def warning(msg: str, category: str = "") -> int:
|
|
"""Log at WARN level."""
|
|
if log is not None: return log.warning(msg, category)
|
|
return 0
|
|
|
|
|
|
def error(msg: str, category: str = "") -> int:
|
|
"""Log at ERROR level: exit in batch mode, longjmp to REPL under JIT/REPL."""
|
|
if log is not None: return log.error(msg, category)
|
|
# When the logger is unavailable, still use the unified exit (REPL can recover if registered)
|
|
repl_abort()
|
|
return 0
|
|
|
|
|
|
def success(msg: str, category: str = "") -> int:
|
|
"""Log at SUCCESS level."""
|
|
if log is not None: return log.success(msg, category)
|
|
return 0
|
|
|
|
|
|
def critical(msg: str, category: str = "") -> int:
|
|
"""Log at CRITICAL level (fatal, red background)."""
|
|
if log is not None: return log.critical(msg, category)
|
|
return 0
|
|
|
|
|
|
def debug(msg: str, category: str = "") -> int:
|
|
"""Log at DEBUG level."""
|
|
if log is not None: return log.debug(msg, category)
|
|
return 0
|