code wiki / (root) / nx_json.nx

nx_json.nx source

↩ module page · 267 lines · 11697 B

1// nx_json.nx -- JSON tokenizer (RFC 8259 subset). 2// 3// Streaming token-stream API: caller drives nx_json_next which 4// reports the next token kind + span within the input buffer. 5// A second pass (or the caller's own state machine) builds the 6// tree. 7// 8// Token kinds: 9// NX_JSON_LBRACE { 10// NX_JSON_RBRACE } 11// NX_JSON_LBRACKET [ 12// NX_JSON_RBRACKET ] 13// NX_JSON_COMMA , 14// NX_JSON_COLON : 15// NX_JSON_STRING "..." (span includes quotes) 16// NX_JSON_NUMBER -123.4e5 (span covers all digits + sign + exp) 17// NX_JSON_TRUE true 18// NX_JSON_FALSE false 19// NX_JSON_NULL null 20// NX_JSON_EOF end of input 21// NX_JSON_ERROR malformed input 22// 23// String content escapes (\n, \t, \uXXXX) are NOT decoded by the 24// tokenizer -- the span includes the raw bytes between the quotes. 25// A separate nx_pct-style decoder can be added later. 26// 27// nx_safety_envelope: 28// intended_use: "JSON decoder -- substrate-native parsing of 29// structured data per cardinal feedback-no-pdfs- 30// no-proprietary-binary-formats" 31// sil_target: SIL2 (input parsing; malformed JSON 32// must not crash callers) 33// asil_target: QM 34// dal_target: DAL C 35// evidence: [RFC_8259_canonical_basis, no_FP, 36// sealed_token_kind_enum, 37// bounded_recursion_depth] 38// hazard_register: [bug-tape-JSON-bomb-via-deep-nesting, 39// bug-tape-billion-laughs-style-amplification, 40// bug-tape-Unicode-escape-handling-bug] 41// residual_risk: "Nesting-depth caller-bounded. No 42// evaluation (no JSON5 / JSONnet); strict 43// RFC 8259 only." 44// verdict: NOT_YET_EVALUATED 45 46import "nx_syscalls.nx" 47import "nx_lex.nx" 48import "nx_ascii.nx" 49 50const NX_JSON_LBRACE: i64 = 1 51const NX_JSON_RBRACE: i64 = 2 52const NX_JSON_LBRACKET: i64 = 3 53const NX_JSON_RBRACKET: i64 = 4 54const NX_JSON_COMMA: i64 = 5 55const NX_JSON_COLON: i64 = 6 56const NX_JSON_STRING: i64 = 7 57const NX_JSON_NUMBER: i64 = 8 58const NX_JSON_TRUE: i64 = 9 59const NX_JSON_FALSE: i64 = 10 60const NX_JSON_NULL: i64 = 11 61const NX_JSON_EOF: i64 = 12 62const NX_JSON_ERROR: i64 = 13 63 64struct NxJsonTok { 65 src: *u8, 66 len: i64, 67 pos: i64, 68 // Last-token output: 69 kind: i64, 70 tok_off: i64, 71 tok_len: i64, 72} 73 74const NX_JSONTOK_BYTES: i64 = 48 75 76func nx_json_new(src: *u8, len: i64) -> *NxJsonTok { 77 let raw: *u8 = sys_mmap(NX_JSONTOK_BYTES) 78 if (raw as i64) <= 0 { return 0 as *NxJsonTok } 79 let t: *NxJsonTok = raw as *NxJsonTok 80 t.src = src 81 t.len = len 82 t.pos = 0 83 t.kind = 0 84 t.tok_off = 0 85 t.tok_len = 0 86 return t 87} 88 89// is_ws canonical in nx_lex.nx. 90 91// is_digit canonical in nx_ascii.nx (nx_ascii_is_digit). 92 93// Skip whitespace from current pos. 94func nx_json_skip_ws(t: *NxJsonTok) -> i64 { 95 while t.pos < t.len { 96 let c: i64 = t.src[t.pos] as i64 97 var ws: i64 = 0 98 if c == 32 { ws = 1 } 99 if c == 9 { ws = 1 } 100 if c == 10 { ws = 1 } 101 if c == 13 { ws = 1 } 102 if ws == 0 { return 0 } 103 t.pos = t.pos + 1 104 } 105 return 0 106} 107// Match a literal at current pos. Returns 1 on match (advances 108// pos), 0 otherwise. 109func nx_json_lit(t: *NxJsonTok, lit: *u8, n: i64) -> i64 { 110 if t.pos + n > t.len { return 0 } 111 var i: i64 = 0 112 while i < n { 113 if t.src[t.pos + i] != lit[i] { return 0 } 114 i = i + 1 115 } 116 t.pos = t.pos + n 117 return 1 118} 119 120// Read next token. Returns the token kind (also stored in t.kind). 121// On STRING / NUMBER, t.tok_off + t.tok_len describe the span. 122func nx_json_next(t: *NxJsonTok) -> i64 { 123 nx_json_skip_ws(t) 124 if t.pos >= t.len { t.kind = NX_JSON_EOF; return NX_JSON_EOF } 125 let start: i64 = t.pos 126 let c: i64 = t.src[start] as i64 127 t.tok_off = start 128 t.tok_len = 1 129 if c == 123 { t.pos = start + 1; t.kind = NX_JSON_LBRACE; return t.kind } 130 if c == 125 { t.pos = start + 1; t.kind = NX_JSON_RBRACE; return t.kind } 131 if c == 91 { t.pos = start + 1; t.kind = NX_JSON_LBRACKET; return t.kind } 132 if c == 93 { t.pos = start + 1; t.kind = NX_JSON_RBRACKET; return t.kind } 133 if c == 44 { t.pos = start + 1; t.kind = NX_JSON_COMMA; return t.kind } 134 if c == 58 { t.pos = start + 1; t.kind = NX_JSON_COLON; return t.kind } 135 if c == 34 { 136 var p: i64 = start + 1 137 while p < t.len { 138 let ch: i64 = t.src[p] as i64 139 if ch == 34 { t.pos = p + 1; t.tok_len = t.pos - start; t.kind = NX_JSON_STRING; return t.kind } 140 if ch < 32 { t.kind = NX_JSON_ERROR; return t.kind } 141 if ch == 92 { 142 p = p + 1 143 if p >= t.len { t.kind = NX_JSON_ERROR; return t.kind } 144 let e: i64 = t.src[p] as i64 145 var valid: i64 = 0 146 if e == 34 { valid = 1 } 147 if e == 92 { valid = 1 } 148 if e == 47 { valid = 1 } 149 if e == 98 { valid = 1 } 150 if e == 102 { valid = 1 } 151 if e == 110 { valid = 1 } 152 if e == 114 { valid = 1 } 153 if e == 116 { valid = 1 } 154 if e == 117 { 155 if t.len - p <= 4 { t.kind = NX_JSON_ERROR; return t.kind } 156 var h: i64 = 1 157 while h <= 4 { 158 let d: i64 = t.src[p+h] as i64 159 var hex: i64 = 0 160 if d >= 48 { if d <= 57 { hex = 1 } } 161 if d >= 65 { if d <= 70 { hex = 1 } } 162 if d >= 97 { if d <= 102 { hex = 1 } } 163 if hex == 0 { t.kind = NX_JSON_ERROR; return t.kind } 164 h = h + 1 165 } 166 p = p + 4 167 valid = 1 168 } 169 if valid == 0 { t.kind = NX_JSON_ERROR; return t.kind } 170 } 171 p = p + 1 172 } 173 t.kind = NX_JSON_ERROR; return t.kind 174 } 175 // Literal constants replace per-token anonymous mappings in the daemon hot path. 176 if c == 116 { if nx_json_lit(t, "true" as *u8, 4) == 1 { t.tok_len = 4; t.kind = NX_JSON_TRUE; return t.kind } t.kind = NX_JSON_ERROR; return t.kind } 177 if c == 102 { if nx_json_lit(t, "false" as *u8, 5) == 1 { t.tok_len = 5; t.kind = NX_JSON_FALSE; return t.kind } t.kind = NX_JSON_ERROR; return t.kind } 178 if c == 110 { if nx_json_lit(t, "null" as *u8, 4) == 1 { t.tok_len = 4; t.kind = NX_JSON_NULL; return t.kind } t.kind = NX_JSON_ERROR; return t.kind } 179 var p: i64 = start 180 if c == 45 { p = p + 1 } 181 if p >= t.len { t.kind = NX_JSON_ERROR; return t.kind } 182 if nx_ascii_is_digit(t.src[p]) == 0 { t.kind = NX_JSON_ERROR; return t.kind } 183 if t.src[p] == (48 as u8) { 184 p = p + 1 185 if p < t.len { if nx_ascii_is_digit(t.src[p]) == 1 { t.kind = NX_JSON_ERROR; return t.kind } } 186 } else { 187 while p < t.len { if nx_ascii_is_digit(t.src[p]) == 0 { break } p = p + 1 } 188 } 189 if p < t.len { if t.src[p] == (46 as u8) { 190 p = p + 1 191 let fs: i64 = p 192 while p < t.len { if nx_ascii_is_digit(t.src[p]) == 0 { break } p = p + 1 } 193 if p == fs { t.kind = NX_JSON_ERROR; return t.kind } 194 } } 195 if p < t.len { 196 var exponent: i64 = 0 197 if t.src[p] == (101 as u8) { exponent = 1 } 198 if t.src[p] == (69 as u8) { exponent = 1 } 199 if exponent == 1 { 200 p = p + 1 201 if p < t.len { if t.src[p] == (43 as u8) { p = p + 1 } else { if t.src[p] == (45 as u8) { p = p + 1 } } } 202 let es: i64 = p 203 while p < t.len { if nx_ascii_is_digit(t.src[p]) == 0 { break } p = p + 1 } 204 if p == es { t.kind = NX_JSON_ERROR; return t.kind } 205 } 206 } 207 t.pos = p; t.tok_len = p - start; t.kind = NX_JSON_NUMBER; return t.kind 208} 209 210// ---- self-test --------------------------------------------------- 211 212func main() -> i64 { 213 // Input: {"name":"foo","count":42,"active":true,"empty":null,"items":[1,2,3]} 214 let s: *u8 = sys_mmap(128) 215 s[0]=0x7B; s[1]=0x22; s[2]=0x6E; s[3]=0x61; s[4]=0x6D 216 s[5]=0x65; s[6]=0x22; s[7]=0x3A; s[8]=0x22; s[9]=0x66 217 s[10]=0x6F; s[11]=0x6F; s[12]=0x22; s[13]=0x2C; s[14]=0x22 218 s[15]=0x63; s[16]=0x6F; s[17]=0x75; s[18]=0x6E; s[19]=0x74 219 s[20]=0x22; s[21]=0x3A; s[22]=0x34; s[23]=0x32; s[24]=0x2C 220 s[25]=0x22; s[26]=0x61; s[27]=0x63; s[28]=0x74; s[29]=0x69 221 s[30]=0x76; s[31]=0x65; s[32]=0x22; s[33]=0x3A; s[34]=0x74 222 s[35]=0x72; s[36]=0x75; s[37]=0x65; s[38]=0x2C; s[39]=0x22 223 s[40]=0x65; s[41]=0x6D; s[42]=0x70; s[43]=0x74; s[44]=0x79 224 s[45]=0x22; s[46]=0x3A; s[47]=0x6E; s[48]=0x75; s[49]=0x6C 225 s[50]=0x6C; s[51]=0x2C; s[52]=0x22; s[53]=0x69; s[54]=0x74 226 s[55]=0x65; s[56]=0x6D; s[57]=0x73; s[58]=0x22; s[59]=0x3A 227 s[60]=0x5B; s[61]=0x31; s[62]=0x2C; s[63]=0x32; s[64]=0x2C 228 s[65]=0x33; s[66]=0x5D; s[67]=0x7D 229 230 let t: *NxJsonTok = nx_json_new(s, 68) 231 232 if nx_json_next(t) != NX_JSON_LBRACE { return __syscall(93, 1, 0, 0, 0, 0, 0) } 233 234 if nx_json_next(t) != NX_JSON_STRING { return __syscall(93, 2, 0, 0, 0, 0, 0) } 235 if t.tok_len != 6 { return __syscall(93, 3, 0, 0, 0, 0, 0) } // "name" 236 237 if nx_json_next(t) != NX_JSON_COLON { return __syscall(93, 4, 0, 0, 0, 0, 0) } 238 if nx_json_next(t) != NX_JSON_STRING { return __syscall(93, 5, 0, 0, 0, 0, 0) } // "foo" 239 if nx_json_next(t) != NX_JSON_COMMA { return __syscall(93, 6, 0, 0, 0, 0, 0) } 240 if nx_json_next(t) != NX_JSON_STRING { return __syscall(93, 7, 0, 0, 0, 0, 0) } // "count" 241 if nx_json_next(t) != NX_JSON_COLON { return __syscall(93, 8, 0, 0, 0, 0, 0) } 242 if nx_json_next(t) != NX_JSON_NUMBER { return __syscall(93, 9, 0, 0, 0, 0, 0) } // 42 243 if t.tok_len != 2 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 244 if nx_json_next(t) != NX_JSON_COMMA { return __syscall(93, 11, 0, 0, 0, 0, 0) } 245 if nx_json_next(t) != NX_JSON_STRING { return __syscall(93, 12, 0, 0, 0, 0, 0) } // "active" 246 if nx_json_next(t) != NX_JSON_COLON { return __syscall(93, 13, 0, 0, 0, 0, 0) } 247 if nx_json_next(t) != NX_JSON_TRUE { return __syscall(93, 14, 0, 0, 0, 0, 0) } 248 if nx_json_next(t) != NX_JSON_COMMA { return __syscall(93, 15, 0, 0, 0, 0, 0) } 249 if nx_json_next(t) != NX_JSON_STRING { return __syscall(93, 16, 0, 0, 0, 0, 0) } // "empty" 250 if nx_json_next(t) != NX_JSON_COLON { return __syscall(93, 17, 0, 0, 0, 0, 0) } 251 if nx_json_next(t) != NX_JSON_NULL { return __syscall(93, 18, 0, 0, 0, 0, 0) } 252 if nx_json_next(t) != NX_JSON_COMMA { return __syscall(93, 19, 0, 0, 0, 0, 0) } 253 if nx_json_next(t) != NX_JSON_STRING { return __syscall(93, 20, 0, 0, 0, 0, 0) } // "items" 254 if nx_json_next(t) != NX_JSON_COLON { return __syscall(93, 21, 0, 0, 0, 0, 0) } 255 if nx_json_next(t) != NX_JSON_LBRACKET { return __syscall(93, 22, 0, 0, 0, 0, 0) } 256 if nx_json_next(t) != NX_JSON_NUMBER { return __syscall(93, 23, 0, 0, 0, 0, 0) } 257 if nx_json_next(t) != NX_JSON_COMMA { return __syscall(93, 24, 0, 0, 0, 0, 0) } 258 if nx_json_next(t) != NX_JSON_NUMBER { return __syscall(93, 25, 0, 0, 0, 0, 0) } 259 if nx_json_next(t) != NX_JSON_COMMA { return __syscall(93, 26, 0, 0, 0, 0, 0) } 260 if nx_json_next(t) != NX_JSON_NUMBER { return __syscall(93, 27, 0, 0, 0, 0, 0) } 261 if nx_json_next(t) != NX_JSON_RBRACKET { return __syscall(93, 28, 0, 0, 0, 0, 0) } 262 if nx_json_next(t) != NX_JSON_RBRACE { return __syscall(93, 29, 0, 0, 0, 0, 0) } 263 if nx_json_next(t) != NX_JSON_EOF { return __syscall(93, 30, 0, 0, 0, 0, 0) } 264 265 return 0 266} 267