nx_hex.nx source
↩ module page · 185 lines · 6079 B
1// nx_hex.nx -- hex + binary + byte dump helpers.
2//
3// Every enterprise toolchain has a `hexdump` equivalent: raw byte
4// regions rendered in a stable human-readable format for
5// debugging memory state. This module provides three formats:
6//
7// nx_hex_i64 i64 as 16-char hex (no prefix, no pad-space)
8// nx_hex_bytes byte array as `hh hh hh ...`
9// nx_hex_dump full hexdump-C-style: offset + 16 hex + ASCII
10//
11// All write to stderr (fd 2) by default to avoid collision with
12// program stdout. Deterministic output -- no timestamps, no
13// tool-version headers. F6-compatible.
14//
15// Enterprise grade means:
16// * fixed-width columns (sortable, greppable)
17// * ASCII fallback for unprintable bytes (prints '.')
18// * explicit region length (no overruns on short buffers)
19// * offset prefix shows 8-digit hex (up to 4 GB regions)
20// * no dependency on the filesystem -- pure fd write
21//
22// Paired with nx_log (levels + tags) and nx_metrics (counters),
23// nx_hex closes the "what do I see when I'm lost" gap. Every
24// diagnostic should use ONE of: tagged log, numeric metric, or
25// hex dump. Nothing else.
26
27// nx_safety_envelope:
28// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
29// sil_target: SIL1
30// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
31// verdict: NOT_YET_EVALUATED
32
33import "syscalls.nx"
34
35// Convert a nibble (0-15) to an ASCII hex digit. Lowercase
36// matches Prometheus + standard hex-dump conventions.
37func nx_hex_nibble(n: i64) -> i64 {
38 if n < 10 { return 0x30 + n } // '0'..'9'
39 return 0x61 + (n - 10) // 'a'..'f'
40}
41
42// Emit `v` to `fd` as 16 characters of lowercase hex, no prefix.
43// Typical use: dumping a pointer address or a 64-bit word.
44func nx_hex_i64(fd: i64, v: i64) -> i64 {
45 let buf: *u8 = sys_mmap(32)
46 var i: i64 = 0
47 while i < 16 {
48 let nib: i64 = (v >> ((15 - i) * 4)) & 0xF
49 buf[i] = nx_hex_nibble(nib)
50 i = i + 1
51 }
52 sys_write(fd, buf, 16)
53 return 0
54}
55
56// Emit `v` as N lowercase hex chars (N in 1..16). Used for fixed-
57// width offset displays in hexdump.
58func nx_hex_i64_n(fd: i64, v: i64, n: i64) -> i64 {
59 if n < 1 { return 0 }
60 if n > 16 { return 0 }
61 let buf: *u8 = sys_mmap(32)
62 var i: i64 = 0
63 while i < n {
64 let shift: i64 = (n - 1 - i) * 4
65 let nib: i64 = (v >> shift) & 0xF
66 buf[i] = nx_hex_nibble(nib)
67 i = i + 1
68 }
69 sys_write(fd, buf, n)
70 return 0
71}
72
73// Emit a single byte as two hex chars.
74func nx_hex_u8(fd: i64, b: i64) -> i64 {
75 let buf: *u8 = sys_mmap(8)
76 buf[0] = nx_hex_nibble((b >> 4) & 0xF)
77 buf[1] = nx_hex_nibble(b & 0xF)
78 sys_write(fd, buf, 2)
79 return 0
80}
81
82// Emit `n` bytes of buf as space-separated hex pairs.
83// nx_hex_bytes(2, p, 4) -> "de ad be ef"
84func nx_hex_bytes(fd: i64, buf: *u8, n: i64) -> i64 {
85 var i: i64 = 0
86 while i < n {
87 if i > 0 { sys_write(fd, " " as *u8, 1) }
88 nx_hex_u8(fd, buf[i] & 0xFF)
89 i = i + 1
90 }
91 return 0
92}
93
94// Full `hexdump -C` style dump. 16 bytes per row:
95// <8-hex-offset> <16 hex pairs> |<16 printable-or-dot>|
96// For short buffers the last row is padded with spaces where bytes
97// are missing so column alignment stays fixed-width.
98func nx_hex_dump(fd: i64, buf: *u8, n: i64) -> i64 {
99 var row: i64 = 0
100 while row * 16 < n {
101 // Offset: 8 hex digits, 2 spaces
102 nx_hex_i64_n(fd, row * 16, 8)
103 sys_write(fd, " " as *u8, 2)
104
105 // 16 hex pairs, space-separated; extra space in the middle
106 var col: i64 = 0
107 while col < 16 {
108 let pos: i64 = row * 16 + col
109 if col == 8 { sys_write(fd, " " as *u8, 1) }
110 if pos < n {
111 nx_hex_u8(fd, buf[pos] & 0xFF)
112 if col < 15 { sys_write(fd, " " as *u8, 1) }
113 }
114 if pos >= n {
115 // Padding to keep column width fixed.
116 sys_write(fd, " " as *u8, 2)
117 if col < 15 { sys_write(fd, " " as *u8, 1) }
118 }
119 col = col + 1
120 }
121
122 // ASCII section: 2 spaces, bar, 16 chars, bar, newline
123 sys_write(fd, " |" as *u8, 3)
124 col = 0
125 while col < 16 {
126 let pos: i64 = row * 16 + col
127 if pos < n {
128 let b: i64 = buf[pos] & 0xFF
129 if b >= 0x20 {
130 if b < 0x7F {
131 sys_write(fd, buf + pos, 1)
132 } else {
133 sys_write(fd, "." as *u8, 1)
134 }
135 }
136 if b < 0x20 {
137 sys_write(fd, "." as *u8, 1)
138 }
139 }
140 if pos >= n {
141 sys_write(fd, " " as *u8, 1)
142 }
143 col = col + 1
144 }
145 sys_write(fd, "|\n" as *u8, 2)
146
147 row = row + 1
148 }
149 return 0
150}
151
152// --- self-test ------------------------------------------------------
153
154func main() -> i64 {
155 // nx_hex_nibble basic cases
156 if nx_hex_nibble(0) != 0x30 { return __syscall(93, 10, 0, 0, 0, 0, 0) }
157 if nx_hex_nibble(9) != 0x39 { return __syscall(93, 11, 0, 0, 0, 0, 0) }
158 if nx_hex_nibble(10) != 0x61 { return __syscall(93, 12, 0, 0, 0, 0, 0) }
159 if nx_hex_nibble(15) != 0x66 { return __syscall(93, 13, 0, 0, 0, 0, 0) }
160
161 // Build a test buffer with known bytes
162 let buf: *u8 = sys_mmap(64)
163 // 'deadbeef' pattern + ASCII + control chars
164 buf[0] = 0xDE
165 buf[1] = 0xAD
166 buf[2] = 0xBE
167 buf[3] = 0xEF
168 buf[4] = 0x48 // 'H'
169 buf[5] = 0x65 // 'e'
170 buf[6] = 0x6C // 'l'
171 buf[7] = 0x6C // 'l'
172 buf[8] = 0x6F // 'o'
173 buf[9] = 0x0A // '\n' (control, -> '.')
174 buf[10] = 0x00 // NUL (control, -> '.')
175
176 // Demonstrate the three emitters.
177 sys_write(2, "i64 hex: " as *u8, 9)
178 nx_hex_i64(2, 0xDEADBEEFCAFEBABE)
179 sys_write(2, "\nbytes: " as *u8, 8)
180 nx_hex_bytes(2, buf, 11)
181 sys_write(2, "\ndump:\n" as *u8, 7)
182 nx_hex_dump(2, buf, 11)
183
184 return 0
185}