corvus_json_schema 0.1.3 → 0.1.4
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/Cargo.lock +2 -12
- data/README.md +10 -1
- data/THIRD-PARTY-NOTICES.txt +95 -0
- data/VERSIONHISTORY.md +24 -0
- data/ext/corvus_json_schema/Cargo.toml +2 -2
- data/ext/corvus_json_schema/clippy.toml +3 -4
- data/ext/corvus_json_schema/src/lib.rs +42 -19
- data/ext/corvus_json_schema/vendor/corvus-json-schema/Cargo.toml +9 -4
- data/ext/corvus_json_schema/vendor/corvus-json-schema/README.md +87 -4
- data/ext/corvus_json_schema/vendor/corvus-json-schema/THIRD-PARTY-NOTICES.txt +93 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/compiler.rs +88 -96
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/document.rs +2 -2
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/charset.rs +271 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/compile.rs +941 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/dfa.rs +366 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/fold.rs +138 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/mod.rs +329 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/parse.rs +1128 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/re2.rs +129 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/tables_gen.rs +6685 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/tests.rs +1672 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/ucd.rs +309 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/units.rs +165 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/vm.rs +981 -0
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/eval/plan/fused.rs +13 -9
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/eval/plan.rs +12 -11
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/eval.rs +105 -73
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/formats.rs +95 -1
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/lib.rs +165 -30
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/loader.rs +19 -21
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/numbers.rs +1 -1
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/options.rs +199 -4
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/pattern.rs +758 -79
- data/ext/corvus_json_schema/vendor/corvus-json-schema/src/uri.rs +4 -4
- data/lib/corvus_json_schema/version.rb +1 -1
- data/lib/corvus_json_schema.rb +13 -3
- metadata +16 -2
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
//! A deterministic automaton over ASCII text, for the patterns that need no backtracking (`dfa.go`).
|
|
2
|
+
//!
|
|
3
|
+
//! The strings a schema tests are short and nearly always ASCII, and most patterns in schemas are small. For such a
|
|
4
|
+
//! pattern every state set the text can lead to is worked out once, and a match is then one table read per byte.
|
|
5
|
+
//!
|
|
6
|
+
//! The automaton reads bytes below 0x80 only. A text with any other byte is handed back to the backtracking matcher
|
|
7
|
+
//! from the start, so nothing here needs to know UTF-8 or the Unicode classes: a class is its ASCII members. A
|
|
8
|
+
//! pattern with a word boundary, or whose automaton has more states than a byte can number, has no automaton. The
|
|
9
|
+
//! automaton is built on the first match, not when the pattern is compiled, so a schema pays nothing for the
|
|
10
|
+
//! patterns its instances never reach.
|
|
11
|
+
|
|
12
|
+
use std::collections::HashMap;
|
|
13
|
+
|
|
14
|
+
use super::parse::{Node, NodeKind, UNBOUNDED};
|
|
15
|
+
|
|
16
|
+
// The states every automaton has. DEAD: no match can follow. ACCEPT: a match was found. BAIL: the text is not
|
|
17
|
+
// ASCII.
|
|
18
|
+
const DFA_DEAD: u8 = 0;
|
|
19
|
+
const DFA_ACCEPT: u8 = 1;
|
|
20
|
+
const DFA_BAIL: u8 = 2;
|
|
21
|
+
const DFA_FIRST: usize = 3;
|
|
22
|
+
|
|
23
|
+
// An automaton has at most this many states (they are numbered in a byte), built from at most this many states of
|
|
24
|
+
// the pattern, in at most this many steps (a step is one state of the pattern looked at while a set is made). The
|
|
25
|
+
// steps bound what a pattern too large for an automaton costs before that is known, to about a millisecond.
|
|
26
|
+
const MAX_DFA_STATES: usize = 256;
|
|
27
|
+
const MAX_NFA_STATES: usize = 512;
|
|
28
|
+
const MAX_DFA_STEPS: i64 = 400_000;
|
|
29
|
+
|
|
30
|
+
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
31
|
+
enum NfaKind {
|
|
32
|
+
/// Reads one byte of its set and goes to `out`.
|
|
33
|
+
Set,
|
|
34
|
+
/// Goes to `out` and to `alt`, reading nothing.
|
|
35
|
+
Split,
|
|
36
|
+
/// `Bol` and `Eol` go to `out` at the start and at the end of the text.
|
|
37
|
+
Bol,
|
|
38
|
+
Eol,
|
|
39
|
+
Match,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#[derive(Clone, Copy)]
|
|
43
|
+
struct NfaState {
|
|
44
|
+
kind: NfaKind,
|
|
45
|
+
set: [u64; 2],
|
|
46
|
+
out: usize,
|
|
47
|
+
alt: usize,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
impl NfaState {
|
|
51
|
+
fn new(kind: NfaKind, out: usize) -> NfaState {
|
|
52
|
+
NfaState { kind, set: [0; 2], out, alt: 0 }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#[derive(Default)]
|
|
57
|
+
struct NfaBuilder {
|
|
58
|
+
states: Vec<NfaState>,
|
|
59
|
+
/// The pattern has something an automaton over bytes cannot decide, or is too large.
|
|
60
|
+
failed: bool,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
impl NfaBuilder {
|
|
64
|
+
fn add(&mut self, s: NfaState) -> usize {
|
|
65
|
+
if self.states.len() >= MAX_NFA_STATES {
|
|
66
|
+
self.failed = true;
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
self.states.push(s);
|
|
70
|
+
self.states.len() - 1
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Adds the states of a node that go on to `next`, and returns the state they start at.
|
|
74
|
+
fn compile(&mut self, n: &Node, mut next: usize) -> usize {
|
|
75
|
+
if self.failed {
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
match n.kind {
|
|
79
|
+
NodeKind::Empty => next,
|
|
80
|
+
NodeKind::Char => {
|
|
81
|
+
let mut set = [0u64; 2];
|
|
82
|
+
if (0..128).contains(&n.r) {
|
|
83
|
+
set[(n.r >> 6) as usize] = 1 << (n.r & 63);
|
|
84
|
+
}
|
|
85
|
+
self.add(NfaState { set, ..NfaState::new(NfaKind::Set, next) })
|
|
86
|
+
}
|
|
87
|
+
NodeKind::Set => self.add(NfaState { set: n.class().ascii, ..NfaState::new(NfaKind::Set, next) }),
|
|
88
|
+
NodeKind::Cat => {
|
|
89
|
+
for sub in n.subs.iter().rev() {
|
|
90
|
+
next = self.compile(sub, next);
|
|
91
|
+
}
|
|
92
|
+
next
|
|
93
|
+
}
|
|
94
|
+
NodeKind::Alt => {
|
|
95
|
+
let mut start = self.compile(&n.subs[n.subs.len() - 1], next);
|
|
96
|
+
for sub in n.subs[..n.subs.len() - 1].iter().rev() {
|
|
97
|
+
let out = self.compile(sub, next);
|
|
98
|
+
start = self.add(NfaState { alt: start, ..NfaState::new(NfaKind::Split, out) });
|
|
99
|
+
}
|
|
100
|
+
start
|
|
101
|
+
}
|
|
102
|
+
NodeKind::Group => self.compile(&n.subs[0], next),
|
|
103
|
+
NodeKind::Repeat => {
|
|
104
|
+
let sub = &n.subs[0];
|
|
105
|
+
if n.max == UNBOUNDED {
|
|
106
|
+
// The loop, then the copies that must match before it.
|
|
107
|
+
let loop_ = self.add(NfaState { alt: next, ..NfaState::new(NfaKind::Split, 0) });
|
|
108
|
+
if self.failed {
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
let out = self.compile(sub, loop_);
|
|
112
|
+
self.states[loop_].out = out;
|
|
113
|
+
next = loop_;
|
|
114
|
+
} else {
|
|
115
|
+
// The copies that may match, innermost last.
|
|
116
|
+
let mut i = n.min;
|
|
117
|
+
while i < n.max && !self.failed {
|
|
118
|
+
let out = self.compile(sub, next);
|
|
119
|
+
next = self.add(NfaState { alt: next, ..NfaState::new(NfaKind::Split, out) });
|
|
120
|
+
i += 1;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
let mut i = 0;
|
|
124
|
+
while i < n.min && !self.failed {
|
|
125
|
+
next = self.compile(sub, next);
|
|
126
|
+
i += 1;
|
|
127
|
+
}
|
|
128
|
+
next
|
|
129
|
+
}
|
|
130
|
+
NodeKind::Bol => self.add(NfaState::new(NfaKind::Bol, next)),
|
|
131
|
+
NodeKind::Eol => self.add(NfaState::new(NfaKind::Eol, next)),
|
|
132
|
+
// A word boundary, or anything that needs the backtracking matcher.
|
|
133
|
+
_ => {
|
|
134
|
+
self.failed = true;
|
|
135
|
+
0
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// The automaton. A state is a byte, a byte of the text is one of a few classes (bytes the pattern does not tell
|
|
142
|
+
/// apart), and `trans[state << shift | class]` is the state after the byte.
|
|
143
|
+
pub(crate) struct Dfa {
|
|
144
|
+
class: [u8; 256],
|
|
145
|
+
trans: Vec<u8>,
|
|
146
|
+
shift: u32,
|
|
147
|
+
start: u8,
|
|
148
|
+
/// For each state: the pattern matches if the text ends there.
|
|
149
|
+
at_end: Vec<bool>,
|
|
150
|
+
/// The pattern matches the empty text.
|
|
151
|
+
empty: bool,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
impl Dfa {
|
|
155
|
+
/// Reports whether the pattern matches the text. `None` when the text is not ASCII, which this automaton does
|
|
156
|
+
/// not decide.
|
|
157
|
+
#[inline]
|
|
158
|
+
pub(crate) fn is_match(&self, text: &[u8]) -> Option<bool> {
|
|
159
|
+
if text.is_empty() {
|
|
160
|
+
return Some(self.empty);
|
|
161
|
+
}
|
|
162
|
+
let mut s = self.start;
|
|
163
|
+
if s == DFA_ACCEPT {
|
|
164
|
+
return Some(true);
|
|
165
|
+
}
|
|
166
|
+
let (trans, shift) = (&self.trans[..], self.shift);
|
|
167
|
+
for &c in text {
|
|
168
|
+
s = trans[(s as usize) << shift | self.class[c as usize] as usize];
|
|
169
|
+
if (s as usize) < DFA_FIRST {
|
|
170
|
+
return if s == DFA_BAIL { None } else { Some(s == DFA_ACCEPT) };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
Some(self.at_end[s as usize])
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// Makes the automaton's states from sets of the pattern's states.
|
|
178
|
+
struct DfaBuilder<'a> {
|
|
179
|
+
nfa: &'a [NfaState],
|
|
180
|
+
// Scratch for closure: the states visited, by generation, and the stack.
|
|
181
|
+
mark: Vec<u32>,
|
|
182
|
+
generation: u32,
|
|
183
|
+
stack: Vec<usize>,
|
|
184
|
+
/// The steps left (see `MAX_DFA_STEPS`).
|
|
185
|
+
steps: i64,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
impl DfaBuilder<'_> {
|
|
189
|
+
/// Adds to `set` every state that reading nothing reaches from the states in `from`, keeping the states that
|
|
190
|
+
/// read a byte, the end anchors still to pass, and the match. A start anchor is passed only `at_start`, and an
|
|
191
|
+
/// end anchor only `at_end`.
|
|
192
|
+
fn closure(&mut self, set: &mut Vec<usize>, from: &[usize], at_start: bool, at_end: bool) {
|
|
193
|
+
self.generation += 1;
|
|
194
|
+
self.stack.clear();
|
|
195
|
+
self.stack.extend_from_slice(from);
|
|
196
|
+
while let Some(i) = self.stack.pop() {
|
|
197
|
+
self.steps -= 1;
|
|
198
|
+
if self.mark[i] == self.generation {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
self.mark[i] = self.generation;
|
|
202
|
+
let s = &self.nfa[i];
|
|
203
|
+
match s.kind {
|
|
204
|
+
NfaKind::Split => {
|
|
205
|
+
self.stack.push(s.out);
|
|
206
|
+
self.stack.push(s.alt);
|
|
207
|
+
}
|
|
208
|
+
NfaKind::Bol => {
|
|
209
|
+
if at_start {
|
|
210
|
+
self.stack.push(s.out);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
NfaKind::Eol => {
|
|
214
|
+
if at_end {
|
|
215
|
+
self.stack.push(s.out);
|
|
216
|
+
} else {
|
|
217
|
+
set.push(i);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
_ => set.push(i),
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
fn has_match(&self, set: &[usize]) -> bool {
|
|
226
|
+
set.iter().any(|&i| self.nfa[i].kind == NfaKind::Match)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/// A set as a map key: its states in increasing order.
|
|
231
|
+
fn set_key(set: &mut [usize]) -> Vec<u16> {
|
|
232
|
+
set.sort_unstable();
|
|
233
|
+
set.iter().map(|&s| s as u16).collect()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/// Builds the automaton of a pattern for an unanchored search, or `None` when it has none.
|
|
237
|
+
pub(crate) fn build_dfa(root: &Node) -> Option<Dfa> {
|
|
238
|
+
let mut nb = NfaBuilder::default();
|
|
239
|
+
let match_ = nb.add(NfaState::new(NfaKind::Match, 0));
|
|
240
|
+
let start = nb.compile(root, match_);
|
|
241
|
+
if nb.failed {
|
|
242
|
+
return None;
|
|
243
|
+
}
|
|
244
|
+
let nfa = &nb.states[..];
|
|
245
|
+
let mut b = DfaBuilder { nfa, mark: vec![0; nfa.len()], generation: 0, stack: Vec::new(), steps: MAX_DFA_STEPS };
|
|
246
|
+
|
|
247
|
+
// The classes of bytes: two bytes are in one class when every set of the pattern has both or neither. Class 0
|
|
248
|
+
// is the bytes that are not ASCII.
|
|
249
|
+
let mut d = Dfa { class: [0; 256], trans: Vec::new(), shift: 0, start: 0, at_end: Vec::new(), empty: false };
|
|
250
|
+
let mut classes = 1usize;
|
|
251
|
+
let mut signatures: HashMap<Vec<u8>, u8> = HashMap::new();
|
|
252
|
+
for c in 0..128usize {
|
|
253
|
+
let mut signature = Vec::with_capacity(nfa.len() / 8 + 1);
|
|
254
|
+
let (mut acc, mut n) = (0u8, 0u8);
|
|
255
|
+
for s in nfa {
|
|
256
|
+
if s.kind != NfaKind::Set {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
acc = acc << 1 | ((s.set[c >> 6] >> (c & 63)) & 1) as u8;
|
|
260
|
+
n += 1;
|
|
261
|
+
if n == 8 {
|
|
262
|
+
signature.push(acc);
|
|
263
|
+
(acc, n) = (0, 0);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
signature.push(acc);
|
|
267
|
+
let class = *signatures.entry(signature).or_insert_with(|| {
|
|
268
|
+
classes += 1;
|
|
269
|
+
(classes - 1) as u8
|
|
270
|
+
});
|
|
271
|
+
d.class[c] = class;
|
|
272
|
+
}
|
|
273
|
+
while 1usize << d.shift < classes {
|
|
274
|
+
d.shift += 1;
|
|
275
|
+
}
|
|
276
|
+
// One byte of each class, to move a set by.
|
|
277
|
+
let mut sample = [0usize; 129];
|
|
278
|
+
for c in (0..128usize).rev() {
|
|
279
|
+
sample[d.class[c] as usize] = c;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// The search is unanchored: a match may start at any position, so after every byte the states the pattern
|
|
283
|
+
// starts in (not at the start of the text) join the set.
|
|
284
|
+
let mut restart = Vec::new();
|
|
285
|
+
b.closure(&mut restart, &[start], false, false);
|
|
286
|
+
let mut at_both = Vec::new();
|
|
287
|
+
b.closure(&mut at_both, &[start], true, true);
|
|
288
|
+
d.empty = b.has_match(&at_both);
|
|
289
|
+
|
|
290
|
+
let mut ids: HashMap<Vec<u16>, u8> = HashMap::new();
|
|
291
|
+
let mut sets: Vec<Vec<usize>> = vec![Vec::new(); DFA_FIRST];
|
|
292
|
+
let stride = 1usize << d.shift;
|
|
293
|
+
d.trans = vec![0; DFA_FIRST * stride];
|
|
294
|
+
d.at_end = vec![false; DFA_FIRST];
|
|
295
|
+
for class in 0..stride {
|
|
296
|
+
d.trans[DFA_ACCEPT as usize * stride + class] = DFA_ACCEPT;
|
|
297
|
+
d.trans[DFA_BAIL as usize * stride + class] = DFA_BAIL;
|
|
298
|
+
}
|
|
299
|
+
// Gives a set its state. `None` when there are too many.
|
|
300
|
+
fn intern(
|
|
301
|
+
b: &mut DfaBuilder<'_>,
|
|
302
|
+
d: &mut Dfa,
|
|
303
|
+
ids: &mut HashMap<Vec<u16>, u8>,
|
|
304
|
+
sets: &mut Vec<Vec<usize>>,
|
|
305
|
+
stride: usize,
|
|
306
|
+
set: &mut [usize],
|
|
307
|
+
) -> Option<u8> {
|
|
308
|
+
if b.has_match(set) {
|
|
309
|
+
return Some(DFA_ACCEPT);
|
|
310
|
+
}
|
|
311
|
+
if set.is_empty() {
|
|
312
|
+
return Some(DFA_DEAD);
|
|
313
|
+
}
|
|
314
|
+
let key = set_key(set);
|
|
315
|
+
if let Some(&id) = ids.get(&key) {
|
|
316
|
+
return Some(id);
|
|
317
|
+
}
|
|
318
|
+
if sets.len() >= MAX_DFA_STATES {
|
|
319
|
+
return None;
|
|
320
|
+
}
|
|
321
|
+
let id = sets.len() as u8;
|
|
322
|
+
ids.insert(key, id);
|
|
323
|
+
sets.push(set.to_vec());
|
|
324
|
+
d.trans.resize(d.trans.len() + stride, 0);
|
|
325
|
+
// At the end of the text the end anchors are passed.
|
|
326
|
+
let ends: Vec<usize> = set.iter().filter(|&&i| b.nfa[i].kind == NfaKind::Eol).map(|&i| b.nfa[i].out).collect();
|
|
327
|
+
let mut closed = Vec::new();
|
|
328
|
+
if !ends.is_empty() {
|
|
329
|
+
b.closure(&mut closed, &ends, false, true);
|
|
330
|
+
}
|
|
331
|
+
d.at_end.push(!ends.is_empty() && b.has_match(&closed));
|
|
332
|
+
Some(id)
|
|
333
|
+
}
|
|
334
|
+
let mut first = Vec::new();
|
|
335
|
+
b.closure(&mut first, &[start], true, false);
|
|
336
|
+
d.start = intern(&mut b, &mut d, &mut ids, &mut sets, stride, &mut first)?;
|
|
337
|
+
let (mut moved, mut next) = (Vec::new(), Vec::new());
|
|
338
|
+
let mut id = DFA_FIRST;
|
|
339
|
+
while id < sets.len() {
|
|
340
|
+
for class in 0..stride {
|
|
341
|
+
if class == 0 || class >= classes {
|
|
342
|
+
// Not ASCII (or no such class).
|
|
343
|
+
d.trans[id * stride + class] = DFA_BAIL;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
let c = *sample.get(class).expect("a class has a byte");
|
|
347
|
+
moved.clear();
|
|
348
|
+
for &i in &sets[id] {
|
|
349
|
+
let s = &nfa[i];
|
|
350
|
+
if s.kind == NfaKind::Set && (s.set[c >> 6] >> (c & 63)) & 1 != 0 {
|
|
351
|
+
moved.push(s.out);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
moved.extend_from_slice(&restart);
|
|
355
|
+
next.clear();
|
|
356
|
+
b.closure(&mut next, &moved, false, false);
|
|
357
|
+
let to = intern(&mut b, &mut d, &mut ids, &mut sets, stride, &mut next)?;
|
|
358
|
+
if b.steps < 0 {
|
|
359
|
+
return None;
|
|
360
|
+
}
|
|
361
|
+
d.trans[id * stride + class] = to;
|
|
362
|
+
}
|
|
363
|
+
id += 1;
|
|
364
|
+
}
|
|
365
|
+
Some(d)
|
|
366
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
//! Case-insensitive matching (`fold.go`).
|
|
2
|
+
//!
|
|
3
|
+
//! It exists only inside a modifier group such as `(?i:...)`. ECMA-262 defines it through a Canonicalize function.
|
|
4
|
+
//! Two characters match when they canonicalize to the same character. The function differs between the two
|
|
5
|
+
//! grammars. With the u flag it is Unicode simple case folding. With no flag it is the single character uppercase
|
|
6
|
+
//! mapping of a UTF-16 code unit, which never maps a character outside ASCII into ASCII.
|
|
7
|
+
//!
|
|
8
|
+
//! The parser applies the equivalence to every literal and class as it reads them, so a case-insensitive group is
|
|
9
|
+
//! matched with plain classes. Only a backreference needs the function when matching.
|
|
10
|
+
//!
|
|
11
|
+
//! The folding and the uppercase mapping come from the tables of `ucd`, never from the standard library, so a
|
|
12
|
+
//! pattern matches the same texts whichever toolchain built the program.
|
|
13
|
+
|
|
14
|
+
use std::collections::HashMap;
|
|
15
|
+
use std::sync::{Arc, LazyLock};
|
|
16
|
+
|
|
17
|
+
use super::Rune;
|
|
18
|
+
use super::charset::CharSet;
|
|
19
|
+
use super::ucd;
|
|
20
|
+
|
|
21
|
+
/// The characters of the Basic Multilingual Plane whose uppercase form is more than one character, as inclusive
|
|
22
|
+
/// pairs. Canonicalize leaves them alone.
|
|
23
|
+
static MULTI_UPPER: LazyLock<CharSet> = LazyLock::new(|| {
|
|
24
|
+
CharSet::new(
|
|
25
|
+
&[
|
|
26
|
+
0xDF, 0xDF, 0x149, 0x149, 0x1F0, 0x1F0, 0x390, 0x390, 0x3B0, 0x3B0, 0x587, 0x587, 0x1E96, 0x1E9A, 0x1F50,
|
|
27
|
+
0x1F50, 0x1F52, 0x1F52, 0x1F54, 0x1F54, 0x1F56, 0x1F56, 0x1F80, 0x1FAF, 0x1FB2, 0x1FB4, 0x1FB6, 0x1FB7,
|
|
28
|
+
0x1FBC, 0x1FBC, 0x1FC2, 0x1FC4, 0x1FC6, 0x1FC7, 0x1FCC, 0x1FCC, 0x1FD2, 0x1FD3, 0x1FD6, 0x1FD7, 0x1FE2,
|
|
29
|
+
0x1FE4, 0x1FE6, 0x1FE7, 0x1FF2, 0x1FF4, 0x1FF6, 0x1FF7, 0x1FFC, 0x1FFC, 0xFB00, 0xFB06, 0xFB13, 0xFB17,
|
|
30
|
+
],
|
|
31
|
+
false,
|
|
32
|
+
)
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/// The Canonicalize function of ECMA-262 for case-insensitive matching.
|
|
36
|
+
pub(crate) fn canonicalize(r: Rune, unicode_mode: bool) -> Rune {
|
|
37
|
+
if unicode_mode {
|
|
38
|
+
// Every character of a simple case folding class folds to the same character.
|
|
39
|
+
return ucd::fold(r);
|
|
40
|
+
}
|
|
41
|
+
// A character beyond the Basic Multilingual Plane is two code units, and neither has an uppercase form.
|
|
42
|
+
if r > 0xFFFF || MULTI_UPPER.contains(r) {
|
|
43
|
+
return r;
|
|
44
|
+
}
|
|
45
|
+
let upper = ucd::to_upper(r);
|
|
46
|
+
if r >= 128 && upper < 128 || upper > 0xFFFF {
|
|
47
|
+
return r;
|
|
48
|
+
}
|
|
49
|
+
upper
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// The characters that are equivalent to some other character, each with all the members of its equivalence class.
|
|
53
|
+
pub(crate) struct FoldTable {
|
|
54
|
+
points: Vec<Rune>,
|
|
55
|
+
/// For each point, the number of its class in `classes`.
|
|
56
|
+
class_of: Vec<u32>,
|
|
57
|
+
classes: Vec<Vec<Rune>>,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fn new_fold_table(classes: HashMap<Rune, Vec<Rune>>) -> FoldTable {
|
|
61
|
+
let mut t = FoldTable { points: Vec::new(), class_of: Vec::new(), classes: Vec::new() };
|
|
62
|
+
let mut by_member: HashMap<Rune, u32> = HashMap::new();
|
|
63
|
+
for (_, class) in classes {
|
|
64
|
+
if class.len() < 2 {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
for &member in &class {
|
|
68
|
+
t.points.push(member);
|
|
69
|
+
by_member.insert(member, t.classes.len() as u32);
|
|
70
|
+
}
|
|
71
|
+
t.classes.push(class);
|
|
72
|
+
}
|
|
73
|
+
t.points.sort_unstable();
|
|
74
|
+
t.class_of = t.points.iter().map(|point| by_member[point]).collect();
|
|
75
|
+
t
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static UNICODE_FOLD_TABLE: LazyLock<FoldTable> = LazyLock::new(|| {
|
|
79
|
+
// A class is a character that others fold to, with those others.
|
|
80
|
+
let mut classes: HashMap<Rune, Vec<Rune>> = HashMap::new();
|
|
81
|
+
for r in ucd::folding() {
|
|
82
|
+
let key = ucd::fold(r);
|
|
83
|
+
classes.entry(key).or_insert_with(|| vec![key]).push(r);
|
|
84
|
+
}
|
|
85
|
+
new_fold_table(classes)
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
static LEGACY_FOLD_TABLE: LazyLock<FoldTable> = LazyLock::new(|| {
|
|
89
|
+
let mut classes: HashMap<Rune, Vec<Rune>> = HashMap::new();
|
|
90
|
+
for r in 0..=0xFFFF {
|
|
91
|
+
classes.entry(canonicalize(r, false)).or_default().push(r);
|
|
92
|
+
}
|
|
93
|
+
new_fold_table(classes)
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
/// The table of the grammar in use. It is built on first use, which only a pattern with a case-insensitive group
|
|
97
|
+
/// reaches.
|
|
98
|
+
fn fold_classes(unicode_mode: bool) -> &'static FoldTable {
|
|
99
|
+
if unicode_mode { &UNICODE_FOLD_TABLE } else { &LEGACY_FOLD_TABLE }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// The set of every character equivalent to some member of the set.
|
|
103
|
+
///
|
|
104
|
+
/// It also returns the number of characters with a case variant that the set holds, which is the work it did.
|
|
105
|
+
pub(crate) fn fold_closure(set: &Arc<CharSet>, unicode_mode: bool) -> (Arc<CharSet>, usize) {
|
|
106
|
+
let table = fold_classes(unicode_mode);
|
|
107
|
+
let mut extra: Vec<Rune> = Vec::new();
|
|
108
|
+
let mut variants = 0;
|
|
109
|
+
for &[lo, hi] in set.ranges.as_chunks::<2>().0 {
|
|
110
|
+
let mut k = table.points.partition_point(|&p| p < lo);
|
|
111
|
+
while k < table.points.len() && table.points[k] <= hi {
|
|
112
|
+
variants += 1;
|
|
113
|
+
for &member in &table.classes[table.class_of[k] as usize] {
|
|
114
|
+
if !set.contains(member) {
|
|
115
|
+
extra.push(member);
|
|
116
|
+
extra.push(member);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
k += 1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if extra.is_empty() {
|
|
123
|
+
return (set.clone(), variants);
|
|
124
|
+
}
|
|
125
|
+
extra.extend_from_slice(&set.ranges);
|
|
126
|
+
(CharSet::shared(&extra, false), variants)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// With the u flag and case-insensitive matching, `\w` and `\b` also count the two characters that fold to an ASCII
|
|
130
|
+
/// letter (U+017F, the long s, and U+212A, the Kelvin sign) as word characters.
|
|
131
|
+
const FOLD_WORD_PAIRS: &[Rune] = &[0x30, 0x39, 0x41, 0x5A, 0x5F, 0x5F, 0x61, 0x7A, 0x17F, 0x17F, 0x212A, 0x212A];
|
|
132
|
+
|
|
133
|
+
pub(crate) static FOLD_WORD_SET: LazyLock<Arc<CharSet>> = LazyLock::new(|| CharSet::shared(FOLD_WORD_PAIRS, false));
|
|
134
|
+
pub(crate) static NOT_FOLD_WORD_SET: LazyLock<Arc<CharSet>> = LazyLock::new(|| CharSet::shared(FOLD_WORD_PAIRS, true));
|
|
135
|
+
|
|
136
|
+
pub(crate) fn is_fold_word_rune(r: Rune) -> bool {
|
|
137
|
+
(0..128).contains(&r) && super::charset::is_word_byte(r as u8) || r == 0x17F || r == 0x212A
|
|
138
|
+
}
|