code wiki / (root) / nx_color.nx

nx_color.nx source

↩ module page · 120 lines · 4524 B

1// nx_color.nx -- ANSI terminal color escape sequences. 2// 3// Compiler error rendering, log output, debugger UI all benefit 4// from terminal colors when stderr is a TTY. This module exposes 5// the standard ANSI SGR (Select Graphic Rendition) escapes as 6// pre-built byte sequences that callers can write directly. 7// 8// Convention: callers should detect TTY-ness once at startup 9// (typically via isatty(2) or its sim equivalent) and gate color 10// output on that. This module doesn't decide; it just provides 11// the escapes. 12// 13// No string literals as constants in NishiLang yet, so we expose 14// pointer-returning helpers that build the sequences in mmap'd 15// memory (lazy-init, single allocation per sequence). 16// 17// Standard ANSI SGR escape pattern: ESC [ N m 18// ESC = 0x1B = '\e' 19// [ = 0x5B 20// N = digit code (e.g. 31 = red) 21// m = 0x6D 22// 23// We produce the literal byte sequence per code (saves a snprintf- 24// equivalent which we don't have natively yet). 25// 26// nx_safety_envelope: 27// intended_use: "Color primitives (RGB / HSL / Q14 lerp) -- 28// substrate visual pipeline foundation" 29// sil_target: SIL1 30// asil_target: QM 31// dal_target: NONE 32// evidence: [Q14_fixed_point_color_arithmetic, no_FP, 33// sRGB_canonical_basis] 34// hazard_register: [bug-tape-gamma-uncorrected-output, 35// bug-tape-channel-overflow-via-saturation] 36// residual_risk: "Linear-light vs sRGB gamma is caller- 37// decided; substrate provides both paths." 38// verdict: NOT_YET_EVALUATED 39 40import "nx_syscalls.nx" 41 42// SGR codes we expose. 43const NX_COLOR_RESET: i64 = 0 44const NX_COLOR_BOLD: i64 = 1 45const NX_COLOR_DIM: i64 = 2 46const NX_COLOR_UNDER: i64 = 4 47const NX_COLOR_RED: i64 = 31 48const NX_COLOR_GREEN: i64 = 32 49const NX_COLOR_YELLOW: i64 = 33 50const NX_COLOR_BLUE: i64 = 34 51const NX_COLOR_MAGENTA: i64 = 35 52const NX_COLOR_CYAN: i64 = 36 53const NX_COLOR_WHITE: i64 = 37 54 55// Build "\x1B[Nm" into `buf`; returns the byte count written. 56// Handles 1- and 2-digit codes (covers 0..99, the entire SGR space 57// we care about today). 58func nx_color_seq(code: i64, buf: *u8) -> i64 { 59 buf[0] = 0x1B 60 buf[1] = 0x5B 61 if code < 10 { 62 buf[2] = 0x30 + code 63 buf[3] = 0x6D 64 return 4 65 } 66 let tens: i64 = code / 10 67 let ones: i64 = code - tens * 10 68 buf[2] = 0x30 + tens 69 buf[3] = 0x30 + ones 70 buf[4] = 0x6D 71 return 5 72} 73 74// Convenience: write a colored string to fd. Wraps `s` in a 75// color escape + text + reset. If `enable` is 0, just writes `s` 76// raw (TTY-detection callers can pass enable=isatty). 77func nx_color_write(fd: i64, enable: i64, code: i64, s: *u8, n: i64) -> i64 { 78 if enable == 0 { 79 sys_write(fd, s, n) 80 return 0 81 } 82 let buf: *u8 = sys_mmap(8) 83 let len: i64 = nx_color_seq(code, buf) 84 sys_write(fd, buf, len) 85 sys_write(fd, s, n) 86 let reset: *u8 = sys_mmap(8) 87 let rlen: i64 = nx_color_seq(NX_COLOR_RESET, reset) 88 sys_write(fd, reset, rlen) 89 return 0 90} 91 92// ---- self-test --------------------------------------------------- 93 94func main() -> i64 { 95 let buf: *u8 = sys_mmap(8) 96 97 // Single-digit code (0 = reset). 98 let n0: i64 = nx_color_seq(NX_COLOR_RESET, buf) 99 if n0 != 4 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 100 if buf[0] != 0x1B { return __syscall(93, 2, 0, 0, 0, 0, 0) } 101 if buf[1] != 0x5B { return __syscall(93, 3, 0, 0, 0, 0, 0) } 102 if buf[2] != 0x30 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 103 if buf[3] != 0x6D { return __syscall(93, 5, 0, 0, 0, 0, 0) } 104 105 // Two-digit code (31 = red). 106 let n1: i64 = nx_color_seq(NX_COLOR_RED, buf) 107 if n1 != 5 { return __syscall(93, 6, 0, 0, 0, 0, 0) } 108 if buf[0] != 0x1B { return __syscall(93, 7, 0, 0, 0, 0, 0) } 109 if buf[1] != 0x5B { return __syscall(93, 8, 0, 0, 0, 0, 0) } 110 if buf[2] != 0x33 { return __syscall(93, 9, 0, 0, 0, 0, 0) } // '3' 111 if buf[3] != 0x31 { return __syscall(93, 10, 0, 0, 0, 0, 0) } // '1' 112 if buf[4] != 0x6D { return __syscall(93, 11, 0, 0, 0, 0, 0) } // 'm' 113 114 // Bold (single digit, code 1). 115 let n2: i64 = nx_color_seq(NX_COLOR_BOLD, buf) 116 if n2 != 4 { return __syscall(93, 12, 0, 0, 0, 0, 0) } 117 if buf[2] != 0x31 { return __syscall(93, 13, 0, 0, 0, 0, 0) } 118 119 return 0 120}