code wiki / (root) / nx_kv.nx

nx_kv.nx source

↩ module page · 219 lines · 7506 B

1// nx_kv.nx -- key=value (.env / dotenv-style) parser. 2// 3// Parses input of the form: 4// 5// # comment 6// KEY=value 7// ANOTHER_KEY=other value 8// QUOTED="value with spaces" 9// SINGLE='also fine' 10// 11// Returns one entry at a time via a streaming iterator. No 12// multiline values, no variable interpolation, no escape sequences 13// in this commit; those land if a real .env consumer requires them. 14// 15// Used by: 16// - Service config loaders (NX_HOST=... NX_PORT=...) 17// - Build-tool .env file consumers 18// - Test-harness fixture loading 19 20// nx_safety_envelope: 21// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 22// sil_target: SIL1 23// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 24// verdict: NOT_YET_EVALUATED 25 26import "syscalls.nx" 27import "nx_lex.nx" 28 29struct NxKvIter { 30 src: *u8, 31 len: i64, 32 pos: i64, 33 // Last entry's spans into `src`. Both NUL-terminated via 34 // separate scratch buffers since src is read-only. 35 key_off: i64, key_len: i64, 36 val_off: i64, val_len: i64, 37} 38 39const NX_KVITER_BYTES: i64 = 56 40 41func nx_kv_new(src: *u8, len: i64) -> *NxKvIter { 42 let raw: *u8 = sys_mmap(NX_KVITER_BYTES) 43 let it: *NxKvIter = raw as *NxKvIter 44 it.src = src 45 it.len = len 46 it.pos = 0 47 it.key_off = 0; it.key_len = 0 48 it.val_off = 0; it.val_len = 0 49 return it 50} 51 52// is_intraline_ws canonical in nx_lex.nx (KV deliberately treats newline 53// as record separator, NOT whitespace -- distinct from nx_lex_is_ws). 54 55func nx_kv_is_eol(c: i64) -> i64 { 56 if c == 0x0A { return 1 } 57 if c == 0x0D { return 1 } 58 return 0 59} 60 61// Skip whitespace + blank lines + comments at current position. 62func nx_kv_skip_blanks(it: *NxKvIter) -> i64 { 63 var done: i64 = 0 64 while done == 0 { 65 if it.pos >= it.len { done = 1 } 66 else { 67 let c: i64 = it.src[it.pos] 68 if nx_lex_is_intraline_ws(c) == 1 { it.pos = it.pos + 1 } 69 else { if nx_kv_is_eol(c) == 1 { it.pos = it.pos + 1 } 70 else { 71 if c == 0x23 { // '#' comment -- skip to EOL 72 while it.pos < it.len { 73 if nx_kv_is_eol(it.src[it.pos]) == 1 { it.pos = it.len + 1 } 74 else { it.pos = it.pos + 1 } 75 } 76 if it.pos > it.len { it.pos = it.pos - 1 - it.len } 77 // recover index after sentinel break 78 var stop: i64 = 0 79 while stop == 0 { 80 if it.pos >= it.len { stop = 1 } 81 else { 82 if nx_kv_is_eol(it.src[it.pos]) == 1 { stop = 1 } 83 else { it.pos = it.pos + 1 } 84 } 85 } 86 } else { 87 done = 1 88 } 89 } 90 } 91 } 92 } 93 return 0 94} 95 96// Read next key=value entry. Returns: 97// 1 = entry available; key/val spans populated 98// 0 = end of input 99// -1 = malformed 100func nx_kv_next(it: *NxKvIter) -> i64 { 101 nx_kv_skip_blanks(it) 102 if it.pos >= it.len { return 0 } 103 104 // Key = identifier-ish chars until '='. 105 it.key_off = it.pos 106 var k_end: i64 = it.pos 107 var stop_k: i64 = 0 108 while stop_k == 0 { 109 if k_end >= it.len { return -1 } 110 let c: i64 = it.src[k_end] 111 if c == 0x3D { stop_k = 1 } 112 else { if nx_kv_is_eol(c) == 1 { return -1 } 113 else { k_end = k_end + 1 } 114 } 115 } 116 it.key_len = k_end - it.key_off 117 if it.key_len == 0 { return -1 } 118 it.pos = k_end + 1 // past '=' 119 120 // Value: until EOL. If first char is ' or ", read until matching. 121 if it.pos >= it.len { 122 it.val_off = it.pos 123 it.val_len = 0 124 return 1 125 } 126 let first: i64 = it.src[it.pos] 127 if first == 0x22 { 128 it.pos = it.pos + 1 129 it.val_off = it.pos 130 var stop_v: i64 = 0 131 while stop_v == 0 { 132 if it.pos >= it.len { return -1 } 133 if it.src[it.pos] == 0x22 { stop_v = 1 } 134 else { it.pos = it.pos + 1 } 135 } 136 it.val_len = it.pos - it.val_off 137 it.pos = it.pos + 1 // past closing " 138 } else { 139 if first == 0x27 { 140 it.pos = it.pos + 1 141 it.val_off = it.pos 142 var stop_v2: i64 = 0 143 while stop_v2 == 0 { 144 if it.pos >= it.len { return -1 } 145 if it.src[it.pos] == 0x27 { stop_v2 = 1 } 146 else { it.pos = it.pos + 1 } 147 } 148 it.val_len = it.pos - it.val_off 149 it.pos = it.pos + 1 150 } else { 151 it.val_off = it.pos 152 var stop_v3: i64 = 0 153 while stop_v3 == 0 { 154 if it.pos >= it.len { stop_v3 = 1 } 155 else { 156 if nx_kv_is_eol(it.src[it.pos]) == 1 { stop_v3 = 1 } 157 else { it.pos = it.pos + 1 } 158 } 159 } 160 it.val_len = it.pos - it.val_off 161 } 162 } 163 return 1 164} 165 166// ---- self-test --------------------------------------------------- 167 168func main() -> i64 { 169 // Input: 170 // # config 171 // HOST=localhost 172 // PORT=8080 173 // NAME="quoted value" 174 // 175 let s: *u8 = sys_mmap(128) 176 s[0]=0x23; s[1]=0x20; s[2]=0x63; s[3]=0x66; s[4]=0x67; s[5]=0x0A 177 s[6]=0x48; s[7]=0x4F; s[8]=0x53; s[9]=0x54; s[10]=0x3D 178 s[11]=0x6C; s[12]=0x6F; s[13]=0x63; s[14]=0x61; s[15]=0x6C 179 s[16]=0x68; s[17]=0x6F; s[18]=0x73; s[19]=0x74; s[20]=0x0A 180 s[21]=0x50; s[22]=0x4F; s[23]=0x52; s[24]=0x54; s[25]=0x3D 181 s[26]=0x38; s[27]=0x30; s[28]=0x38; s[29]=0x30; s[30]=0x0A 182 s[31]=0x4E; s[32]=0x41; s[33]=0x4D; s[34]=0x45; s[35]=0x3D 183 s[36]=0x22; s[37]=0x71; s[38]=0x75; s[39]=0x6F; s[40]=0x74 184 s[41]=0x65; s[42]=0x64; s[43]=0x20; s[44]=0x76; s[45]=0x61 185 s[46]=0x6C; s[47]=0x75; s[48]=0x65; s[49]=0x22; s[50]=0x0A 186 let total: i64 = 51 187 188 let it: *NxKvIter = nx_kv_new(s, total) 189 190 // Entry 1: HOST=localhost 191 let r1: i64 = nx_kv_next(it) 192 if r1 != 1 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 193 if it.key_len != 4 { return __syscall(93, 2, 0, 0, 0, 0, 0) } 194 if s[it.key_off] != 0x48 { return __syscall(93, 3, 0, 0, 0, 0, 0) } // 'H' 195 if it.val_len != 9 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 196 if s[it.val_off] != 0x6C { return __syscall(93, 5, 0, 0, 0, 0, 0) } // 'l' 197 198 // Entry 2: PORT=8080 199 let r2: i64 = nx_kv_next(it) 200 if r2 != 1 { return __syscall(93, 6, 0, 0, 0, 0, 0) } 201 if it.key_len != 4 { return __syscall(93, 7, 0, 0, 0, 0, 0) } 202 if s[it.key_off] != 0x50 { return __syscall(93, 8, 0, 0, 0, 0, 0) } // 'P' 203 if it.val_len != 4 { return __syscall(93, 9, 0, 0, 0, 0, 0) } 204 if s[it.val_off] != 0x38 { return __syscall(93, 10, 0, 0, 0, 0, 0) } // '8' 205 206 // Entry 3: NAME="quoted value" -- value should be 12 chars (no quotes) 207 let r3: i64 = nx_kv_next(it) 208 if r3 != 1 { return __syscall(93, 11, 0, 0, 0, 0, 0) } 209 if it.key_len != 4 { return __syscall(93, 12, 0, 0, 0, 0, 0) } 210 if it.val_len != 12 { return __syscall(93, 13, 0, 0, 0, 0, 0) } 211 if s[it.val_off] != 0x71 { return __syscall(93, 14, 0, 0, 0, 0, 0) } // 'q' 212 if s[it.val_off + 6] != 0x20 { return __syscall(93, 15, 0, 0, 0, 0, 0) } // ' ' 213 214 // EOI. 215 let r4: i64 = nx_kv_next(it) 216 if r4 != 0 { return __syscall(93, 16, 0, 0, 0, 0, 0) } 217 218 return 0 219}