Files
ViperC/App/lib/core/VLogger.vp
T

247 lines
9.4 KiB
Plaintext

from stdint import *
import stdio
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: rawptr) -> 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: rawptr = _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