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