schemurai 2.2.2 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,63 @@
1
+ /* Shared C/Rust/Ruby native protocol. Keep wire values stable. */
2
+ SN_OPCODE(BOOLEAN, boolean, 0)
3
+ SN_OPCODE(TYPE_NULL, type_null, 1)
4
+ SN_OPCODE(TYPE_BOOLEAN, type_boolean, 2)
5
+ SN_OPCODE(TYPE_OBJECT, type_object, 3)
6
+ SN_OPCODE(TYPE_ARRAY, type_array, 4)
7
+ SN_OPCODE(TYPE_NUMBER, type_number, 5)
8
+ SN_OPCODE(TYPE_INTEGER, type_integer, 6)
9
+ SN_OPCODE(TYPE_STRING, type_string, 7)
10
+ SN_OPCODE(TYPES, types, 8)
11
+ SN_OPCODE(CONST, const, 9)
12
+ SN_OPCODE(ENUM, enum, 10)
13
+ SN_OPCODE(ALL_OF, allOf, 11)
14
+ SN_OPCODE(ANY_OF, anyOf, 12)
15
+ SN_OPCODE(ONE_OF, oneOf, 13)
16
+ SN_OPCODE(NOT, not, 14)
17
+ SN_OPCODE(CONDITIONAL, conditional, 15)
18
+ SN_OPCODE(NUMBER, number, 16)
19
+ SN_OPCODE(TYPED_NUMBER, typed_number, 17)
20
+ SN_OPCODE(TYPED_INTEGER, typed_integer, 18)
21
+ SN_OPCODE(STRING, string, 19)
22
+ SN_OPCODE(TYPED_STRING, typed_string, 20)
23
+ SN_OPCODE(ARRAY, array, 21)
24
+ SN_OPCODE(TYPED_ARRAY, typed_array, 22)
25
+ SN_OPCODE(OBJECT, object, 23)
26
+ SN_OPCODE(TYPED_OBJECT, typed_object, 24)
27
+ SN_OPCODE(REF, ref, 25)
28
+ SN_OPCODE(RECURSIVE_REF, recursive_ref, 26)
29
+ SN_OPCODE(DYNAMIC_REF, dynamic_ref, 27)
30
+
31
+ /* Match VM::TypeRules and VM::NumberRules bit masks. */
32
+ SN_CONSTANT(TYPE_NULL, 1)
33
+ SN_CONSTANT(TYPE_BOOLEAN, 2)
34
+ SN_CONSTANT(TYPE_OBJECT, 4)
35
+ SN_CONSTANT(TYPE_ARRAY, 8)
36
+ SN_CONSTANT(TYPE_NUMBER, 16)
37
+ SN_CONSTANT(TYPE_INTEGER, 32)
38
+ SN_CONSTANT(TYPE_STRING, 64)
39
+ SN_CONSTANT(TYPE_INTEGRAL_NUMBER, 48)
40
+ SN_CONSTANT(TYPE_UNSUPPORTED, 128)
41
+ SN_CONSTANT(MAXIMUM, 1)
42
+ SN_CONSTANT(MINIMUM, 2)
43
+ SN_CONSTANT(EXCLUSIVE_MAXIMUM, 4)
44
+ SN_CONSTANT(EXCLUSIVE_MINIMUM, 8)
45
+ SN_CONSTANT(MULTIPLE_OF, 16)
46
+ SN_CONSTANT(TRACKS_EVALUATION, 1)
47
+ SN_CONSTANT(TRACKS_DYNAMIC_SCOPE, 2)
48
+
49
+ /* Adapter results; comparison/packing reserves 3 beyond ordinary -2..2. */
50
+ SN_CONSTANT(VERDICT_INVALID, 0)
51
+ SN_CONSTANT(VERDICT_VALID, 1)
52
+ SN_CONSTANT(VERDICT_UNSUPPORTED, 2)
53
+ SN_CONSTANT(ADAPTER_UNSUPPORTED, 3)
54
+ SN_CONSTANT(STATUS_PANIC, -1)
55
+ SN_CONSTANT(STATUS_EXCEPTION, -2)
56
+ SN_CONSTANT(OPCODE_UNKNOWN, -1)
57
+ SN_CONSTANT(SMALL_HASH_ORDINARY, 16)
58
+ SN_CONSTANT(SMALL_HASH_COUNT_MASK, 15)
59
+ SN_CONSTANT(SMALL_HASH_TOO_LARGE, -2)
60
+ SN_CONSTANT(ENCODING_OTHER, 0)
61
+ SN_CONSTANT(ENCODING_UTF8, 1)
62
+ SN_CONSTANT(ENCODING_ASCII, 2)
63
+ SN_CONSTANT(ENCODING_SEVEN_BIT, 3)
@@ -0,0 +1,10 @@
1
+ #ifndef SCHEMURAI_PROTOCOL_H
2
+ #define SCHEMURAI_PROTOCOL_H
3
+ enum {
4
+ #define SN_OPCODE(name, symbol, value) SN_OP_##name = value,
5
+ #define SN_CONSTANT(name, value) SN_##name = value,
6
+ #include "protocol.def"
7
+ #undef SN_OPCODE
8
+ #undef SN_CONSTANT
9
+ };
10
+ #endif
@@ -0,0 +1,574 @@
1
+ //! Content validation borrows Ruby string storage. Base64 decoding uses bounded
2
+ //! stack scratch; JSON scanning never constructs a tree.
3
+ use super::*;
4
+ use std::mem::MaybeUninit;
5
+
6
+ const STACK_BYTES: usize = 384;
7
+ // Full refills must end on a three-byte Base64 group boundary.
8
+ const STREAM_BYTES: usize = 1536;
9
+ const DIRECT_BYTES: usize = 8192;
10
+
11
+ #[derive(Debug)]
12
+ pub(super) struct Content {
13
+ pub(super) base64: bool,
14
+ pub(super) json: bool,
15
+ }
16
+ impl Content {
17
+ pub(super) fn eval_items(&self, required: i32, values: &[Value]) -> Verdict {
18
+ self.visit_invalid_items(required, values, |_, _| Invalid)
19
+ }
20
+ pub(super) fn visit_invalid_items(
21
+ &self,
22
+ required: i32,
23
+ values: &[Value],
24
+ invalid: impl FnMut(usize, Value) -> Verdict,
25
+ ) -> Verdict {
26
+ match (self.base64, self.json) {
27
+ (true, true) => Self::visit_items_impl::<true, true>(required, values, invalid),
28
+ (true, false) => Self::visit_items_impl::<true, false>(required, values, invalid),
29
+ (false, true) => Self::visit_items_impl::<false, true>(required, values, invalid),
30
+ (false, false) => Self::visit_items_impl::<false, false>(required, values, invalid),
31
+ }
32
+ }
33
+ fn visit_items_impl<const BASE64: bool, const JSON: bool>(
34
+ required: i32,
35
+ values: &[Value],
36
+ mut invalid: impl FnMut(usize, Value) -> Verdict,
37
+ ) -> Verdict {
38
+ for (index, &value) in values.iter().enumerate() {
39
+ let mut data = MaybeUninit::uninit();
40
+ let mut len = MaybeUninit::uninit();
41
+ // Plain JSON combines its String guard, byte loan and encoding
42
+ // check. Base64 remains independent of the source encoding.
43
+ let kind = if JSON && !BASE64 {
44
+ unsafe { sn_json_item_view(value, data.as_mut_ptr(), len.as_mut_ptr()) }
45
+ } else {
46
+ unsafe { sn_format_item_view(value, data.as_mut_ptr(), len.as_mut_ptr()) }
47
+ };
48
+ let result = if kind == -TYPE_UNSUPPORTED || (JSON && !BASE64 && kind == ENCODING_OTHER)
49
+ {
50
+ Unsupported
51
+ } else if kind < 0 {
52
+ verdict(required == 0)
53
+ } else {
54
+ // Only the successful String view initializes these outputs.
55
+ let bytes = unsafe { byte_view(data.assume_init(), len.assume_init()) };
56
+ if JSON && !BASE64 {
57
+ if kind == ENCODING_ASCII && !bytes.is_ascii() {
58
+ Unsupported
59
+ } else {
60
+ json(bytes)
61
+ }
62
+ } else {
63
+ Self::eval_impl::<true, BASE64, JSON>(value, bytes, 0)
64
+ }
65
+ };
66
+ match result {
67
+ Unsupported => return Unsupported,
68
+ Invalid => {
69
+ let status = invalid(index, value);
70
+ if status != Valid {
71
+ return status;
72
+ }
73
+ }
74
+ Valid => {}
75
+ }
76
+ }
77
+ Valid
78
+ }
79
+ pub(super) fn eval(&self, value: Value) -> Verdict {
80
+ self.eval_selected::<false>(value, &[], 0)
81
+ }
82
+ pub(super) fn eval_borrowed(&self, value: Value, bytes: &[u8], encoding: i32) -> Verdict {
83
+ self.eval_selected::<true>(value, bytes, encoding)
84
+ }
85
+ fn eval_selected<const BORROWED: bool>(
86
+ &self,
87
+ value: Value,
88
+ bytes: &[u8],
89
+ encoding: i32,
90
+ ) -> Verdict {
91
+ match (self.base64, self.json) {
92
+ (true, true) => Self::eval_impl::<BORROWED, true, true>(value, bytes, encoding),
93
+ (true, false) => Self::eval_impl::<BORROWED, true, false>(value, bytes, encoding),
94
+ (false, true) => Self::eval_impl::<BORROWED, false, true>(value, bytes, encoding),
95
+ (false, false) => Self::eval_impl::<BORROWED, false, false>(value, bytes, encoding),
96
+ }
97
+ }
98
+ fn eval_impl<const BORROWED: bool, const BASE64: bool, const JSON: bool>(
99
+ value: Value,
100
+ bytes: &[u8],
101
+ encoding: i32,
102
+ ) -> Verdict {
103
+ let bytes = if BORROWED {
104
+ bytes
105
+ } else {
106
+ unsafe { byte_view(sn_string_data(value), sn_string_bytes(value)) }
107
+ };
108
+ if BASE64 {
109
+ if !JSON {
110
+ return verdict(base64_simd::STANDARD.check(bytes).is_ok());
111
+ }
112
+ let Ok(length) = base64_simd::STANDARD.decoded_length(bytes) else {
113
+ return Invalid;
114
+ };
115
+ // This scratch is a derived content operand, not a conversion of
116
+ // Ruby values. Short content avoids interleaving decoding with
117
+ // every parser advance; longer content keeps bounded streaming.
118
+ if length < 128 {
119
+ let mut scratch = [MaybeUninit::<u8>::uninit(); STACK_BYTES];
120
+ // Only the crate's successful, initialized output slice is
121
+ // passed to the parser. Failed/short output is never read.
122
+ match base64_simd::STANDARD.decode(
123
+ bytes,
124
+ base64_simd::Out::from_uninit_slice(&mut scratch[..length]),
125
+ ) {
126
+ Ok(decoded) if decoded.len() == length => return json(&*decoded),
127
+ _ => return Invalid,
128
+ }
129
+ }
130
+ if length <= STACK_BYTES {
131
+ return decode_json_medium(bytes, length);
132
+ }
133
+ if length <= DIRECT_BYTES {
134
+ return decode_json_direct(bytes, length);
135
+ }
136
+ decode_json_stream(bytes, length)
137
+ } else {
138
+ if JSON {
139
+ let encoding = match (BORROWED, encoding) {
140
+ (true, ENCODING_UTF8 | ENCODING_SEVEN_BIT) => ENCODING_UTF8,
141
+ (true, ENCODING_ASCII) => ENCODING_ASCII,
142
+ _ => unsafe { sn_json_encoding(value) },
143
+ };
144
+ if encoding == ENCODING_OTHER || (encoding == ENCODING_ASCII && !bytes.is_ascii()) {
145
+ return Unsupported;
146
+ }
147
+ }
148
+ if !JSON {
149
+ return Valid;
150
+ }
151
+ json(bytes)
152
+ }
153
+ }
154
+ }
155
+
156
+ // Keep the larger frame out of the short-content path. This bounded decoded
157
+ // operand avoids repeated decoder setup without exporting an instance tree.
158
+ #[inline(never)]
159
+ fn decode_json_medium(bytes: &[u8], length: usize) -> Verdict {
160
+ let mut scratch = [0; STACK_BYTES + 2];
161
+ decode_json_turbo(bytes, length, &mut scratch)
162
+ }
163
+
164
+ #[inline(never)]
165
+ fn decode_json_direct(bytes: &[u8], length: usize) -> Verdict {
166
+ let mut scratch = [0; DIRECT_BYTES + 2];
167
+ decode_json_turbo(bytes, length, &mut scratch)
168
+ }
169
+
170
+ fn decode_json_turbo(bytes: &[u8], length: usize, scratch: &mut [u8]) -> Verdict {
171
+ // The slice API requires an initialized upper-bound buffer, including
172
+ // up to two padding bytes beyond the exact decoded length. Only the
173
+ // successful written prefix is parsed. This is a derived content operand.
174
+ let capacity = base64_turbo::STANDARD.decoded_len_estimate(bytes.len());
175
+ match base64_turbo::STANDARD.decode_slice(bytes, &mut scratch[..capacity]) {
176
+ Ok(decoded) if decoded == length => json(&scratch[..decoded]),
177
+ _ => Invalid,
178
+ }
179
+ }
180
+
181
+ // Isolate the streaming frame from both direct-decoding paths.
182
+ #[inline(never)]
183
+ fn decode_json_stream(bytes: &[u8], length: usize) -> Verdict {
184
+ let mut input = BufferedBase64 {
185
+ bytes,
186
+ decoded: [0; STREAM_BYTES + 2],
187
+ at: 0,
188
+ len: 0,
189
+ remaining: length,
190
+ valid: true,
191
+ };
192
+ input.fill();
193
+ json(input)
194
+ }
195
+
196
+ // Decode borrowed bytes into the caller's fixed stack scratch. The STANDARD
197
+ // variant also checks each chunk's alphabet, padding and unused trailing bits.
198
+ fn decode_groups(bytes: &[u8], output: &mut [u8], expected: usize) -> bool {
199
+ matches!(
200
+ base64_turbo::STANDARD.decode_slice(bytes, output),
201
+ Ok(decoded) if decoded == expected
202
+ )
203
+ }
204
+ trait Input {
205
+ fn byte(&self, at: usize) -> Option<u8>;
206
+ fn advance(&mut self) {}
207
+ fn string_run(&mut self, at: usize) -> usize;
208
+ fn valid(&self) -> bool {
209
+ true
210
+ }
211
+ }
212
+ impl Input for &[u8] {
213
+ #[inline(always)]
214
+ fn byte(&self, at: usize) -> Option<u8> {
215
+ self.get(at).copied()
216
+ }
217
+ fn string_run(&mut self, at: usize) -> usize {
218
+ let mut consumed = 0;
219
+ let remaining = &self[at..];
220
+ #[cfg(any(
221
+ all(target_arch = "x86_64", target_feature = "sse2"),
222
+ all(
223
+ target_arch = "aarch64",
224
+ target_feature = "neon",
225
+ target_endian = "little"
226
+ )
227
+ ))]
228
+ if remaining.len() >= 24 {
229
+ // Most object keys end within the first word. Preserve that
230
+ // inexpensive path before preparing the vector scanner.
231
+ let special = string_special_word(remaining[..8].try_into().unwrap());
232
+ if special != 0 {
233
+ return special.trailing_zeros() as usize / 8;
234
+ }
235
+ consumed = 8;
236
+ #[cfg(all(
237
+ target_arch = "aarch64",
238
+ target_feature = "neon",
239
+ target_endian = "little"
240
+ ))]
241
+ for bytes in remaining[consumed..].as_chunks::<16>().0 {
242
+ use core::arch::aarch64::{
243
+ vceqq_u8, vcltq_u8, vdupq_n_u8, vget_lane_u64, vld1q_u8, vorrq_u8,
244
+ vreinterpret_u64_u8, vreinterpretq_u16_u8, vshrn_n_u16,
245
+ };
246
+ // NEON is enabled for this target. Each unaligned load stays
247
+ // within a complete chunk. Comparisons produce 0xff per special
248
+ // byte; narrowing packs each pair into two four-bit flags.
249
+ // Little-endian lane order puts the first byte in the low bits.
250
+ let special = unsafe {
251
+ let word = vld1q_u8(bytes.as_ptr());
252
+ let control = vcltq_u8(word, vdupq_n_u8(32));
253
+ let quote = vceqq_u8(word, vdupq_n_u8(b'"'));
254
+ let escape = vceqq_u8(word, vdupq_n_u8(b'\\'));
255
+ let flags = vorrq_u8(control, vorrq_u8(quote, escape));
256
+ let packed = vshrn_n_u16::<4>(vreinterpretq_u16_u8(flags));
257
+ vget_lane_u64::<0>(vreinterpret_u64_u8(packed))
258
+ };
259
+ if special != 0 {
260
+ return consumed + special.trailing_zeros() as usize / 4;
261
+ }
262
+ consumed += 16;
263
+ }
264
+ #[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
265
+ if remaining.len() >= 40 && std::is_x86_feature_detected!("avx2") {
266
+ // Runtime detection includes OS support for the vector state.
267
+ let (length, stopped) = unsafe { string_run_avx2(&remaining[8..]) };
268
+ consumed += length;
269
+ if stopped {
270
+ return consumed;
271
+ }
272
+ }
273
+ #[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
274
+ for bytes in remaining[consumed..].as_chunks::<16>().0 {
275
+ use core::arch::x86_64::{
276
+ _mm_and_si128, _mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8,
277
+ _mm_or_si128, _mm_set1_epi8, _mm_setzero_si128,
278
+ };
279
+ // The target guarantees SSE2. Each complete chunk provides all
280
+ // sixteen readable bytes, with no alignment requirement. Masking
281
+ // the high three bits detects exactly the unsigned range 0..32.
282
+ let special = unsafe {
283
+ let word = _mm_loadu_si128(bytes.as_ptr().cast());
284
+ let control = _mm_cmpeq_epi8(
285
+ _mm_and_si128(word, _mm_set1_epi8(-32)),
286
+ _mm_setzero_si128(),
287
+ );
288
+ let quote = _mm_cmpeq_epi8(word, _mm_set1_epi8(b'"' as i8));
289
+ let escape = _mm_cmpeq_epi8(word, _mm_set1_epi8(b'\\' as i8));
290
+ _mm_movemask_epi8(_mm_or_si128(control, _mm_or_si128(quote, escape))) as u32
291
+ };
292
+ if special != 0 {
293
+ return consumed + special.trailing_zeros() as usize;
294
+ }
295
+ consumed += 16;
296
+ }
297
+ }
298
+ for bytes in remaining[consumed..].as_chunks::<8>().0 {
299
+ let special = string_special_word(*bytes);
300
+ if special != 0 {
301
+ return consumed + special.trailing_zeros() as usize / 8;
302
+ }
303
+ consumed += 8;
304
+ }
305
+ consumed
306
+ + self[at + consumed..]
307
+ .iter()
308
+ .take_while(|&&byte| byte >= 32 && byte != b'"' && byte != b'\\')
309
+ .count()
310
+ }
311
+ }
312
+ #[inline]
313
+ fn string_special_word(bytes: [u8; 8]) -> u64 {
314
+ let word = u64::from_le_bytes(bytes);
315
+ let quote = word ^ 0x2222_2222_2222_2222;
316
+ let escape = word ^ 0x5c5c_5c5c_5c5c_5c5c;
317
+ // Borrow may flag later bytes too, but the first flagged byte is the
318
+ // first control, quote or escape in little-endian order.
319
+ ((word.wrapping_sub(0x2020_2020_2020_2020) & !word)
320
+ | (quote.wrapping_sub(0x0101_0101_0101_0101) & !quote)
321
+ | (escape.wrapping_sub(0x0101_0101_0101_0101) & !escape))
322
+ & 0x8080_8080_8080_8080
323
+ }
324
+ #[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
325
+ #[target_feature(enable = "avx2")]
326
+ unsafe fn string_run_avx2(bytes: &[u8]) -> (usize, bool) {
327
+ use core::arch::x86_64::{
328
+ _mm256_and_si256, _mm256_cmpeq_epi8, _mm256_loadu_si256, _mm256_movemask_epi8,
329
+ _mm256_or_si256, _mm256_set1_epi8, _mm256_setzero_si256,
330
+ };
331
+ let mut consumed = 0;
332
+ for bytes in bytes.as_chunks::<32>().0 {
333
+ // The caller detected AVX2; every load stays within this complete,
334
+ // initialized chunk. Vector lanes identify unsigned control bytes.
335
+ let special = unsafe {
336
+ let word = _mm256_loadu_si256(bytes.as_ptr().cast());
337
+ let control = _mm256_cmpeq_epi8(
338
+ _mm256_and_si256(word, _mm256_set1_epi8(-32)),
339
+ _mm256_setzero_si256(),
340
+ );
341
+ let quote = _mm256_cmpeq_epi8(word, _mm256_set1_epi8(b'"' as i8));
342
+ let escape = _mm256_cmpeq_epi8(word, _mm256_set1_epi8(b'\\' as i8));
343
+ _mm256_movemask_epi8(_mm256_or_si256(control, _mm256_or_si256(quote, escape))) as u32
344
+ };
345
+ if special != 0 {
346
+ return (consumed + special.trailing_zeros() as usize, true);
347
+ }
348
+ consumed += 32;
349
+ }
350
+ (consumed, false)
351
+ }
352
+ struct BufferedBase64<'a> {
353
+ bytes: &'a [u8],
354
+ decoded: [u8; STREAM_BYTES + 2],
355
+ at: usize,
356
+ len: usize,
357
+ remaining: usize,
358
+ valid: bool,
359
+ }
360
+ impl BufferedBase64<'_> {
361
+ fn fill(&mut self) {
362
+ self.at = 0;
363
+ self.len = self.remaining.min(STREAM_BYTES);
364
+ if self.len == 0 || !self.valid {
365
+ self.len = 0;
366
+ return;
367
+ }
368
+ let encoded = self.len.div_ceil(3) * 4;
369
+ let capacity = base64_turbo::STANDARD.decoded_len_estimate(encoded);
370
+ self.valid = decode_groups(
371
+ &self.bytes[..encoded],
372
+ &mut self.decoded[..capacity],
373
+ self.len,
374
+ );
375
+ self.bytes = &self.bytes[encoded..];
376
+ self.remaining -= self.len;
377
+ if !self.valid {
378
+ self.len = 0;
379
+ }
380
+ }
381
+ }
382
+ impl Input for BufferedBase64<'_> {
383
+ #[inline(always)]
384
+ fn byte(&self, _at: usize) -> Option<u8> {
385
+ self.decoded[..self.len].get(self.at).copied()
386
+ }
387
+ #[inline(always)]
388
+ fn advance(&mut self) {
389
+ self.at += 1;
390
+ if self.at == self.len {
391
+ self.fill();
392
+ }
393
+ }
394
+ fn string_run(&mut self, _at: usize) -> usize {
395
+ let consumed = (&self.decoded[..self.len]).string_run(self.at);
396
+ self.at += consumed;
397
+ if self.at == self.len {
398
+ self.fill();
399
+ }
400
+ consumed
401
+ }
402
+ fn valid(&self) -> bool {
403
+ self.valid
404
+ }
405
+ }
406
+ fn json<I: Input>(input: I) -> Verdict {
407
+ let mut parser = Parser { input, at: 0 };
408
+ let valid = parser.value(0);
409
+ parser.whitespace();
410
+ verdict(valid && parser.peek().is_none() && parser.input.valid())
411
+ }
412
+ struct Parser<I> {
413
+ input: I,
414
+ at: usize,
415
+ }
416
+ impl<I: Input> Parser<I> {
417
+ #[inline(always)]
418
+ fn peek(&self) -> Option<u8> {
419
+ self.input.byte(self.at)
420
+ }
421
+ #[inline(always)]
422
+ fn advance(&mut self) {
423
+ self.at += 1;
424
+ self.input.advance();
425
+ }
426
+ #[inline(always)]
427
+ fn take(&mut self, byte: u8) -> bool {
428
+ if self.peek() == Some(byte) {
429
+ self.advance();
430
+ true
431
+ } else {
432
+ false
433
+ }
434
+ }
435
+ #[inline(always)]
436
+ fn whitespace(&mut self) {
437
+ while matches!(self.peek(), Some(b' ' | b'\t' | b'\r' | b'\n')) {
438
+ self.advance();
439
+ }
440
+ }
441
+ #[inline(always)]
442
+ fn value(&mut self, depth: usize) -> bool {
443
+ self.whitespace();
444
+ match self.peek() {
445
+ Some(b'[') => self.container::<false>(depth),
446
+ Some(b'{') => self.container::<true>(depth),
447
+ Some(b'"') => self.string(),
448
+ Some(b't') => self.literal(b"true"),
449
+ Some(b'f') => self.literal(b"false"),
450
+ Some(b'n') => self.literal(b"null"),
451
+ Some(b'-' | b'0'..=b'9') => self.number(),
452
+ _ => false,
453
+ }
454
+ }
455
+ fn literal(&mut self, bytes: &[u8]) -> bool {
456
+ bytes.iter().all(|&byte| self.take(byte))
457
+ }
458
+ fn container<const OBJECT: bool>(&mut self, depth: usize) -> bool {
459
+ if depth >= 100 {
460
+ return false;
461
+ }
462
+ self.advance();
463
+ let close = if OBJECT { b'}' } else { b']' };
464
+ self.whitespace();
465
+ if self.take(close) {
466
+ return true;
467
+ }
468
+ loop {
469
+ if OBJECT {
470
+ if !self.string() {
471
+ return false;
472
+ }
473
+ self.whitespace();
474
+ if !self.take(b':') {
475
+ return false;
476
+ }
477
+ }
478
+ if !self.value(depth + 1) {
479
+ return false;
480
+ }
481
+ self.whitespace();
482
+ if self.take(close) {
483
+ return true;
484
+ }
485
+ if !self.take(b',') {
486
+ return false;
487
+ }
488
+ self.whitespace();
489
+ }
490
+ }
491
+ fn string(&mut self) -> bool {
492
+ if !self.take(b'"') {
493
+ return false;
494
+ }
495
+ loop {
496
+ self.at += self.input.string_run(self.at);
497
+ match self.peek() {
498
+ None | Some(0..=31) => return false,
499
+ Some(b'"') => {
500
+ self.advance();
501
+ return true;
502
+ }
503
+ Some(b'\\') => {
504
+ self.advance();
505
+ match self.peek() {
506
+ Some(b'"' | b'\\' | b'/' | b'b' | b'f' | b'n' | b'r' | b't') => {
507
+ self.advance()
508
+ }
509
+ Some(b'u') => {
510
+ self.advance();
511
+ let Some(code) = self.hex4() else {
512
+ return false;
513
+ };
514
+ if (0xD800..=0xDBFF).contains(&code) {
515
+ if !self.literal(b"\\u") {
516
+ return false;
517
+ }
518
+ let Some(low) = self.hex4() else {
519
+ return false;
520
+ };
521
+ if !(0xDC00..=0xDFFF).contains(&low) {
522
+ return false;
523
+ }
524
+ }
525
+ }
526
+ _ => return false,
527
+ }
528
+ }
529
+ Some(_) => self.advance(),
530
+ }
531
+ }
532
+ }
533
+ fn hex4(&mut self) -> Option<u32> {
534
+ let mut value = 0;
535
+ for _ in 0..4 {
536
+ let digit = match self.peek()? {
537
+ b'0'..=b'9' => self.peek()? - b'0',
538
+ b'a'..=b'f' => self.peek()? - b'a' + 10,
539
+ b'A'..=b'F' => self.peek()? - b'A' + 10,
540
+ _ => return None,
541
+ };
542
+ value = value * 16 + u32::from(digit);
543
+ self.advance();
544
+ }
545
+ Some(value)
546
+ }
547
+ fn digits(&mut self) -> bool {
548
+ if !self.peek().is_some_and(|byte| byte.is_ascii_digit()) {
549
+ return false;
550
+ }
551
+ while self.peek().is_some_and(|byte| byte.is_ascii_digit()) {
552
+ self.advance();
553
+ }
554
+ true
555
+ }
556
+ fn number(&mut self) -> bool {
557
+ self.take(b'-');
558
+ if !self.take(b'0') && !self.digits() {
559
+ return false;
560
+ }
561
+ if self.take(b'.') && !self.digits() {
562
+ return false;
563
+ }
564
+ if self.take(b'e') || self.take(b'E') {
565
+ if !self.take(b'+') {
566
+ self.take(b'-');
567
+ }
568
+ if !self.digits() {
569
+ return false;
570
+ }
571
+ }
572
+ true
573
+ }
574
+ }