easy-regex-lib 0.1.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.
- package/README.md +546 -0
- package/dist/index.cjs +1037 -0
- package/dist/index.d.cts +308 -0
- package/dist/index.d.ts +308 -0
- package/dist/index.js +972 -0
- package/package.json +39 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,972 @@
|
|
|
1
|
+
// src/ast.ts
|
|
2
|
+
var PATTERN_SCHEMA_VERSION = 1;
|
|
3
|
+
function seq(...children) {
|
|
4
|
+
const flat = flattenSequences(children);
|
|
5
|
+
if (flat.length === 0) return { kind: "Literal", value: "" };
|
|
6
|
+
if (flat.length === 1) return flat[0];
|
|
7
|
+
return { kind: "Sequence", children: flat };
|
|
8
|
+
}
|
|
9
|
+
function alt(...children) {
|
|
10
|
+
const choices = flattenChoices(children);
|
|
11
|
+
if (choices.length === 0) return { kind: "Literal", value: "" };
|
|
12
|
+
if (choices.length === 1) return choices[0];
|
|
13
|
+
return { kind: "Choice", children: choices };
|
|
14
|
+
}
|
|
15
|
+
function repeat(child, min, max, greedy = true) {
|
|
16
|
+
if (min === 0 && max === 1) return { kind: "Optional", child, greedy };
|
|
17
|
+
if (min === 1 && max === 1) return child;
|
|
18
|
+
return { kind: "Repeat", child, min, max, greedy };
|
|
19
|
+
}
|
|
20
|
+
function optional(child, greedy = true) {
|
|
21
|
+
return { kind: "Optional", child, greedy };
|
|
22
|
+
}
|
|
23
|
+
function namedGroup(name, child) {
|
|
24
|
+
return { kind: "NamedGroup", name, child };
|
|
25
|
+
}
|
|
26
|
+
function nonCapturing(child) {
|
|
27
|
+
return { kind: "NonCapturing", child };
|
|
28
|
+
}
|
|
29
|
+
function literal(value) {
|
|
30
|
+
return { kind: "Literal", value };
|
|
31
|
+
}
|
|
32
|
+
function optimize(pattern) {
|
|
33
|
+
return flattenAst(pattern);
|
|
34
|
+
}
|
|
35
|
+
function flattenSequences(children) {
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const c of children) {
|
|
38
|
+
const o = optimize(c);
|
|
39
|
+
if (o.kind === "Literal" && o.value === "") continue;
|
|
40
|
+
if (o.kind === "Sequence") out.push(...o.children.map(optimize));
|
|
41
|
+
else out.push(o);
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
function flattenChoices(children) {
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const c of children) {
|
|
48
|
+
const o = optimize(c);
|
|
49
|
+
if (o.kind === "Choice") out.push(...o.children.map(optimize));
|
|
50
|
+
else out.push(o);
|
|
51
|
+
}
|
|
52
|
+
return dedupeAdjacentDuplicates(out);
|
|
53
|
+
}
|
|
54
|
+
function dedupeAdjacentDuplicates(children) {
|
|
55
|
+
return children;
|
|
56
|
+
}
|
|
57
|
+
function flattenAst(node) {
|
|
58
|
+
switch (node.kind) {
|
|
59
|
+
case "Sequence":
|
|
60
|
+
return seq(...node.children);
|
|
61
|
+
case "Choice":
|
|
62
|
+
return alt(...node.children);
|
|
63
|
+
case "Repeat":
|
|
64
|
+
return {
|
|
65
|
+
...node,
|
|
66
|
+
child: flattenAst(node.child)
|
|
67
|
+
};
|
|
68
|
+
case "Optional":
|
|
69
|
+
return { ...node, child: flattenAst(node.child) };
|
|
70
|
+
case "NamedGroup":
|
|
71
|
+
return { ...node, child: flattenAst(node.child) };
|
|
72
|
+
case "NonCapturing":
|
|
73
|
+
return { ...node, child: flattenAst(node.child) };
|
|
74
|
+
default:
|
|
75
|
+
return node;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/primitives.ts
|
|
80
|
+
function wrap(base) {
|
|
81
|
+
const q = base;
|
|
82
|
+
q.exactly = (n) => repeat(base, n, n);
|
|
83
|
+
q.atLeast = (n) => repeat(base, n, Number.POSITIVE_INFINITY);
|
|
84
|
+
q.atMost = (n) => repeat(base, 0, n);
|
|
85
|
+
q.between = (min, max) => repeat(base, min, max);
|
|
86
|
+
q.oneOrMore = () => repeat(base, 1, Number.POSITIVE_INFINITY);
|
|
87
|
+
q.zeroOrMore = () => repeat(base, 0, Number.POSITIVE_INFINITY);
|
|
88
|
+
q.maybe = () => optional(base);
|
|
89
|
+
return q;
|
|
90
|
+
}
|
|
91
|
+
function anyChar() {
|
|
92
|
+
return wrap({ kind: "Any" });
|
|
93
|
+
}
|
|
94
|
+
function digit(opts = {}) {
|
|
95
|
+
return wrap({ kind: "Digit", unicode: opts.unicode ?? false });
|
|
96
|
+
}
|
|
97
|
+
function word(opts = {}) {
|
|
98
|
+
return wrap({ kind: "Word", unicode: opts.unicode ?? false });
|
|
99
|
+
}
|
|
100
|
+
function whitespace(opts = {}) {
|
|
101
|
+
return wrap({ kind: "Whitespace", unicode: opts.unicode ?? false });
|
|
102
|
+
}
|
|
103
|
+
function letter(opts = {}) {
|
|
104
|
+
return wrap({
|
|
105
|
+
kind: "Letter",
|
|
106
|
+
letterCase: opts.case ?? "both",
|
|
107
|
+
unicode: opts.unicode ?? false
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function hexDigit(opts = {}) {
|
|
111
|
+
const node = opts.uppercaseOnly === void 0 ? { kind: "HexDigit" } : { kind: "HexDigit", uppercaseOnly: opts.uppercaseOnly };
|
|
112
|
+
return wrap(node);
|
|
113
|
+
}
|
|
114
|
+
function start() {
|
|
115
|
+
return { kind: "Start" };
|
|
116
|
+
}
|
|
117
|
+
function end() {
|
|
118
|
+
return { kind: "End" };
|
|
119
|
+
}
|
|
120
|
+
function wordBoundary() {
|
|
121
|
+
return { kind: "WordBoundary" };
|
|
122
|
+
}
|
|
123
|
+
function raw(source, flags) {
|
|
124
|
+
return flags === void 0 ? { kind: "RawRegex", source } : { kind: "RawRegex", source, flags };
|
|
125
|
+
}
|
|
126
|
+
function dash() {
|
|
127
|
+
return literal("-");
|
|
128
|
+
}
|
|
129
|
+
function underscore() {
|
|
130
|
+
return literal("_");
|
|
131
|
+
}
|
|
132
|
+
function dot() {
|
|
133
|
+
return literal(".");
|
|
134
|
+
}
|
|
135
|
+
function integer() {
|
|
136
|
+
return digit().oneOrMore();
|
|
137
|
+
}
|
|
138
|
+
function booleanLiteral() {
|
|
139
|
+
return alt(literal("true"), literal("false"));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/compile.ts
|
|
143
|
+
var META = /* @__PURE__ */ new Set([
|
|
144
|
+
"\\",
|
|
145
|
+
"^",
|
|
146
|
+
"$",
|
|
147
|
+
".",
|
|
148
|
+
"|",
|
|
149
|
+
"?",
|
|
150
|
+
"*",
|
|
151
|
+
"+",
|
|
152
|
+
"(",
|
|
153
|
+
")",
|
|
154
|
+
"[",
|
|
155
|
+
"]",
|
|
156
|
+
"{",
|
|
157
|
+
"}"
|
|
158
|
+
]);
|
|
159
|
+
function escapeLiteral(s) {
|
|
160
|
+
let out = "";
|
|
161
|
+
for (const ch of s) {
|
|
162
|
+
if (META.has(ch)) out += "\\";
|
|
163
|
+
out += ch;
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
function compilePattern(root, options = {}) {
|
|
168
|
+
const ast = optimize(root);
|
|
169
|
+
const warnings = [];
|
|
170
|
+
const flagCtx = mergeFlagCtx(options.flags, ast);
|
|
171
|
+
const frag = emit(ast, {
|
|
172
|
+
nonCapturing: options.nonCapturing ?? false,
|
|
173
|
+
flags: flagCtx,
|
|
174
|
+
warnings
|
|
175
|
+
});
|
|
176
|
+
return {
|
|
177
|
+
pattern: frag.s,
|
|
178
|
+
flags: stringifyFlags(flagCtx),
|
|
179
|
+
warnings
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function toRegExp(root, options = {}) {
|
|
183
|
+
const { pattern, flags } = compilePattern(root, options);
|
|
184
|
+
return new RegExp(pattern, flags);
|
|
185
|
+
}
|
|
186
|
+
function defaultFlags() {
|
|
187
|
+
return {
|
|
188
|
+
ignoreCase: false,
|
|
189
|
+
multiline: false,
|
|
190
|
+
dotAll: false,
|
|
191
|
+
unicode: false,
|
|
192
|
+
global: false,
|
|
193
|
+
sticky: false
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function mergeFlagCtx(base, ast) {
|
|
197
|
+
const f = defaultFlags();
|
|
198
|
+
if (base) applyFlags(f, base);
|
|
199
|
+
inferFlags(ast, f);
|
|
200
|
+
return f;
|
|
201
|
+
}
|
|
202
|
+
function applyFlags(f, p) {
|
|
203
|
+
if (p.ignoreCase) f.ignoreCase = true;
|
|
204
|
+
if (p.multiline) f.multiline = true;
|
|
205
|
+
if (p.dotAll) f.dotAll = true;
|
|
206
|
+
if (p.unicode) f.unicode = true;
|
|
207
|
+
if (p.global) f.global = true;
|
|
208
|
+
if (p.sticky) f.sticky = true;
|
|
209
|
+
}
|
|
210
|
+
function inferFlags(ast, f) {
|
|
211
|
+
walk(ast, (n) => {
|
|
212
|
+
if (n.kind === "Letter" && n.unicode) f.unicode = true;
|
|
213
|
+
if (n.kind === "Digit" && n.unicode) f.unicode = true;
|
|
214
|
+
if (n.kind === "Word" && n.unicode) f.unicode = true;
|
|
215
|
+
if (n.kind === "Whitespace" && n.unicode) f.unicode = true;
|
|
216
|
+
if (n.kind === "RawRegex" && n.flags) applyFlags(f, n.flags);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function walk(p, fn) {
|
|
220
|
+
fn(p);
|
|
221
|
+
switch (p.kind) {
|
|
222
|
+
case "Sequence":
|
|
223
|
+
for (const c of p.children) walk(c, fn);
|
|
224
|
+
break;
|
|
225
|
+
case "Choice":
|
|
226
|
+
for (const c of p.children) walk(c, fn);
|
|
227
|
+
break;
|
|
228
|
+
case "Repeat":
|
|
229
|
+
walk(p.child, fn);
|
|
230
|
+
break;
|
|
231
|
+
case "Optional":
|
|
232
|
+
walk(p.child, fn);
|
|
233
|
+
break;
|
|
234
|
+
case "NamedGroup":
|
|
235
|
+
walk(p.child, fn);
|
|
236
|
+
break;
|
|
237
|
+
case "NonCapturing":
|
|
238
|
+
walk(p.child, fn);
|
|
239
|
+
break;
|
|
240
|
+
default:
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function stringifyFlags(f) {
|
|
245
|
+
let s = "";
|
|
246
|
+
if (f.global) s += "g";
|
|
247
|
+
if (f.ignoreCase) s += "i";
|
|
248
|
+
if (f.multiline) s += "m";
|
|
249
|
+
if (f.dotAll) s += "s";
|
|
250
|
+
if (f.unicode) s += "u";
|
|
251
|
+
if (f.sticky) s += "y";
|
|
252
|
+
return s;
|
|
253
|
+
}
|
|
254
|
+
function emit(node, ctx) {
|
|
255
|
+
switch (node.kind) {
|
|
256
|
+
case "Literal":
|
|
257
|
+
return { s: escapeLiteral(node.value), prec: 3 /* Atom */ };
|
|
258
|
+
case "Any":
|
|
259
|
+
return { s: ".", prec: 3 /* Atom */ };
|
|
260
|
+
case "Digit":
|
|
261
|
+
return {
|
|
262
|
+
s: node.unicode ? "\\d" : "\\d",
|
|
263
|
+
prec: 3 /* Atom */
|
|
264
|
+
};
|
|
265
|
+
case "Word":
|
|
266
|
+
return { s: "\\w", prec: 3 /* Atom */ };
|
|
267
|
+
case "Whitespace":
|
|
268
|
+
return { s: "\\s", prec: 3 /* Atom */ };
|
|
269
|
+
case "Letter": {
|
|
270
|
+
if (node.unicode) {
|
|
271
|
+
ctx.flags.unicode = true;
|
|
272
|
+
if (node.letterCase === "lower") return { s: "\\p{Ll}", prec: 3 /* Atom */ };
|
|
273
|
+
if (node.letterCase === "upper") return { s: "\\p{Lu}", prec: 3 /* Atom */ };
|
|
274
|
+
return { s: "\\p{L}", prec: 3 /* Atom */ };
|
|
275
|
+
}
|
|
276
|
+
if (node.letterCase === "lower") return { s: "[a-z]", prec: 3 /* Atom */ };
|
|
277
|
+
if (node.letterCase === "upper") return { s: "[A-Z]", prec: 3 /* Atom */ };
|
|
278
|
+
return { s: "[A-Za-z]", prec: 3 /* Atom */ };
|
|
279
|
+
}
|
|
280
|
+
case "HexDigit":
|
|
281
|
+
return {
|
|
282
|
+
s: node.uppercaseOnly ? "[0-9A-F]" : "[0-9A-Fa-f]",
|
|
283
|
+
prec: 3 /* Atom */
|
|
284
|
+
};
|
|
285
|
+
case "Start":
|
|
286
|
+
return { s: "^", prec: 3 /* Atom */ };
|
|
287
|
+
case "End":
|
|
288
|
+
return { s: "$", prec: 3 /* Atom */ };
|
|
289
|
+
case "WordBoundary":
|
|
290
|
+
return { s: "\\b", prec: 3 /* Atom */ };
|
|
291
|
+
case "RawRegex":
|
|
292
|
+
ctx.warnings.push({
|
|
293
|
+
code: "raw-regex",
|
|
294
|
+
message: "Raw regex fragment embedded \u2014 review for ReDoS and engine-specific behavior."
|
|
295
|
+
});
|
|
296
|
+
if (node.flags) applyFlags(ctx.flags, node.flags);
|
|
297
|
+
return { s: node.source, prec: 3 /* Atom */ };
|
|
298
|
+
case "Sequence": {
|
|
299
|
+
if (node.children.length === 0) return { s: "", prec: 3 /* Atom */ };
|
|
300
|
+
let s = "";
|
|
301
|
+
for (const c of node.children) {
|
|
302
|
+
const f = emit(c, ctx);
|
|
303
|
+
s += wrapPrec(f, 1 /* Seq */).s;
|
|
304
|
+
}
|
|
305
|
+
return { s, prec: 1 /* Seq */ };
|
|
306
|
+
}
|
|
307
|
+
case "Choice": {
|
|
308
|
+
const parts = node.children.map((c) => {
|
|
309
|
+
const f = emit(c, ctx);
|
|
310
|
+
return wrapPrec(f, 0 /* Alt */).s;
|
|
311
|
+
});
|
|
312
|
+
return { s: parts.join("|"), prec: 0 /* Alt */ };
|
|
313
|
+
}
|
|
314
|
+
case "Repeat": {
|
|
315
|
+
const inner = emit(node.child, ctx);
|
|
316
|
+
const core = wrapPrec(inner, 2 /* Quant */);
|
|
317
|
+
const q = quantifier(node.min, node.max, node.greedy);
|
|
318
|
+
return { s: `${core.s}${q}`, prec: 2 /* Quant */ };
|
|
319
|
+
}
|
|
320
|
+
case "Optional": {
|
|
321
|
+
const inner = emit(node.child, ctx);
|
|
322
|
+
const core = wrapPrec(inner, 2 /* Quant */);
|
|
323
|
+
return { s: `${core.s}?${node.greedy ? "" : "?"}`, prec: 2 /* Quant */ };
|
|
324
|
+
}
|
|
325
|
+
case "NamedGroup": {
|
|
326
|
+
const inner = emit(node.child, ctx);
|
|
327
|
+
const body = wrapPrec(inner, 1 /* Seq */);
|
|
328
|
+
if (ctx.nonCapturing) {
|
|
329
|
+
return { s: `(?:${body.s})`, prec: 3 /* Atom */ };
|
|
330
|
+
}
|
|
331
|
+
return { s: `(?<${escapeGroupName(node.name)}>${body.s})`, prec: 3 /* Atom */ };
|
|
332
|
+
}
|
|
333
|
+
case "NonCapturing": {
|
|
334
|
+
const inner = emit(node.child, ctx);
|
|
335
|
+
return { s: `(?:${inner.s})`, prec: 3 /* Atom */ };
|
|
336
|
+
}
|
|
337
|
+
default: {
|
|
338
|
+
const _exhaustive = node;
|
|
339
|
+
return _exhaustive;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function escapeGroupName(name) {
|
|
344
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
345
|
+
throw new Error(`Invalid named group identifier: ${JSON.stringify(name)}`);
|
|
346
|
+
}
|
|
347
|
+
return name;
|
|
348
|
+
}
|
|
349
|
+
function quantifier(min, max, greedy) {
|
|
350
|
+
const lazy = greedy ? "" : "?";
|
|
351
|
+
if (min === 0 && max === Number.POSITIVE_INFINITY) return `*${lazy}`;
|
|
352
|
+
if (min === 1 && max === Number.POSITIVE_INFINITY) return `+${lazy}`;
|
|
353
|
+
if (min === 0 && max === 1) return `?${lazy}`;
|
|
354
|
+
if (min === max) return `{${min}}${lazy}`;
|
|
355
|
+
if (max === Number.POSITIVE_INFINITY) return `{${min},}${lazy}`;
|
|
356
|
+
return `{${min},${max}}${lazy}`;
|
|
357
|
+
}
|
|
358
|
+
function wrapPrec(f, parent) {
|
|
359
|
+
if (f.prec < parent) {
|
|
360
|
+
return { s: `(?:${f.s})`, prec: 3 /* Atom */ };
|
|
361
|
+
}
|
|
362
|
+
return f;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/explain.ts
|
|
366
|
+
var clauseCounter = 0;
|
|
367
|
+
function nextId() {
|
|
368
|
+
clauseCounter += 1;
|
|
369
|
+
return `c${clauseCounter}`;
|
|
370
|
+
}
|
|
371
|
+
function explainPattern(root) {
|
|
372
|
+
clauseCounter = 0;
|
|
373
|
+
const ast = optimize(root);
|
|
374
|
+
const clauses = explainNode(ast);
|
|
375
|
+
return {
|
|
376
|
+
clauses,
|
|
377
|
+
summary: clauses.map((c) => c.text).join(" ")
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
function explainNode(node) {
|
|
381
|
+
switch (node.kind) {
|
|
382
|
+
case "Literal":
|
|
383
|
+
return [
|
|
384
|
+
{
|
|
385
|
+
id: nextId(),
|
|
386
|
+
text: node.value === "" ? "Matches an empty fragment." : `Matches the literal ${quote(node.value)}.`
|
|
387
|
+
}
|
|
388
|
+
];
|
|
389
|
+
case "Any":
|
|
390
|
+
return [{ id: nextId(), text: "Matches any single character (except line terminators unless dotAll)." }];
|
|
391
|
+
case "Digit":
|
|
392
|
+
return [{ id: nextId(), text: node.unicode ? "Matches a Unicode digit." : "Matches an ASCII digit." }];
|
|
393
|
+
case "Word":
|
|
394
|
+
return [{ id: nextId(), text: "Matches a word character." }];
|
|
395
|
+
case "Whitespace":
|
|
396
|
+
return [{ id: nextId(), text: "Matches whitespace." }];
|
|
397
|
+
case "Letter": {
|
|
398
|
+
if (node.unicode) {
|
|
399
|
+
if (node.letterCase === "lower") return [{ id: nextId(), text: "Matches a Unicode lowercase letter." }];
|
|
400
|
+
if (node.letterCase === "upper") return [{ id: nextId(), text: "Matches a Unicode uppercase letter." }];
|
|
401
|
+
return [{ id: nextId(), text: "Matches a Unicode letter." }];
|
|
402
|
+
}
|
|
403
|
+
if (node.letterCase === "lower") return [{ id: nextId(), text: "Matches an ASCII lowercase letter." }];
|
|
404
|
+
if (node.letterCase === "upper") return [{ id: nextId(), text: "Matches an ASCII uppercase letter." }];
|
|
405
|
+
return [{ id: nextId(), text: "Matches an ASCII letter." }];
|
|
406
|
+
}
|
|
407
|
+
case "HexDigit":
|
|
408
|
+
return [
|
|
409
|
+
{
|
|
410
|
+
id: nextId(),
|
|
411
|
+
text: node.uppercaseOnly ? "Matches a hexadecimal digit (0-9A-F)." : "Matches a hexadecimal digit (0-9A-Fa-f)."
|
|
412
|
+
}
|
|
413
|
+
];
|
|
414
|
+
case "Start":
|
|
415
|
+
return [{ id: nextId(), text: "Must align with the start of the string (or line if multiline)." }];
|
|
416
|
+
case "End":
|
|
417
|
+
return [{ id: nextId(), text: "Must align with the end of the string (or line if multiline)." }];
|
|
418
|
+
case "WordBoundary":
|
|
419
|
+
return [{ id: nextId(), text: "Requires a word boundary." }];
|
|
420
|
+
case "RawRegex":
|
|
421
|
+
return [
|
|
422
|
+
{
|
|
423
|
+
id: nextId(),
|
|
424
|
+
text: `Uses a raw regex fragment: ${quote(node.source)}.`
|
|
425
|
+
}
|
|
426
|
+
];
|
|
427
|
+
case "Sequence": {
|
|
428
|
+
const out = [];
|
|
429
|
+
for (const c of node.children) out.push(...explainNode(c));
|
|
430
|
+
return out;
|
|
431
|
+
}
|
|
432
|
+
case "Choice": {
|
|
433
|
+
const parts = node.children.map((c) => explainNode(c).map((x) => x.text).join(" "));
|
|
434
|
+
return [
|
|
435
|
+
{
|
|
436
|
+
id: nextId(),
|
|
437
|
+
text: `Matches one of: ${parts.join(" OR ")}.`
|
|
438
|
+
}
|
|
439
|
+
];
|
|
440
|
+
}
|
|
441
|
+
case "Repeat": {
|
|
442
|
+
const inner = explainNode(node.child).map((x) => x.text).join(" ");
|
|
443
|
+
const q = describeQuantifier(node.min, node.max, node.greedy);
|
|
444
|
+
return [{ id: nextId(), text: `${q} ${inner}` }];
|
|
445
|
+
}
|
|
446
|
+
case "Optional": {
|
|
447
|
+
const inner = explainNode(node.child).map((x) => x.text).join(" ");
|
|
448
|
+
const laz = node.greedy ? "" : " (lazy)";
|
|
449
|
+
return [{ id: nextId(), text: `Optionally${laz}: ${inner}` }];
|
|
450
|
+
}
|
|
451
|
+
case "NamedGroup": {
|
|
452
|
+
const inner = explainNode(node.child).map((x) => x.text).join(" ");
|
|
453
|
+
return [{ id: nextId(), text: `Captures "${node.name}" as: ${inner}` }];
|
|
454
|
+
}
|
|
455
|
+
case "NonCapturing": {
|
|
456
|
+
return explainNode(node.child);
|
|
457
|
+
}
|
|
458
|
+
default: {
|
|
459
|
+
const _never = node;
|
|
460
|
+
return _never;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function quote(s) {
|
|
465
|
+
return JSON.stringify(s);
|
|
466
|
+
}
|
|
467
|
+
function describeQuantifier(min, max, greedy) {
|
|
468
|
+
const laz = greedy ? "" : "lazy ";
|
|
469
|
+
if (min === 0 && max === Number.POSITIVE_INFINITY) return `Repeat ${laz}zero or more times:`;
|
|
470
|
+
if (min === 1 && max === Number.POSITIVE_INFINITY) return `Repeat ${laz}one or more times:`;
|
|
471
|
+
if (min === 0 && max === 1) return `At most once${greedy ? "" : " (lazy)"}:`;
|
|
472
|
+
if (min === max) return `Repeat exactly ${min} times:`;
|
|
473
|
+
if (max === Number.POSITIVE_INFINITY) return `Repeat at least ${min} times:`;
|
|
474
|
+
return `Repeat between ${min} and ${max} times:`;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/diagnose.ts
|
|
478
|
+
function diagnose(root, input, options = {}) {
|
|
479
|
+
const ast = optimize(root);
|
|
480
|
+
const { pattern, flags } = compilePattern(ast, options);
|
|
481
|
+
const anchored = new RegExp(`^(?:${pattern})$`, flags);
|
|
482
|
+
const m = anchored.exec(input);
|
|
483
|
+
if (m) {
|
|
484
|
+
const groups = m.groups ?? {};
|
|
485
|
+
return { ok: true, match: m[0], index: 0, groups };
|
|
486
|
+
}
|
|
487
|
+
const sim = simulate(ast, input, 0, options);
|
|
488
|
+
if (sim.ok) {
|
|
489
|
+
if (sim.end === input.length) {
|
|
490
|
+
return {
|
|
491
|
+
ok: false,
|
|
492
|
+
index: 0,
|
|
493
|
+
message: "Simulation matched the full input but the anchored RegExp did not \u2014 check lazy quantifiers, ambiguous alternatives, or raw fragments.",
|
|
494
|
+
expected: explainPattern(ast).summary
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
ok: false,
|
|
499
|
+
index: sim.end,
|
|
500
|
+
message: "The pattern matched only a prefix of the input.",
|
|
501
|
+
expected: explainPattern(ast).summary
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
ok: false,
|
|
506
|
+
index: sim.at,
|
|
507
|
+
message: "The pattern did not match.",
|
|
508
|
+
expected: sim.expected
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function simulate(node, input, pos, options) {
|
|
512
|
+
switch (node.kind) {
|
|
513
|
+
case "Sequence": {
|
|
514
|
+
let p = pos;
|
|
515
|
+
for (const child of node.children) {
|
|
516
|
+
const r = simulate(child, input, p, options);
|
|
517
|
+
if (!r.ok) return r;
|
|
518
|
+
p = r.end;
|
|
519
|
+
}
|
|
520
|
+
return { ok: true, end: p };
|
|
521
|
+
}
|
|
522
|
+
case "NonCapturing":
|
|
523
|
+
return simulate(node.child, input, pos, options);
|
|
524
|
+
case "NamedGroup":
|
|
525
|
+
return simulate(node.child, input, pos, options);
|
|
526
|
+
case "Choice": {
|
|
527
|
+
let lastFail = null;
|
|
528
|
+
for (const child of node.children) {
|
|
529
|
+
const r = simulate(child, input, pos, options);
|
|
530
|
+
if (r.ok) return r;
|
|
531
|
+
lastFail = r;
|
|
532
|
+
}
|
|
533
|
+
return lastFail ?? {
|
|
534
|
+
ok: false,
|
|
535
|
+
at: pos,
|
|
536
|
+
expected: explainPattern(node).summary
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
case "Optional": {
|
|
540
|
+
const tryMatch = matchPrefix(node.child, input, pos, options);
|
|
541
|
+
if (!tryMatch) return { ok: true, end: pos };
|
|
542
|
+
return { ok: true, end: pos + tryMatch.len };
|
|
543
|
+
}
|
|
544
|
+
case "Repeat": {
|
|
545
|
+
let p = pos;
|
|
546
|
+
let count = 0;
|
|
547
|
+
while (count < node.min) {
|
|
548
|
+
const m = matchPrefix(node.child, input, p, options);
|
|
549
|
+
if (!m) {
|
|
550
|
+
return {
|
|
551
|
+
ok: false,
|
|
552
|
+
at: p,
|
|
553
|
+
expected: explainPattern(node.child).summary
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
p += m.len;
|
|
557
|
+
count += 1;
|
|
558
|
+
}
|
|
559
|
+
if (node.greedy) {
|
|
560
|
+
while (count < node.max) {
|
|
561
|
+
const m = matchPrefix(node.child, input, p, options);
|
|
562
|
+
if (!m) break;
|
|
563
|
+
p += m.len;
|
|
564
|
+
count += 1;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return { ok: true, end: p };
|
|
568
|
+
}
|
|
569
|
+
default: {
|
|
570
|
+
const m = matchPrefix(node, input, pos, options);
|
|
571
|
+
if (!m) {
|
|
572
|
+
return {
|
|
573
|
+
ok: false,
|
|
574
|
+
at: pos,
|
|
575
|
+
expected: explainPattern(node).summary
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
return { ok: true, end: pos + m.len };
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function matchPrefix(node, input, pos, options) {
|
|
583
|
+
const { pattern, flags } = compilePattern(node, {
|
|
584
|
+
...options,
|
|
585
|
+
nonCapturing: true
|
|
586
|
+
});
|
|
587
|
+
const re = new RegExp(`^(?:${pattern})`, flags);
|
|
588
|
+
const slice = input.slice(pos);
|
|
589
|
+
const m = re.exec(slice);
|
|
590
|
+
if (!m || m.index !== 0) return null;
|
|
591
|
+
return { len: m[0].length };
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// src/analyze.ts
|
|
595
|
+
function analyzePattern(root) {
|
|
596
|
+
const ast = optimize(root);
|
|
597
|
+
const out = [];
|
|
598
|
+
walk2(ast, (n) => {
|
|
599
|
+
if (n.kind === "RawRegex") {
|
|
600
|
+
out.push({
|
|
601
|
+
severity: "warn",
|
|
602
|
+
code: "raw-regex",
|
|
603
|
+
message: "Raw regex fragments bypass semantic guarantees \u2014 audit for ReDoS."
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
if (n.kind === "Repeat") {
|
|
607
|
+
const inner = n.child;
|
|
608
|
+
if (inner.kind === "Repeat" || inner.kind === "Optional" && n.max === Number.POSITIVE_INFINITY) {
|
|
609
|
+
out.push({
|
|
610
|
+
severity: "info",
|
|
611
|
+
code: "nested-quantifier",
|
|
612
|
+
message: "Nested quantifiers can cause catastrophic backtracking \u2014 prefer possessive/atomic patterns when available."
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
return out;
|
|
618
|
+
}
|
|
619
|
+
function walk2(node, fn) {
|
|
620
|
+
fn(node);
|
|
621
|
+
switch (node.kind) {
|
|
622
|
+
case "Sequence":
|
|
623
|
+
for (const c of node.children) walk2(c, fn);
|
|
624
|
+
break;
|
|
625
|
+
case "Choice":
|
|
626
|
+
for (const c of node.children) walk2(c, fn);
|
|
627
|
+
break;
|
|
628
|
+
case "Repeat":
|
|
629
|
+
walk2(node.child, fn);
|
|
630
|
+
break;
|
|
631
|
+
case "Optional":
|
|
632
|
+
walk2(node.child, fn);
|
|
633
|
+
break;
|
|
634
|
+
case "NamedGroup":
|
|
635
|
+
walk2(node.child, fn);
|
|
636
|
+
break;
|
|
637
|
+
case "NonCapturing":
|
|
638
|
+
walk2(node.child, fn);
|
|
639
|
+
break;
|
|
640
|
+
default:
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// src/serialize.ts
|
|
646
|
+
function serializePattern(root) {
|
|
647
|
+
return {
|
|
648
|
+
schemaVersion: PATTERN_SCHEMA_VERSION,
|
|
649
|
+
pattern: toJson(optimize(root))
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
function deserializePattern(data) {
|
|
653
|
+
if (!data || typeof data !== "object") throw new Error("Invalid pattern payload.");
|
|
654
|
+
const obj = data;
|
|
655
|
+
if (obj.schemaVersion !== PATTERN_SCHEMA_VERSION) {
|
|
656
|
+
throw new Error(`Unsupported schemaVersion: ${String(obj.schemaVersion)}`);
|
|
657
|
+
}
|
|
658
|
+
if (!obj.pattern) throw new Error("Missing pattern.");
|
|
659
|
+
return fromJson(obj.pattern);
|
|
660
|
+
}
|
|
661
|
+
function patternToJsonString(root, space) {
|
|
662
|
+
return JSON.stringify(serializePattern(root), null, space);
|
|
663
|
+
}
|
|
664
|
+
function patternFromJsonString(text) {
|
|
665
|
+
return deserializePattern(JSON.parse(text));
|
|
666
|
+
}
|
|
667
|
+
function toJson(node) {
|
|
668
|
+
switch (node.kind) {
|
|
669
|
+
case "Repeat":
|
|
670
|
+
return {
|
|
671
|
+
kind: "Repeat",
|
|
672
|
+
child: toJson(node.child),
|
|
673
|
+
min: node.min,
|
|
674
|
+
max: node.max === Number.POSITIVE_INFINITY ? "__inf__" : node.max,
|
|
675
|
+
greedy: node.greedy
|
|
676
|
+
};
|
|
677
|
+
case "Sequence":
|
|
678
|
+
return { kind: "Sequence", children: node.children.map(toJson) };
|
|
679
|
+
case "Choice":
|
|
680
|
+
return { kind: "Choice", children: node.children.map(toJson) };
|
|
681
|
+
case "Optional":
|
|
682
|
+
return { kind: "Optional", child: toJson(node.child), greedy: node.greedy };
|
|
683
|
+
case "NamedGroup":
|
|
684
|
+
return { kind: "NamedGroup", name: node.name, child: toJson(node.child) };
|
|
685
|
+
case "NonCapturing":
|
|
686
|
+
return { kind: "NonCapturing", child: toJson(node.child) };
|
|
687
|
+
case "Literal":
|
|
688
|
+
return { kind: "Literal", value: node.value };
|
|
689
|
+
case "Any":
|
|
690
|
+
return { kind: "Any" };
|
|
691
|
+
case "Digit":
|
|
692
|
+
return { kind: "Digit", unicode: node.unicode };
|
|
693
|
+
case "Word":
|
|
694
|
+
return { kind: "Word", unicode: node.unicode };
|
|
695
|
+
case "Whitespace":
|
|
696
|
+
return { kind: "Whitespace", unicode: node.unicode };
|
|
697
|
+
case "Letter":
|
|
698
|
+
return { kind: "Letter", letterCase: node.letterCase, unicode: node.unicode };
|
|
699
|
+
case "HexDigit":
|
|
700
|
+
return node.uppercaseOnly === void 0 ? { kind: "HexDigit" } : { kind: "HexDigit", uppercaseOnly: node.uppercaseOnly };
|
|
701
|
+
case "Start":
|
|
702
|
+
return { kind: "Start" };
|
|
703
|
+
case "End":
|
|
704
|
+
return { kind: "End" };
|
|
705
|
+
case "WordBoundary":
|
|
706
|
+
return { kind: "WordBoundary" };
|
|
707
|
+
case "RawRegex":
|
|
708
|
+
return {
|
|
709
|
+
kind: "RawRegex",
|
|
710
|
+
source: node.source,
|
|
711
|
+
...node.flags ? { flags: node.flags } : {},
|
|
712
|
+
...node.trusted ? { trusted: node.trusted } : {}
|
|
713
|
+
};
|
|
714
|
+
default: {
|
|
715
|
+
const _never = node;
|
|
716
|
+
return _never;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
function fromJson(node) {
|
|
721
|
+
switch (node.kind) {
|
|
722
|
+
case "Repeat":
|
|
723
|
+
return {
|
|
724
|
+
kind: "Repeat",
|
|
725
|
+
child: fromJson(node.child),
|
|
726
|
+
min: node.min,
|
|
727
|
+
max: node.max === "__inf__" ? Number.POSITIVE_INFINITY : node.max,
|
|
728
|
+
greedy: node.greedy
|
|
729
|
+
};
|
|
730
|
+
case "Sequence":
|
|
731
|
+
return { kind: "Sequence", children: node.children.map(fromJson) };
|
|
732
|
+
case "Choice":
|
|
733
|
+
return { kind: "Choice", children: node.children.map(fromJson) };
|
|
734
|
+
case "Optional":
|
|
735
|
+
return { kind: "Optional", child: fromJson(node.child), greedy: node.greedy };
|
|
736
|
+
case "NamedGroup":
|
|
737
|
+
return { kind: "NamedGroup", name: node.name, child: fromJson(node.child) };
|
|
738
|
+
case "NonCapturing":
|
|
739
|
+
return { kind: "NonCapturing", child: fromJson(node.child) };
|
|
740
|
+
case "Literal":
|
|
741
|
+
return { kind: "Literal", value: node.value };
|
|
742
|
+
case "Any":
|
|
743
|
+
return { kind: "Any" };
|
|
744
|
+
case "Digit":
|
|
745
|
+
return { kind: "Digit", unicode: node.unicode };
|
|
746
|
+
case "Word":
|
|
747
|
+
return { kind: "Word", unicode: node.unicode };
|
|
748
|
+
case "Whitespace":
|
|
749
|
+
return { kind: "Whitespace", unicode: node.unicode };
|
|
750
|
+
case "Letter":
|
|
751
|
+
return {
|
|
752
|
+
kind: "Letter",
|
|
753
|
+
letterCase: node.letterCase,
|
|
754
|
+
unicode: node.unicode
|
|
755
|
+
};
|
|
756
|
+
case "HexDigit":
|
|
757
|
+
return node.uppercaseOnly === void 0 ? { kind: "HexDigit" } : { kind: "HexDigit", uppercaseOnly: node.uppercaseOnly };
|
|
758
|
+
case "Start":
|
|
759
|
+
return { kind: "Start" };
|
|
760
|
+
case "End":
|
|
761
|
+
return { kind: "End" };
|
|
762
|
+
case "WordBoundary":
|
|
763
|
+
return { kind: "WordBoundary" };
|
|
764
|
+
case "RawRegex":
|
|
765
|
+
return {
|
|
766
|
+
kind: "RawRegex",
|
|
767
|
+
source: node.source,
|
|
768
|
+
...node.flags ? { flags: node.flags } : {},
|
|
769
|
+
...node.trusted ? { trusted: node.trusted } : {}
|
|
770
|
+
};
|
|
771
|
+
default: {
|
|
772
|
+
const _never = node;
|
|
773
|
+
return _never;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// src/compiled.ts
|
|
779
|
+
function compile(ast, options = {}) {
|
|
780
|
+
return new CompiledPattern(ast, options);
|
|
781
|
+
}
|
|
782
|
+
var CompiledPattern = class {
|
|
783
|
+
constructor(ast, compileOpts = {}) {
|
|
784
|
+
this.ast = ast;
|
|
785
|
+
this.compileOpts = compileOpts;
|
|
786
|
+
}
|
|
787
|
+
ast;
|
|
788
|
+
compileOpts;
|
|
789
|
+
/** Compiled regex source (body only). */
|
|
790
|
+
get source() {
|
|
791
|
+
return compilePattern(this.ast, this.compileOpts).pattern;
|
|
792
|
+
}
|
|
793
|
+
/** Engine flags string, e.g. `"iu"`. */
|
|
794
|
+
get flags() {
|
|
795
|
+
return compilePattern(this.ast, this.compileOpts).flags;
|
|
796
|
+
}
|
|
797
|
+
/** Compiler warnings (for example raw-regex notices). */
|
|
798
|
+
get warnings() {
|
|
799
|
+
return compilePattern(this.ast, this.compileOpts).warnings;
|
|
800
|
+
}
|
|
801
|
+
toRegExp() {
|
|
802
|
+
return toRegExp(this.ast, this.compileOpts);
|
|
803
|
+
}
|
|
804
|
+
test(input) {
|
|
805
|
+
return this.toRegExp().test(input);
|
|
806
|
+
}
|
|
807
|
+
exec(input) {
|
|
808
|
+
return this.toRegExp().exec(input);
|
|
809
|
+
}
|
|
810
|
+
explain() {
|
|
811
|
+
return explainPattern(this.ast);
|
|
812
|
+
}
|
|
813
|
+
diagnose(input) {
|
|
814
|
+
return diagnose(this.ast, input, this.compileOpts);
|
|
815
|
+
}
|
|
816
|
+
analyze() {
|
|
817
|
+
return analyzePattern(this.ast);
|
|
818
|
+
}
|
|
819
|
+
toJSON() {
|
|
820
|
+
return serializePattern(this.ast);
|
|
821
|
+
}
|
|
822
|
+
toJSONString(space) {
|
|
823
|
+
return patternToJsonString(this.ast, space);
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
// src/builder.ts
|
|
828
|
+
var MatchBuilder = class {
|
|
829
|
+
constructor(defaults = {}) {
|
|
830
|
+
this.defaults = defaults;
|
|
831
|
+
}
|
|
832
|
+
defaults;
|
|
833
|
+
parts = [];
|
|
834
|
+
/** Anchor start (`^`). */
|
|
835
|
+
start() {
|
|
836
|
+
this.parts.push(start());
|
|
837
|
+
return this;
|
|
838
|
+
}
|
|
839
|
+
/** Anchor end (`$`). */
|
|
840
|
+
end() {
|
|
841
|
+
this.parts.push(end());
|
|
842
|
+
return this;
|
|
843
|
+
}
|
|
844
|
+
boundary() {
|
|
845
|
+
this.parts.push(wordBoundary());
|
|
846
|
+
return this;
|
|
847
|
+
}
|
|
848
|
+
/** Literal text segment (escaped on compile). */
|
|
849
|
+
text(value) {
|
|
850
|
+
this.parts.push(literal(value));
|
|
851
|
+
return this;
|
|
852
|
+
}
|
|
853
|
+
/** Alias of `text`. */
|
|
854
|
+
literal(value) {
|
|
855
|
+
return this.text(value);
|
|
856
|
+
}
|
|
857
|
+
dash() {
|
|
858
|
+
this.parts.push(dash());
|
|
859
|
+
return this;
|
|
860
|
+
}
|
|
861
|
+
/** Append any composed pattern fragment. */
|
|
862
|
+
take(fragment) {
|
|
863
|
+
this.parts.push(fragment);
|
|
864
|
+
return this;
|
|
865
|
+
}
|
|
866
|
+
digit() {
|
|
867
|
+
this.parts.push(digit());
|
|
868
|
+
return this;
|
|
869
|
+
}
|
|
870
|
+
lettersUpper() {
|
|
871
|
+
this.parts.push(letter({ case: "upper" }));
|
|
872
|
+
return this;
|
|
873
|
+
}
|
|
874
|
+
lettersLower() {
|
|
875
|
+
this.parts.push(letter({ case: "lower" }));
|
|
876
|
+
return this;
|
|
877
|
+
}
|
|
878
|
+
named(name, inner) {
|
|
879
|
+
this.parts.push(namedGroup(name, inner));
|
|
880
|
+
return this;
|
|
881
|
+
}
|
|
882
|
+
build() {
|
|
883
|
+
return seq(...this.parts);
|
|
884
|
+
}
|
|
885
|
+
compile(options = {}) {
|
|
886
|
+
const merged = {
|
|
887
|
+
...options,
|
|
888
|
+
flags: { ...this.defaults.flags, ...options.flags }
|
|
889
|
+
};
|
|
890
|
+
return new CompiledPattern(this.build(), merged);
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
function match(opts) {
|
|
894
|
+
return new MatchBuilder(opts ?? {});
|
|
895
|
+
}
|
|
896
|
+
function regex(opts) {
|
|
897
|
+
return match(opts);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// src/presets.ts
|
|
901
|
+
var presets = {
|
|
902
|
+
/** RFC 4122 UUID (case-insensitive hex). Version nibble not enforced here. */
|
|
903
|
+
uuid() {
|
|
904
|
+
return seq(
|
|
905
|
+
start(),
|
|
906
|
+
hexDigit().exactly(8),
|
|
907
|
+
literal("-"),
|
|
908
|
+
repeat(seq(hexDigit().exactly(4), literal("-")), 3, 3),
|
|
909
|
+
hexDigit().exactly(12),
|
|
910
|
+
end()
|
|
911
|
+
);
|
|
912
|
+
},
|
|
913
|
+
/** Conservative ASCII slug: lowercase letters, digits, single dashes between segments. */
|
|
914
|
+
slug() {
|
|
915
|
+
const segment = repeat(alt(letter({ case: "lower" }), digit()), 1, Number.POSITIVE_INFINITY);
|
|
916
|
+
return seq(
|
|
917
|
+
start(),
|
|
918
|
+
segment,
|
|
919
|
+
repeat(seq(dash(), segment), 0, Number.POSITIVE_INFINITY),
|
|
920
|
+
end()
|
|
921
|
+
);
|
|
922
|
+
},
|
|
923
|
+
hexColor(opts = {}) {
|
|
924
|
+
const modes = [];
|
|
925
|
+
if (opts.short !== false) modes.push(seq(literal("#"), hexDigit().exactly(3)));
|
|
926
|
+
modes.push(seq(literal("#"), hexDigit().exactly(6)));
|
|
927
|
+
if (opts.alpha) modes.push(seq(literal("#"), hexDigit().exactly(8)));
|
|
928
|
+
const body = modes.length === 1 ? modes[0] : alt(...modes);
|
|
929
|
+
return seq(start(), body, end());
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
export {
|
|
933
|
+
CompiledPattern,
|
|
934
|
+
MatchBuilder,
|
|
935
|
+
PATTERN_SCHEMA_VERSION,
|
|
936
|
+
alt,
|
|
937
|
+
analyzePattern,
|
|
938
|
+
anyChar,
|
|
939
|
+
booleanLiteral,
|
|
940
|
+
compile,
|
|
941
|
+
compilePattern,
|
|
942
|
+
dash,
|
|
943
|
+
deserializePattern,
|
|
944
|
+
diagnose,
|
|
945
|
+
digit,
|
|
946
|
+
dot,
|
|
947
|
+
end,
|
|
948
|
+
explainPattern,
|
|
949
|
+
hexDigit,
|
|
950
|
+
integer,
|
|
951
|
+
letter,
|
|
952
|
+
literal,
|
|
953
|
+
match,
|
|
954
|
+
namedGroup,
|
|
955
|
+
nonCapturing,
|
|
956
|
+
optimize,
|
|
957
|
+
optional,
|
|
958
|
+
patternFromJsonString,
|
|
959
|
+
patternToJsonString,
|
|
960
|
+
presets,
|
|
961
|
+
raw,
|
|
962
|
+
regex,
|
|
963
|
+
repeat,
|
|
964
|
+
seq,
|
|
965
|
+
serializePattern,
|
|
966
|
+
start,
|
|
967
|
+
toRegExp,
|
|
968
|
+
underscore,
|
|
969
|
+
whitespace,
|
|
970
|
+
word,
|
|
971
|
+
wordBoundary
|
|
972
|
+
};
|