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.
- checksums.yaml +4 -4
- data/README.md +14 -3
- data/ext/schemurai_native/Cargo.lock +99 -0
- data/ext/schemurai_native/Cargo.toml +21 -0
- data/ext/schemurai_native/build.rs +35 -0
- data/ext/schemurai_native/extconf.rb +63 -0
- data/ext/schemurai_native/glue.c +639 -0
- data/ext/schemurai_native/protocol.def +63 -0
- data/ext/schemurai_native/protocol.h +10 -0
- data/ext/schemurai_native/src/content.rs +574 -0
- data/ext/schemurai_native/src/diagnostics.rs +965 -0
- data/ext/schemurai_native/src/format.rs +357 -0
- data/ext/schemurai_native/src/lib.rs +2376 -0
- data/ext/schemurai_native/src/number.rs +1055 -0
- data/ext/schemurai_native/src/pattern.rs +288 -0
- data/ext/schemurai_native/src/scope.rs +210 -0
- data/ext/schemurai_native/src/tracking.rs +532 -0
- data/lib/schemurai/backend.rb +2 -1
- data/lib/schemurai/native/diagnostics.rb +52 -0
- data/lib/schemurai/native/evaluator.rb +32 -0
- data/lib/schemurai/version.rb +1 -1
- data/lib/schemurai/vm/evaluator.rb +28 -32
- data/lib/schemurai.rb +8 -7
- data/sig/schemurai.rbs +1 -1
- metadata +22 -4
|
@@ -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
|
+
}
|