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,329 @@
|
|
|
1
|
+
//! Matches ECMA-262 regular expressions against UTF-8 text, with the semantics JSON Schema requires of the
|
|
2
|
+
//! `pattern` and `patternProperties` keywords and of the `regex` format.
|
|
3
|
+
//!
|
|
4
|
+
//! This is a port of the `ecmaregex` package of the Go module (`src-go/corvus-json-schema/internal/ecmaregex`),
|
|
5
|
+
//! which the Object Pascal and OCaml ports of the evaluator also carry. The files and the names follow the Go
|
|
6
|
+
//! source, so that a fix made in one port can be carried to the others: `parse.rs` is `parse.go`, `compile.rs` is
|
|
7
|
+
//! `compile.go`, `vm.rs` is `vm.go`, `dfa.rs` is `dfa.go`, `charset.rs`, `fold.rs` and `units.rs` are the files of
|
|
8
|
+
//! the same names, and `ucd.rs` with `tables_gen.rs` is the `ucd` package.
|
|
9
|
+
//!
|
|
10
|
+
//! # Unicode mode
|
|
11
|
+
//!
|
|
12
|
+
//! A pattern is read first with the grammar of the ECMA-262 u flag, which is what JSON Schema specifies. A pattern
|
|
13
|
+
//! that is not valid under that grammar is then read with the Annex B grammar of a pattern with no flag, which
|
|
14
|
+
//! accepts what many schemas in the wild contain (identity escapes such as `\&`, a lone brace, legacy octal
|
|
15
|
+
//! escapes). Only a pattern that neither grammar accepts is an error. [`valid`], which backs the `regex` format,
|
|
16
|
+
//! accepts only the u flag grammar.
|
|
17
|
+
//!
|
|
18
|
+
//! A pattern the u flag grammar accepts is matched by Unicode code point over the UTF-8 text. The dot, a class and a
|
|
19
|
+
//! quantifier each see a character beyond the Basic Multilingual Plane as one character, and a surrogate pair
|
|
20
|
+
//! written as two `\u` escapes names one code point.
|
|
21
|
+
//!
|
|
22
|
+
//! A pattern that only the Annex B grammar accepts is matched by UTF-16 code unit, as ECMA-262 matches a pattern
|
|
23
|
+
//! with no u flag. A character beyond the Basic Multilingual Plane is then two characters, a high surrogate and a
|
|
24
|
+
//! low one, to the dot, a class, a quantifier, a group and a backreference. A match can start or end between the
|
|
25
|
+
//! two. Such a character in the pattern is two atoms, and a class range between two of them is an error. Case is
|
|
26
|
+
//! folded by the Canonicalize of a pattern with no u flag. The text stays UTF-8 and no UTF-16 copy of it is made
|
|
27
|
+
//! (see `units.rs`). Only a text with a character beyond the Basic Multilingual Plane is read differently by the
|
|
28
|
+
//! two.
|
|
29
|
+
//!
|
|
30
|
+
//! The text is a `&str`, so it is UTF-8 and cannot hold half of a surrogate pair. A pattern that names one, such
|
|
31
|
+
//! as `\ud800`, matches it only as half of a character and only when the pattern has no u flag.
|
|
32
|
+
//!
|
|
33
|
+
//! JSON Schema gives a pattern no flags, so matching is case sensitive, `^` and `$` match only at the ends of the
|
|
34
|
+
//! text, and the dot excludes the four line terminators. A pattern can change that for part of itself with a
|
|
35
|
+
//! modifier group of ECMAScript 2025, such as `(?i:...)`, `(?m:...)` or `(?s:...)`.
|
|
36
|
+
//!
|
|
37
|
+
//! # The matcher
|
|
38
|
+
//!
|
|
39
|
+
//! Every pattern is compiled for a backtracking matcher. A pattern with no lookaround, no backreference, no
|
|
40
|
+
//! multiline anchor and no case-insensitive word boundary also gets a deterministic automaton over ASCII text when
|
|
41
|
+
//! it is small enough and has no word boundary (see `dfa.rs`), which decides an ASCII text with one table read per
|
|
42
|
+
//! byte. (The Go engine gives such a pattern to the standard library's regexp package as well. In this crate the
|
|
43
|
+
//! `regex` crate has that part, and `pattern.rs` decides which patterns it gets before the engine is asked.)
|
|
44
|
+
//!
|
|
45
|
+
//! The backtracking matcher keeps its choices on an explicit stack that the thread reuses, so a long text cannot
|
|
46
|
+
//! overflow the stack of the thread and a match does not allocate once the stack has grown. Four things keep it
|
|
47
|
+
//! from the time a backtracking matcher can take. A repetition whose body cannot consume a character is run once,
|
|
48
|
+
//! or not at all (see `simplify`), as V8 runs it. A counted loop gives up at a position from which the rest of the
|
|
49
|
+
//! pattern, with the iterations the loop still owes, needs more text than is left (see `set_needs`). A way the next
|
|
50
|
+
//! character of the text rules out is not taken (see `set_quick`), as in V8. And where a pattern has no
|
|
51
|
+
//! backreference the matcher remembers the places from which every way has failed (see `memo` in `vm.rs`), so a
|
|
52
|
+
//! repetition inside a repetition takes time proportional to the pattern times the text. What is left, a group read
|
|
53
|
+
//! back inside a repetition above all, can still take time that doubles with each character, and is ended by the
|
|
54
|
+
//! limit of the match.
|
|
55
|
+
//!
|
|
56
|
+
//! # Limits
|
|
57
|
+
//!
|
|
58
|
+
//! [`Regexp::match_limit`] gives a match a limit of time, and with it a cap on the memory of its backtracking.
|
|
59
|
+
//! [`compile`] refuses a pattern over a limit of size ([`MAX_PATTERN_LENGTH`], [`MAX_NESTING`], [`MAX_SIZE`]) with
|
|
60
|
+
//! a [`TooLargeError`], so that reading and compiling a pattern take bounded time and memory whatever the pattern
|
|
61
|
+
//! holds.
|
|
62
|
+
//!
|
|
63
|
+
//! # Coverage
|
|
64
|
+
//!
|
|
65
|
+
//! The grammar is that of ECMAScript 2025. It includes lookbehind, named groups, group names shared between
|
|
66
|
+
//! separate alternatives, modifier groups, and Unicode property escapes for every property, value and alias
|
|
67
|
+
//! ECMA-262 lists, with Unicode 17 data. The properties and the case folding come from the tables of `ucd.rs` and
|
|
68
|
+
//! never from the standard library or from a dependency, so a pattern matches the same texts whatever built the
|
|
69
|
+
//! program. The v flag (set notation and properties of strings) does not apply, because a JSON Schema pattern has
|
|
70
|
+
//! no flags.
|
|
71
|
+
|
|
72
|
+
mod charset;
|
|
73
|
+
mod compile;
|
|
74
|
+
mod dfa;
|
|
75
|
+
mod fold;
|
|
76
|
+
mod parse;
|
|
77
|
+
mod re2;
|
|
78
|
+
// Generated: it has names of scripts and properties that only the tests read.
|
|
79
|
+
#[allow(dead_code)]
|
|
80
|
+
#[rustfmt::skip]
|
|
81
|
+
mod tables_gen;
|
|
82
|
+
#[cfg(test)]
|
|
83
|
+
mod tests;
|
|
84
|
+
mod ucd;
|
|
85
|
+
mod units;
|
|
86
|
+
mod vm;
|
|
87
|
+
|
|
88
|
+
use std::fmt;
|
|
89
|
+
use std::sync::OnceLock;
|
|
90
|
+
use std::time::Duration;
|
|
91
|
+
|
|
92
|
+
pub(crate) use charset::{CharSet, property_set};
|
|
93
|
+
use parse::Limit;
|
|
94
|
+
pub(crate) use parse::{MAX_NESTING, TooLargeError};
|
|
95
|
+
pub(crate) use re2::MAX_RE2_COST;
|
|
96
|
+
pub(crate) use units::has_supplementary;
|
|
97
|
+
pub(crate) use vm::MatchError;
|
|
98
|
+
|
|
99
|
+
/// A code point, or a UTF-16 code unit of a pattern read without the u flag. It is signed, as the `rune` of the Go
|
|
100
|
+
/// source is, and -1 stands for "none".
|
|
101
|
+
pub(crate) type Rune = i32;
|
|
102
|
+
|
|
103
|
+
/// Why a pattern does not compile.
|
|
104
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
105
|
+
pub(crate) enum Error {
|
|
106
|
+
/// The pattern is over a limit of size. It was not read to its end, so nothing is known of whether it is valid.
|
|
107
|
+
TooLarge(TooLargeError),
|
|
108
|
+
/// The pattern is not valid in either grammar. The message is that of the u flag grammar.
|
|
109
|
+
Invalid(String),
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
impl fmt::Display for Error {
|
|
113
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
114
|
+
match self {
|
|
115
|
+
Error::TooLarge(e) => e.fmt(f),
|
|
116
|
+
Error::Invalid(message) => f.write_str(message),
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
impl std::error::Error for Error {}
|
|
122
|
+
|
|
123
|
+
/// What reading a pattern tells of it, without compiling it.
|
|
124
|
+
#[derive(Clone, Copy, Debug)]
|
|
125
|
+
pub(crate) struct Facts {
|
|
126
|
+
/// The pattern is valid with the u flag. When it is not, it was read with the Annex B grammar.
|
|
127
|
+
pub(crate) unicode: bool,
|
|
128
|
+
/// The pattern has a construct only the backtracking matcher can run.
|
|
129
|
+
pub(crate) needs_backtracking: bool,
|
|
130
|
+
/// The size the parser counted for the pattern (see [`MAX_SIZE`]).
|
|
131
|
+
pub(crate) size: usize,
|
|
132
|
+
/// The number of instructions estimated for the program a linear matcher compiles for the pattern, which stops
|
|
133
|
+
/// counting above [`MAX_RE2_COST`].
|
|
134
|
+
pub(crate) re2_cost: i64,
|
|
135
|
+
/// The number of instructions estimated for the program a linear matcher runs for the pattern (see
|
|
136
|
+
/// `re2::linear_cost`), which is `re2_cost` or less.
|
|
137
|
+
pub(crate) linear_cost: i64,
|
|
138
|
+
/// The memory estimated for the program the `regex` crate compiles for the pattern, in bytes (see
|
|
139
|
+
/// `re2::linear_size`).
|
|
140
|
+
pub(crate) linear_size: i64,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
fn invalid(pattern: &str, msg: &str, pos: usize) -> Error {
|
|
144
|
+
Error::Invalid(format!("ecmaregex: {msg} at offset {pos} in {pattern:?}"))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// Reads a pattern with the u flag grammar and, when that refuses it, with the Annex B grammar.
|
|
148
|
+
fn read(pattern: &str) -> Result<(parse::Node, Facts), Error> {
|
|
149
|
+
let (root, ps, unicode) = match parse::parse(pattern, true) {
|
|
150
|
+
Ok((root, ps)) => (root, ps, true),
|
|
151
|
+
Err(parse::ParseError::TooLarge(large)) => return Err(Error::TooLarge(large)),
|
|
152
|
+
Err(parse::ParseError::Syntax(msg, pos)) => match parse::parse(pattern, false) {
|
|
153
|
+
Ok((root, ps)) => (root, ps, false),
|
|
154
|
+
Err(parse::ParseError::TooLarge(large)) => return Err(Error::TooLarge(large)),
|
|
155
|
+
Err(parse::ParseError::Syntax(..)) => return Err(invalid(pattern, msg, pos)),
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
let facts = Facts {
|
|
159
|
+
unicode,
|
|
160
|
+
needs_backtracking: ps.needs_backtracking,
|
|
161
|
+
size: ps.size,
|
|
162
|
+
re2_cost: re2::re2_cost(&root),
|
|
163
|
+
linear_cost: re2::linear_cost(&root),
|
|
164
|
+
linear_size: re2::linear_size(&root),
|
|
165
|
+
};
|
|
166
|
+
Ok((root, facts))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// The number of groups a pattern may open and still be read on the stack of the thread that asks.
|
|
170
|
+
const SHALLOW: usize = 32;
|
|
171
|
+
|
|
172
|
+
/// The stack of the thread that reads a pattern with more groups than that. The parser, the compiler and the
|
|
173
|
+
/// dropping of the syntax tree go one level deeper for each level of groups, and [`MAX_NESTING`] allows a thousand
|
|
174
|
+
/// levels, which is more than the stack of a thread can be relied on to hold: a thread of a language runtime that
|
|
175
|
+
/// embeds the crate may have a few hundred kilobytes. Only the pages the work touches are used.
|
|
176
|
+
const DEEP_STACK: usize = 64 << 20;
|
|
177
|
+
|
|
178
|
+
/// Runs the reading of a pattern, with everything that walks its syntax tree, where the depth of the tree cannot
|
|
179
|
+
/// overflow the stack. A pattern that opens few groups is shallow, and is read where it is. Any other is read on a
|
|
180
|
+
/// thread of its own with a stack for the deepest nesting the limits allow. (Go grows the stack of a goroutine as
|
|
181
|
+
/// it is needed, so the Go engine has no such step.) `None` when that thread could not be started.
|
|
182
|
+
fn on_a_deep_stack<T: Send>(pattern: &str, work: impl FnOnce() -> T + Send) -> Option<T> {
|
|
183
|
+
if shallow(pattern) {
|
|
184
|
+
return Some(work());
|
|
185
|
+
}
|
|
186
|
+
deep(work)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// Runs work that goes a level deeper for each level of groups of a pattern on a thread with a stack for the
|
|
190
|
+
/// deepest nesting the limits allow. `None` when the thread could not be started.
|
|
191
|
+
pub(crate) fn deep<T: Send>(work: impl FnOnce() -> T + Send) -> Option<T> {
|
|
192
|
+
std::thread::scope(|scope| {
|
|
193
|
+
let thread = std::thread::Builder::new().stack_size(DEEP_STACK).spawn_scoped(scope, work).ok()?;
|
|
194
|
+
match thread.join() {
|
|
195
|
+
Ok(result) => Some(result),
|
|
196
|
+
Err(panic) => std::panic::resume_unwind(panic),
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/// Whether a pattern opens few enough groups to be read on the stack of the thread that asks.
|
|
202
|
+
pub(crate) fn shallow(pattern: &str) -> bool {
|
|
203
|
+
pattern.bytes().filter(|&b| b == b'(').count() <= SHALLOW
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/// The error of a pattern that nests deeply when no thread could be started to read it: the pattern is refused as
|
|
207
|
+
/// one over the limit of nesting, which is the limit the thread is there to serve.
|
|
208
|
+
const NO_DEEP_STACK: Error = Error::TooLarge(TooLargeError { limit: Limit::Nesting, value: MAX_NESTING });
|
|
209
|
+
|
|
210
|
+
/// A compiled ECMA-262 pattern. It is immutable once compiled, and safe to share between threads.
|
|
211
|
+
pub(crate) struct Regexp {
|
|
212
|
+
pattern: String,
|
|
213
|
+
prog: compile::Program,
|
|
214
|
+
/// The pattern was read with the u flag grammar.
|
|
215
|
+
unicode: bool,
|
|
216
|
+
/// The pattern needs no backtracking and is small enough to be tried for an automaton.
|
|
217
|
+
regular: bool,
|
|
218
|
+
/// The automaton of the pattern over ASCII text (see `dfa.rs`), built on the first match. `None` inside when
|
|
219
|
+
/// the pattern has none. The syntax tree is let go of when the pattern is compiled, so the pattern is read
|
|
220
|
+
/// again to build it, which cannot fail.
|
|
221
|
+
dfa: OnceLock<Option<dfa::Dfa>>,
|
|
222
|
+
/// The size the parser counted for the pattern.
|
|
223
|
+
#[cfg(test)]
|
|
224
|
+
size: usize,
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
impl fmt::Debug for Regexp {
|
|
228
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
229
|
+
write!(f, "Regexp({:?})", self.pattern)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/// Compiles an ECMA-262 pattern with the semantics JSON Schema requires. It returns an error for a pattern that is
|
|
234
|
+
/// not valid, and a [`TooLargeError`] for a pattern over a limit of size (see [`MAX_SIZE`]), whether or not it is
|
|
235
|
+
/// valid.
|
|
236
|
+
pub(crate) fn compile(pattern: &str) -> Result<Regexp, Error> {
|
|
237
|
+
on_a_deep_stack(pattern, || {
|
|
238
|
+
let (root, facts) = read(pattern)?;
|
|
239
|
+
let regular = !facts.needs_backtracking && facts.re2_cost <= MAX_RE2_COST;
|
|
240
|
+
let dfa = OnceLock::new();
|
|
241
|
+
if regular && !shallow(pattern) {
|
|
242
|
+
// The automaton is built from the syntax tree, so that of a pattern that nests deeply is built here
|
|
243
|
+
// and not on the stack of the thread that first matches.
|
|
244
|
+
let _ = dfa.set(dfa::build_dfa(&root));
|
|
245
|
+
}
|
|
246
|
+
Ok(Regexp {
|
|
247
|
+
pattern: pattern.to_string(),
|
|
248
|
+
prog: compile::compile_program(root, facts.unicode),
|
|
249
|
+
unicode: facts.unicode,
|
|
250
|
+
regular,
|
|
251
|
+
dfa,
|
|
252
|
+
#[cfg(test)]
|
|
253
|
+
size: facts.size,
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
.unwrap_or(Err(NO_DEEP_STACK))
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
impl Regexp {
|
|
260
|
+
/// Reports whether the pattern matches anywhere in the text (an unanchored search), with no limit on the time
|
|
261
|
+
/// or the memory the match takes. It allocates nothing in the steady state.
|
|
262
|
+
#[cfg(test)]
|
|
263
|
+
pub(crate) fn is_match(&self, text: &str) -> bool {
|
|
264
|
+
self.match_limit(text, None).unwrap_or(false)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/// [`Regexp::is_match`] with a limit on the time the match may take, when `limit` is `Some` and not zero. A
|
|
268
|
+
/// match that reaches the limit is abandoned and the error is [`MatchError::Time`]. So is a match whose
|
|
269
|
+
/// backtracking stack reaches [`MAX_STACK`] entries, and the error is [`MatchError::Memory`]. The result is
|
|
270
|
+
/// then not an answer. The clock is read every 4,096 steps of the backtracking matcher, so a match can go past
|
|
271
|
+
/// the limit by the time those take, which is some tens of microseconds. The automaton takes time proportional
|
|
272
|
+
/// to the text and is not stopped.
|
|
273
|
+
#[inline]
|
|
274
|
+
pub(crate) fn match_limit(&self, text: &str, limit: Option<Duration>) -> Result<bool, MatchError> {
|
|
275
|
+
if self.regular
|
|
276
|
+
&& let Some(dfa) = self.dfa.get_or_init(|| self.build_dfa())
|
|
277
|
+
&& let Some(matched) = dfa.is_match(text.as_bytes())
|
|
278
|
+
{
|
|
279
|
+
return Ok(matched);
|
|
280
|
+
}
|
|
281
|
+
self.prog.is_match(text, limit)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/// The backtracking matcher alone, which the tests use to compare it with the automaton.
|
|
285
|
+
#[cfg(test)]
|
|
286
|
+
pub(crate) fn match_backtrack(&self, text: &str, limit: Option<Duration>) -> Result<bool, MatchError> {
|
|
287
|
+
self.prog.is_match(text, limit)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/// Builds the automaton of the pattern, which opens few groups (see `compile`).
|
|
291
|
+
#[cold]
|
|
292
|
+
fn build_dfa(&self) -> Option<dfa::Dfa> {
|
|
293
|
+
let (root, _) = parse::parse(&self.pattern, self.unicode).ok()?;
|
|
294
|
+
dfa::build_dfa(&root)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/// Whether the pattern is valid with the ECMA-262 u flag. When it is not, the pattern was read with the Annex B
|
|
298
|
+
/// grammar.
|
|
299
|
+
#[cfg(test)]
|
|
300
|
+
pub(crate) fn unicode(&self) -> bool {
|
|
301
|
+
self.unicode
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/// The size the parser counted for the pattern, which the tree, the program and the time to make both are
|
|
305
|
+
/// proportional to (see [`MAX_SIZE`]).
|
|
306
|
+
#[cfg(test)]
|
|
307
|
+
pub(crate) fn size(&self) -> usize {
|
|
308
|
+
self.size
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/// The source of the pattern.
|
|
312
|
+
#[cfg(test)]
|
|
313
|
+
pub(crate) fn as_str(&self) -> &str {
|
|
314
|
+
&self.pattern
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/// Reports whether the pattern is a valid ECMA-262 pattern (for `format: regex`). It applies the grammar of the u
|
|
319
|
+
/// flag. A pattern over a limit of size is not read to its end and is reported as not valid.
|
|
320
|
+
pub(crate) fn valid(pattern: &str) -> bool {
|
|
321
|
+
on_a_deep_stack(pattern, || parse::parse(pattern, true).is_ok()).unwrap_or(false)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/// Reads the pattern without compiling it. It returns what the parser learnt of it, or the error [`compile`]
|
|
325
|
+
/// would: a [`TooLargeError`] for a pattern over a limit of size, and any other error for a pattern that is not
|
|
326
|
+
/// valid.
|
|
327
|
+
pub(crate) fn check(pattern: &str) -> Result<Facts, Error> {
|
|
328
|
+
on_a_deep_stack(pattern, || read(pattern).map(|(_, facts)| facts)).unwrap_or(Err(NO_DEEP_STACK))
|
|
329
|
+
}
|