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}