code wiki / (root) / nx_ini.nx

nx_ini.nx source

↩ module page · 233 lines · 7289 B

1// ini.nx -- parse INI-style configuration files. 2// 3// Classic [section] / key = value format. Simpler than TOML, 4// used by 30+ years of Windows config + a lot of C daemons 5// (php.ini, systemd unit files, mumble / xmms / qt config). 6// 7// Format: 8// [section_name] <- section header, any leading/trailing ws ok 9// key = value <- key/value, '=' or ':' separator 10// ; comment <- semicolon 11// # comment <- hash 12// (blank line) 13// 14// We return a flat array of (section_off, section_len, key_off, 15// key_len, val_off, val_len) tuples. The section fields are 16// zero if a key appears before any [section] header. 17// 18// Like query.nx, this is zero-alloc -- we emit offsets into the 19// caller's buffer and never copy. Whitespace around keys/values 20// is trimmed (not stored). Inline comments are NOT supported 21// (value runs to end-of-line). 22// 23// Invariants: 24// I1 Empty input -> zero entries. 25// I2 Keys with no '=' or ':' are silently dropped (warning 26// would be logged in a production parser; we stay quiet). 27// I3 Sections persist down until next [section] header. 28// I4 Duplicate keys: all are emitted; caller picks which wins. 29 30// nx_safety_envelope: 31// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 32// sil_target: SIL1 33// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 34// verdict: NOT_YET_EVALUATED 35 36import "nx_syscalls.nx" 37const INI_MAGIC_1024: i64 = 1024 38 39const INI_ERR_OVERFLOW: i64 = -1 40 41struct IniEntry { 42 section_off: i64, section_len: i64, 43 key_off: i64, key_len: i64, 44 val_off: i64, val_len: i64, 45} 46 47// Is byte ASCII whitespace? 48func ini_is_ws(b: i64) -> i64 { 49 if b == 0x20 { return 1 } 50 if b == 0x09 { return 1 } 51 if b == 0x0D { return 1 } 52 return 0 53} 54 55// Scan to end of line (LF or end of buffer). Returns position of 56// LF (or n if no LF before EOF). 57func ini_eol(buf: *u8, n: i64, off: i64) -> i64 { 58 var i: i64 = off 59 while i < n { 60 if buf[i] == 0x0A { return i } 61 i = i + 1 62 } 63 return n 64} 65 66// Trim trailing whitespace from [start, end). Returns new end. 67func ini_rtrim(buf: *u8, start: i64, end: i64) -> i64 { 68 var e: i64 = end 69 while e > start { 70 if ini_is_ws(buf[e - 1]) == 0 { return e } 71 e = e - 1 72 } 73 return e 74} 75 76// Trim leading whitespace starting at off, bounded by end. 77func ini_ltrim(buf: *u8, off: i64, end: i64) -> i64 { 78 var i: i64 = off 79 while i < end { 80 if ini_is_ws(buf[i]) == 0 { return i } 81 i = i + 1 82 } 83 return end 84} 85 86// Parse an INI file into caller's entry table. Returns number 87// of entries or INI_ERR_OVERFLOW if cap exhausted. 88func ini_parse(buf: *u8, n: i64, 89 entries: *IniEntry, cap: i64) -> i64 { 90 var count: i64 = 0 91 var line_start: i64 = 0 92 // Current section offset/length (inherited across lines). 93 var sec_off: i64 = 0 94 var sec_len: i64 = 0 95 96 while line_start < n { 97 let line_end: i64 = ini_eol(buf, n, line_start) 98 // Trim leading whitespace. 99 let c_start: i64 = ini_ltrim(buf, line_start, line_end) 100 let c_end: i64 = ini_rtrim(buf, c_start, line_end) 101 102 if c_start == c_end { 103 // Blank line -- advance past LF. 104 line_start = line_end + 1 105 continue 106 } 107 let first: i64 = buf[c_start] 108 if first == 0x3B { 109 // ';' comment. 110 line_start = line_end + 1 111 continue 112 } 113 if first == 0x23 { 114 // '#' comment. 115 line_start = line_end + 1 116 continue 117 } 118 if first == 0x5B { 119 // '[' section header. Find the matching ']'. 120 var j: i64 = c_start + 1 121 while j < c_end { 122 if buf[j] == 0x5D { break } 123 j = j + 1 124 } 125 sec_off = c_start + 1 126 sec_len = j - (c_start + 1) 127 line_start = line_end + 1 128 continue 129 } 130 131 // Otherwise: key = value 132 var eq_pos: i64 = -1 133 var k: i64 = c_start 134 while k < c_end { 135 if buf[k] == 0x3D { 136 eq_pos = k 137 break 138 } 139 if buf[k] == 0x3A { 140 eq_pos = k 141 break 142 } 143 k = k + 1 144 } 145 if eq_pos < 0 { 146 line_start = line_end + 1 147 continue 148 } 149 150 if count >= cap { return INI_ERR_OVERFLOW } 151 let e: *IniEntry = entries + count * 48 152 let key_end: i64 = ini_rtrim(buf, c_start, eq_pos) 153 let val_start: i64 = ini_ltrim(buf, eq_pos + 1, c_end) 154 155 e.section_off = sec_off 156 e.section_len = sec_len 157 e.key_off = c_start 158 e.key_len = key_end - c_start 159 e.val_off = val_start 160 e.val_len = c_end - val_start 161 count = count + 1 162 163 line_start = line_end + 1 164 } 165 return count 166} 167 168// Find first entry with matching section + key. Byte-exact 169// comparison; empty section matches entries before any [section]. 170func ini_find(buf: *u8, entries: *IniEntry, count: i64, 171 section: *u8, section_len: i64, 172 key: *u8, key_len: i64) -> i64 { 173 var i: i64 = 0 174 while i < count { 175 let e: *IniEntry = entries + i * 48 176 if e.section_len == section_len { 177 if e.key_len == key_len { 178 var j: i64 = 0 179 var ok: i64 = 1 180 while j < section_len { 181 if buf[e.section_off + j] != section[j] { 182 ok = 0 183 break 184 } 185 j = j + 1 186 } 187 if ok == 1 { 188 j = 0 189 while j < key_len { 190 if buf[e.key_off + j] != key[j] { 191 ok = 0 192 break 193 } 194 j = j + 1 195 } 196 } 197 if ok == 1 { return i } 198 } 199 } 200 i = i + 1 201 } 202 return -1 203} 204 205// Compile-only smoke. 206func main() -> i64 { 207 let entries_raw: *u8 = sys_mmap(INI_MAGIC_1024) 208 let entries: *IniEntry = entries_raw as *IniEntry 209 210 let input: *u8 = "[db]\nhost = 10.0.0.1\nport = 5432\n; comment\n[log]\nlevel=info\n" 211 // length: "[db]"=4 + LF + "host = 10.0.0.1"=15 + LF + 212 // "port = 5432"=11 + LF + "; comment"=9 + LF + 213 // "[log]"=5 + LF + "level=info"=10 + LF = 58. 214 let n: i64 = ini_parse(input, 58, entries, 16) 215 if n != 3 { return 1 } 216 217 // entries[0] = section "db" key "host" val "10.0.0.1" 218 let e0: *IniEntry = entries 219 if e0.section_len != 2 { return 2 } 220 if e0.key_len != 4 { return 3 } 221 if e0.val_len != 8 { return 4 } 222 if input[e0.section_off] != 0x64 { return 5 } // 'd' 223 if input[e0.key_off] != 0x68 { return 6 } // 'h' 224 if input[e0.val_off] != 0x31 { return 7 } // '1' 225 226 // Lookup "[db]" "port". 227 let idx: i64 = ini_find(input, entries, n, "db", 2, "port", 4) 228 if idx != 1 { return 8 } 229 230 // Lookup miss. 231 if ini_find(input, entries, n, "db", 2, "nope", 4) != -1 { return 9 } 232 return 0 233}