trilean-regex 0.0.0 → 1.0.1
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 +88 -0
- package/dist/ast-D8HLFxxv.d.cts +75 -0
- package/dist/ast-D8HLFxxv.d.ts +75 -0
- package/dist/ast.cjs +14 -0
- package/dist/ast.d.cts +2 -0
- package/dist/ast.d.ts +2 -0
- package/dist/ast.js +12 -0
- package/dist/errors.cjs +19 -0
- package/dist/errors.d.cts +13 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +18 -0
- package/dist/index.cjs +16 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/matcher.cjs +89 -0
- package/dist/matcher.d.cts +31 -0
- package/dist/matcher.d.ts +31 -0
- package/dist/matcher.js +86 -0
- package/dist/nfa.cjs +246 -0
- package/dist/nfa.d.cts +34 -0
- package/dist/nfa.d.ts +34 -0
- package/dist/nfa.js +245 -0
- package/dist/parser.cjs +329 -0
- package/dist/parser.d.cts +12 -0
- package/dist/parser.d.ts +12 -0
- package/dist/parser.js +328 -0
- package/dist/shorthand-classes.cjs +74 -0
- package/dist/shorthand-classes.d.cts +8 -0
- package/dist/shorthand-classes.d.ts +8 -0
- package/dist/shorthand-classes.js +73 -0
- package/package.json +89 -2
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# trilean-regex
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ExaDev/trilean) [](https://www.npmjs.com/package/trilean-regex) [](https://github.com/ExaDev/trilean/releases?q=trilean-regex%40) [](https://github.com/ExaDev/trilean/actions)
|
|
4
|
+
|
|
5
|
+
A portable regular-expression grammar -- a true regular language, with its own parser and a Thompson-construction reference matcher -- so a pattern means the same thing wherever it runs: in process here, and pushed down to a database's own engine by [`trilean-sql`](../trilean-sql).
|
|
6
|
+
|
|
7
|
+
## Why a new grammar
|
|
8
|
+
|
|
9
|
+
ECMAScript's `RegExp` is not a regular language. Backreferences and lookaround make it strictly more expressive than a finite automaton can recognise, and its backtracking implementation can take exponential time on certain inputs (the well-known "catastrophic backtracking" failure mode). Worse for portability: every SQL engine's own regex support is a *different* language again -- PostgreSQL's POSIX ARE is close to ECMAScript but not identical, and SQLite/D1 have no built-in regex engine at all.
|
|
10
|
+
|
|
11
|
+
A pattern written in this package's grammar is different on both counts. It is restricted to constructs a finite automaton can express, so it always matches in linear time with no backtracking risk, and its reference matcher is guaranteed to agree with itself regardless of input. And because the grammar is deliberately small, a SQL dialect compiler can translate it structurally instead of trying to detect which subset of an open-ended ECMAScript pattern happens to be "safe" for that dialect -- see `trilean-sql`'s `portableMatches`/`portableNotMatches` compilers, which do exactly this for PostgreSQL and SQLite/D1.
|
|
12
|
+
|
|
13
|
+
## Grammar
|
|
14
|
+
|
|
15
|
+
Patterns are written with ordinary regex-like syntax so nothing new has to be learned, but only the constructs below are legal -- anything else is a parse error naming exactly where parsing failed, never a fuzzy runtime failure.
|
|
16
|
+
|
|
17
|
+
| Construct | Meaning |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `a` | A literal character |
|
|
20
|
+
| `ab` | Concatenation: `a` then `b` |
|
|
21
|
+
| `a\|b` | Alternation: `a` or `b` |
|
|
22
|
+
| `a*` | Zero or more `a` |
|
|
23
|
+
| `a+` | One or more `a` |
|
|
24
|
+
| `a?` | Zero or one `a` |
|
|
25
|
+
| `a{3}` | Exactly 3 `a` |
|
|
26
|
+
| `a{2,}` | 2 or more `a` |
|
|
27
|
+
| `a{2,5}` | Between 2 and 5 `a`, inclusive |
|
|
28
|
+
| `.` | Any single character |
|
|
29
|
+
| `^` | Start of the input |
|
|
30
|
+
| `$` | End of the input |
|
|
31
|
+
| `[abc]` | Any one of `a`, `b`, `c` |
|
|
32
|
+
| `[a-z]` | Any character in the range `a` to `z` |
|
|
33
|
+
| `[^abc]` | Any character except `a`, `b`, `c` |
|
|
34
|
+
| `\d` `\D` | An ASCII digit (`0`-`9`), or its negation |
|
|
35
|
+
| `\w` `\W` | An ASCII word character (`A`-`Z`, `a`-`z`, `0`-`9`, `_`), or its negation |
|
|
36
|
+
| `\s` `\S` | An ASCII whitespace character (space, tab, `\n`, `\r`, `\f`, `\v`), or its negation |
|
|
37
|
+
| `(?:ab)` | A non-capturing group, for precedence and repetition |
|
|
38
|
+
| `\.` `\*` `\+` `\?` `\(` `\)` `\[` `\]` `\{` `\}` `\|` `\^` `\$` `\\` `\/` | An escaped metacharacter, matched literally |
|
|
39
|
+
| `\n` `\t` `\r` `\f` `\v` | A control character, matched literally |
|
|
40
|
+
|
|
41
|
+
Everything else the grammar deliberately excludes:
|
|
42
|
+
|
|
43
|
+
- **No capturing groups.** `(a)` is a parse error directing the author to `(?:a)` instead. There is nothing to capture: this grammar only ever answers whether a pattern matches, never what matched, so a capturing group would be pure syntax with no corresponding capability.
|
|
44
|
+
- **No backreferences** (`\1`), because they make the language context-sensitive rather than regular -- no finite automaton can recognise `(a+)\1`.
|
|
45
|
+
- **No lookaround** (`(?=...)`, `(?!...)`, `(?<=...)`, `(?<!...)`), for the same reason: lookaround assertions are not part of a regular language's own recognition power.
|
|
46
|
+
- **No lazy or possessive quantifiers** (`a*?`, `a*+`). These only change *which* substring a backtracking engine reports as the match; since this grammar only ever answers a yes/no membership question, greedy and lazy always agree on whether a match exists, so there is no behaviour left for either modifier to control. Stacking a second quantifier onto a first (`a**`, `a*+`, `a*{2}`) is therefore a parse error rather than being silently accepted as a no-op.
|
|
47
|
+
- **No flags** (case-insensitivity, multiline, dotAll). `^`/`$` always mean the absolute start/end of the whole input, never a line boundary, and `.` always matches any character including a line terminator -- there is no flag to make either mean something else, so there is nothing to configure.
|
|
48
|
+
|
|
49
|
+
## Character encoding
|
|
50
|
+
|
|
51
|
+
A character is a UTF-16 code unit, exactly matching `RegExp`'s own behaviour without the `u` flag: `String.prototype.charCodeAt`'s notion of one character, not a full Unicode scalar value. A character outside the Basic Multilingual Plane is two separate code units under `.`, a character class, or a shorthand class, the same as it would be against a non-`u`-flag native `RegExp`.
|
|
52
|
+
|
|
53
|
+
## Matching semantics
|
|
54
|
+
|
|
55
|
+
`testPattern`/`compilePattern(...).test(...)` report whether **any substring** of the input matches -- the same "search" semantics as a bare `RegExp.prototype.test()` call with no anchors, not a "does the whole string match" test. Anchor the pattern yourself (`^...$`) for a full-string match.
|
|
56
|
+
|
|
57
|
+
`^` and `$` can appear anywhere in the pattern -- inside a group, inside an alternation branch -- and always mean the absolute start/end of the whole input, evaluated at whatever position the automaton reaches them, not confined to the very start/end of the pattern text. `^a|z$` matches a string starting with `a`, or one ending with `z` (they are independent branches, each anchored on its own side only).
|
|
58
|
+
|
|
59
|
+
## Reference implementation
|
|
60
|
+
|
|
61
|
+
`parseRegex` is a hand-written recursive-descent parser producing a `RegexNode` AST -- the same AST `trilean-sql`'s dialect compilers pattern-match over to translate a pattern into `LIKE`/`GLOB`/`~` fragments, rather than each compiler re-parsing pattern text itself.
|
|
62
|
+
|
|
63
|
+
`compilePattern`/`compileToNfa` compile that AST to a non-deterministic finite automaton via Thompson's construction, and `matchesNfa` simulates it against an input with the standard "new thread at every position" search algorithm (Cox, ["Regular Expression Matching Can Be Simple And Fast"](https://swtch.com/~rsc/regexp/regexp1.html)). This is what makes the "no catastrophic backtracking" guarantee true by construction rather than merely by convention: simulating an NFA takes time linear in `pattern states x input length` regardless of the pattern's shape, so there is no input this package's own reference matcher can be made to hang on.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { compilePattern, testPattern, parseRegex, RegexParseError } from "trilean-regex";
|
|
67
|
+
|
|
68
|
+
testPattern("^[\\w.]+@[\\w]+\\.[a-z]{2,}$", "person@example.com"); // true
|
|
69
|
+
|
|
70
|
+
const compiled = compilePattern("^ENA-\\d{4,6}$");
|
|
71
|
+
compiled.test("ENA-12345"); // true -- reuse the compiled pattern across many inputs
|
|
72
|
+
compiled.test("ENA-123"); // false
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
parseRegex("(a)");
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (error instanceof RegexParseError) {
|
|
78
|
+
// "invalid pattern at index 0: capturing groups are not supported..."
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Design principles
|
|
84
|
+
|
|
85
|
+
- **A true regular language, not a documented subset of one.** Every construct this grammar accepts corresponds to something a finite automaton can express; nothing is included on the promise that "most engines happen to agree about it."
|
|
86
|
+
- **A real parser, not pattern validation.** `parseRegex` builds an AST via recursive descent, the same way a general-purpose parser would -- it does not check the input string against a meta-pattern and hope the input itself is then safe to hand to another regex engine.
|
|
87
|
+
- **No indeterminate outcome for a bad pattern here.** A pattern is grammatical or it is not; `parseRegex` throws `RegexParseError` for the latter, naming the exact index. (What a caller does with that error -- `trilean` core's `evaluator.ts` turns an invalid `portableMatches` pattern into `wrong-type`, matching how it already treats an invalid ECMAScript pattern under plain `matches` -- is that caller's decision, not this package's.)
|
|
88
|
+
- **Isomorphic.** No `node:*` import, no `Buffer`, in any runtime module -- enforced by this package's own `eslint.config.ts` isomorphism guard, the same convention `trilean` and `trilean-sql` both follow.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
//#region src/ast.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The abstract syntax this package's parser produces and its matcher and NFA compiler both consume.
|
|
4
|
+
*
|
|
5
|
+
* Every node kind here corresponds to a construct of a true regular language: no node kind exists for anything a finite automaton cannot express (a backreference, a lookaround assertion). That is what "portable" means for this grammar -- the AST is small enough, and restricted enough, that a `trilean-sql` dialect compiler can pattern-match over it directly instead of walking an open-ended ECMAScript pattern and hoping the subset it recognises is really the whole story.
|
|
6
|
+
*
|
|
7
|
+
* A code point is represented as its UTF-16 code unit value (what `String.prototype.charCodeAt` returns), matching non-`u`-flag ECMAScript `RegExp` semantics: a character outside the Basic Multilingual Plane is two code units, each matched independently, rather than one Unicode scalar value. This keeps the grammar's notion of "one character" identical to what `[a-z]`-style ranges and `.` mean in the engine most authors already carry a mental model of.
|
|
8
|
+
*/
|
|
9
|
+
/** A single literal character, matched by its UTF-16 code unit value. */
|
|
10
|
+
interface LiteralNode {
|
|
11
|
+
readonly kind: "literal";
|
|
12
|
+
readonly codeUnit: number;
|
|
13
|
+
}
|
|
14
|
+
/** `.` -- matches exactly one character, of any value. Unlike ECMAScript's default mode, this grammar defines it to match a line terminator too: there is no multiline/dotAll mode to make that a meaningful distinction, so one unconditional rule is simpler than a flag nobody can set. */
|
|
15
|
+
interface AnyCharNode {
|
|
16
|
+
readonly kind: "anyChar";
|
|
17
|
+
}
|
|
18
|
+
/** An inclusive code-unit range within a character class, e.g. `a-z`. A single character `c` is represented as `{ from: c, to: c }`. */
|
|
19
|
+
interface CharRange {
|
|
20
|
+
readonly from: number;
|
|
21
|
+
readonly to: number;
|
|
22
|
+
}
|
|
23
|
+
/** `[...]` / `[^...]`, and the standalone shorthand classes (`\d`, `\D`, `\w`, `\W`, `\s`, `\S`) once expanded -- see `shorthand-classes.ts` for the fixed range tables each shorthand expands to. `ranges` is the positive set; `negated` flips membership rather than the ranges themselves, so a negated class still reports the positive ranges it was written against (useful for tooling, and what the SQLite `GLOB` compiler in trilean-sql needs to render `[^...]` back out). */
|
|
24
|
+
interface CharClassNode {
|
|
25
|
+
readonly kind: "charClass";
|
|
26
|
+
readonly negated: boolean;
|
|
27
|
+
readonly ranges: readonly CharRange[];
|
|
28
|
+
}
|
|
29
|
+
/** `^` -- zero-width assertion that the current position is the very start of the input (absolute index 0), regardless of where in the pattern it appears. */
|
|
30
|
+
interface AnchorStartNode {
|
|
31
|
+
readonly kind: "anchorStart";
|
|
32
|
+
}
|
|
33
|
+
/** `$` -- zero-width assertion that the current position is the very end of the input (index equal to the input's length). */
|
|
34
|
+
interface AnchorEndNode {
|
|
35
|
+
readonly kind: "anchorEnd";
|
|
36
|
+
}
|
|
37
|
+
/** Sequencing: `operands` matched one after another with no characters skipped in between. An empty `operands` array is the empty pattern, matching the empty string. */
|
|
38
|
+
interface ConcatNode {
|
|
39
|
+
readonly kind: "concat";
|
|
40
|
+
readonly operands: readonly RegexNode[];
|
|
41
|
+
}
|
|
42
|
+
/** `a|b|c` -- exactly one of `branches` matches. Always at least two branches; a single-branch alternation has no reason to exist and the parser never produces one. */
|
|
43
|
+
interface AlternationNode {
|
|
44
|
+
readonly kind: "alternation";
|
|
45
|
+
readonly branches: readonly RegexNode[];
|
|
46
|
+
}
|
|
47
|
+
/** `a*` -- zero or more repetitions of `operand`. */
|
|
48
|
+
interface StarNode {
|
|
49
|
+
readonly kind: "star";
|
|
50
|
+
readonly operand: RegexNode;
|
|
51
|
+
}
|
|
52
|
+
/** `a+` -- one or more repetitions of `operand`. */
|
|
53
|
+
interface PlusNode {
|
|
54
|
+
readonly kind: "plus";
|
|
55
|
+
readonly operand: RegexNode;
|
|
56
|
+
}
|
|
57
|
+
/** `a?` -- zero or one repetition of `operand`. */
|
|
58
|
+
interface OptionalNode {
|
|
59
|
+
readonly kind: "optional";
|
|
60
|
+
readonly operand: RegexNode;
|
|
61
|
+
}
|
|
62
|
+
/** `a{min,max}` -- between `min` and `max` repetitions of `operand`, inclusive. `max: undefined` means unbounded (`a{min,}`). `a{n}` parses to `min: n, max: n`. The parser never produces `min > max` (a bounded form with `max < min` is a parse error) and never produces the forms already covered by `StarNode`/`PlusNode`/`OptionalNode` -- those three are their own node kinds precisely so a matcher or compiler can special-case the common shapes without reading repetition bounds at all. */
|
|
63
|
+
interface RepeatNode {
|
|
64
|
+
readonly kind: "repeat";
|
|
65
|
+
readonly operand: RegexNode;
|
|
66
|
+
readonly min: number;
|
|
67
|
+
readonly max: number | undefined;
|
|
68
|
+
}
|
|
69
|
+
type RegexNode = LiteralNode | AnyCharNode | CharClassNode | AnchorStartNode | AnchorEndNode | ConcatNode | AlternationNode | StarNode | PlusNode | OptionalNode | RepeatNode;
|
|
70
|
+
/** Whether `unit` falls inside an inclusive range. */
|
|
71
|
+
declare function rangeContains(range: CharRange, unit: number): boolean;
|
|
72
|
+
/** Whether `unit` is a member of a character class, honouring `negated`. */
|
|
73
|
+
declare function charClassMatches(node: CharClassNode, unit: number): boolean;
|
|
74
|
+
//#endregion
|
|
75
|
+
export { CharClassNode as a, LiteralNode as c, RegexNode as d, RepeatNode as f, rangeContains as h, AnyCharNode as i, OptionalNode as l, charClassMatches as m, AnchorEndNode as n, CharRange as o, StarNode as p, AnchorStartNode as r, ConcatNode as s, AlternationNode as t, PlusNode as u };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
//#region src/ast.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The abstract syntax this package's parser produces and its matcher and NFA compiler both consume.
|
|
4
|
+
*
|
|
5
|
+
* Every node kind here corresponds to a construct of a true regular language: no node kind exists for anything a finite automaton cannot express (a backreference, a lookaround assertion). That is what "portable" means for this grammar -- the AST is small enough, and restricted enough, that a `trilean-sql` dialect compiler can pattern-match over it directly instead of walking an open-ended ECMAScript pattern and hoping the subset it recognises is really the whole story.
|
|
6
|
+
*
|
|
7
|
+
* A code point is represented as its UTF-16 code unit value (what `String.prototype.charCodeAt` returns), matching non-`u`-flag ECMAScript `RegExp` semantics: a character outside the Basic Multilingual Plane is two code units, each matched independently, rather than one Unicode scalar value. This keeps the grammar's notion of "one character" identical to what `[a-z]`-style ranges and `.` mean in the engine most authors already carry a mental model of.
|
|
8
|
+
*/
|
|
9
|
+
/** A single literal character, matched by its UTF-16 code unit value. */
|
|
10
|
+
interface LiteralNode {
|
|
11
|
+
readonly kind: "literal";
|
|
12
|
+
readonly codeUnit: number;
|
|
13
|
+
}
|
|
14
|
+
/** `.` -- matches exactly one character, of any value. Unlike ECMAScript's default mode, this grammar defines it to match a line terminator too: there is no multiline/dotAll mode to make that a meaningful distinction, so one unconditional rule is simpler than a flag nobody can set. */
|
|
15
|
+
interface AnyCharNode {
|
|
16
|
+
readonly kind: "anyChar";
|
|
17
|
+
}
|
|
18
|
+
/** An inclusive code-unit range within a character class, e.g. `a-z`. A single character `c` is represented as `{ from: c, to: c }`. */
|
|
19
|
+
interface CharRange {
|
|
20
|
+
readonly from: number;
|
|
21
|
+
readonly to: number;
|
|
22
|
+
}
|
|
23
|
+
/** `[...]` / `[^...]`, and the standalone shorthand classes (`\d`, `\D`, `\w`, `\W`, `\s`, `\S`) once expanded -- see `shorthand-classes.ts` for the fixed range tables each shorthand expands to. `ranges` is the positive set; `negated` flips membership rather than the ranges themselves, so a negated class still reports the positive ranges it was written against (useful for tooling, and what the SQLite `GLOB` compiler in trilean-sql needs to render `[^...]` back out). */
|
|
24
|
+
interface CharClassNode {
|
|
25
|
+
readonly kind: "charClass";
|
|
26
|
+
readonly negated: boolean;
|
|
27
|
+
readonly ranges: readonly CharRange[];
|
|
28
|
+
}
|
|
29
|
+
/** `^` -- zero-width assertion that the current position is the very start of the input (absolute index 0), regardless of where in the pattern it appears. */
|
|
30
|
+
interface AnchorStartNode {
|
|
31
|
+
readonly kind: "anchorStart";
|
|
32
|
+
}
|
|
33
|
+
/** `$` -- zero-width assertion that the current position is the very end of the input (index equal to the input's length). */
|
|
34
|
+
interface AnchorEndNode {
|
|
35
|
+
readonly kind: "anchorEnd";
|
|
36
|
+
}
|
|
37
|
+
/** Sequencing: `operands` matched one after another with no characters skipped in between. An empty `operands` array is the empty pattern, matching the empty string. */
|
|
38
|
+
interface ConcatNode {
|
|
39
|
+
readonly kind: "concat";
|
|
40
|
+
readonly operands: readonly RegexNode[];
|
|
41
|
+
}
|
|
42
|
+
/** `a|b|c` -- exactly one of `branches` matches. Always at least two branches; a single-branch alternation has no reason to exist and the parser never produces one. */
|
|
43
|
+
interface AlternationNode {
|
|
44
|
+
readonly kind: "alternation";
|
|
45
|
+
readonly branches: readonly RegexNode[];
|
|
46
|
+
}
|
|
47
|
+
/** `a*` -- zero or more repetitions of `operand`. */
|
|
48
|
+
interface StarNode {
|
|
49
|
+
readonly kind: "star";
|
|
50
|
+
readonly operand: RegexNode;
|
|
51
|
+
}
|
|
52
|
+
/** `a+` -- one or more repetitions of `operand`. */
|
|
53
|
+
interface PlusNode {
|
|
54
|
+
readonly kind: "plus";
|
|
55
|
+
readonly operand: RegexNode;
|
|
56
|
+
}
|
|
57
|
+
/** `a?` -- zero or one repetition of `operand`. */
|
|
58
|
+
interface OptionalNode {
|
|
59
|
+
readonly kind: "optional";
|
|
60
|
+
readonly operand: RegexNode;
|
|
61
|
+
}
|
|
62
|
+
/** `a{min,max}` -- between `min` and `max` repetitions of `operand`, inclusive. `max: undefined` means unbounded (`a{min,}`). `a{n}` parses to `min: n, max: n`. The parser never produces `min > max` (a bounded form with `max < min` is a parse error) and never produces the forms already covered by `StarNode`/`PlusNode`/`OptionalNode` -- those three are their own node kinds precisely so a matcher or compiler can special-case the common shapes without reading repetition bounds at all. */
|
|
63
|
+
interface RepeatNode {
|
|
64
|
+
readonly kind: "repeat";
|
|
65
|
+
readonly operand: RegexNode;
|
|
66
|
+
readonly min: number;
|
|
67
|
+
readonly max: number | undefined;
|
|
68
|
+
}
|
|
69
|
+
type RegexNode = LiteralNode | AnyCharNode | CharClassNode | AnchorStartNode | AnchorEndNode | ConcatNode | AlternationNode | StarNode | PlusNode | OptionalNode | RepeatNode;
|
|
70
|
+
/** Whether `unit` falls inside an inclusive range. */
|
|
71
|
+
declare function rangeContains(range: CharRange, unit: number): boolean;
|
|
72
|
+
/** Whether `unit` is a member of a character class, honouring `negated`. */
|
|
73
|
+
declare function charClassMatches(node: CharClassNode, unit: number): boolean;
|
|
74
|
+
//#endregion
|
|
75
|
+
export { CharClassNode as a, LiteralNode as c, RegexNode as d, RepeatNode as f, rangeContains as h, AnyCharNode as i, OptionalNode as l, charClassMatches as m, AnchorEndNode as n, CharRange as o, StarNode as p, AnchorStartNode as r, ConcatNode as s, AlternationNode as t, PlusNode as u };
|
package/dist/ast.cjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/ast.ts
|
|
3
|
+
/** Whether `unit` falls inside an inclusive range. */
|
|
4
|
+
function rangeContains(range, unit) {
|
|
5
|
+
return unit >= range.from && unit <= range.to;
|
|
6
|
+
}
|
|
7
|
+
/** Whether `unit` is a member of a character class, honouring `negated`. */
|
|
8
|
+
function charClassMatches(node, unit) {
|
|
9
|
+
const inRanges = node.ranges.some((range) => rangeContains(range, unit));
|
|
10
|
+
return node.negated ? !inRanges : inRanges;
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
exports.charClassMatches = charClassMatches;
|
|
14
|
+
exports.rangeContains = rangeContains;
|
package/dist/ast.d.cts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as CharClassNode, c as LiteralNode, d as RegexNode, f as RepeatNode, h as rangeContains, i as AnyCharNode, l as OptionalNode, m as charClassMatches, n as AnchorEndNode, o as CharRange, p as StarNode, r as AnchorStartNode, s as ConcatNode, t as AlternationNode, u as PlusNode } from "./ast-D8HLFxxv.cjs";
|
|
2
|
+
export { AlternationNode, AnchorEndNode, AnchorStartNode, AnyCharNode, CharClassNode, CharRange, ConcatNode, LiteralNode, OptionalNode, PlusNode, RegexNode, RepeatNode, StarNode, charClassMatches, rangeContains };
|
package/dist/ast.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as CharClassNode, c as LiteralNode, d as RegexNode, f as RepeatNode, h as rangeContains, i as AnyCharNode, l as OptionalNode, m as charClassMatches, n as AnchorEndNode, o as CharRange, p as StarNode, r as AnchorStartNode, s as ConcatNode, t as AlternationNode, u as PlusNode } from "./ast-D8HLFxxv.js";
|
|
2
|
+
export { AlternationNode, AnchorEndNode, AnchorStartNode, AnyCharNode, CharClassNode, CharRange, ConcatNode, LiteralNode, OptionalNode, PlusNode, RegexNode, RepeatNode, StarNode, charClassMatches, rangeContains };
|
package/dist/ast.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/ast.ts
|
|
2
|
+
/** Whether `unit` falls inside an inclusive range. */
|
|
3
|
+
function rangeContains(range, unit) {
|
|
4
|
+
return unit >= range.from && unit <= range.to;
|
|
5
|
+
}
|
|
6
|
+
/** Whether `unit` is a member of a character class, honouring `negated`. */
|
|
7
|
+
function charClassMatches(node, unit) {
|
|
8
|
+
const inRanges = node.ranges.some((range) => rangeContains(range, unit));
|
|
9
|
+
return node.negated ? !inRanges : inRanges;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { charClassMatches, rangeContains };
|
package/dist/errors.cjs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/errors.ts
|
|
3
|
+
/**
|
|
4
|
+
* A pattern the parser refuses, with a real position rather than a generic message: `index` is the UTF-16 code unit offset into the source pattern string where parsing could not proceed, so a caller (or a test) can point at exactly what was wrong instead of re-deriving it from `message` alone.
|
|
5
|
+
*
|
|
6
|
+
* This is the only error this package's parser ever throws. There is no partial-AST return, no best-effort fallback -- a pattern either parses completely into a `RegexNode` or this is thrown, matching the "clear parse error, not a fuzzy runtime failure" goal a portable grammar exists for in the first place.
|
|
7
|
+
*/
|
|
8
|
+
var RegexParseError = class extends Error {
|
|
9
|
+
pattern;
|
|
10
|
+
index;
|
|
11
|
+
constructor(pattern, index, reason) {
|
|
12
|
+
super(`invalid pattern at index ${String(index)}: ${reason}\n ${pattern}\n ${" ".repeat(index)}^`);
|
|
13
|
+
this.name = "RegexParseError";
|
|
14
|
+
this.pattern = pattern;
|
|
15
|
+
this.index = index;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
exports.RegexParseError = RegexParseError;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/errors.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A pattern the parser refuses, with a real position rather than a generic message: `index` is the UTF-16 code unit offset into the source pattern string where parsing could not proceed, so a caller (or a test) can point at exactly what was wrong instead of re-deriving it from `message` alone.
|
|
4
|
+
*
|
|
5
|
+
* This is the only error this package's parser ever throws. There is no partial-AST return, no best-effort fallback -- a pattern either parses completely into a `RegexNode` or this is thrown, matching the "clear parse error, not a fuzzy runtime failure" goal a portable grammar exists for in the first place.
|
|
6
|
+
*/
|
|
7
|
+
declare class RegexParseError extends Error {
|
|
8
|
+
readonly pattern: string;
|
|
9
|
+
readonly index: number;
|
|
10
|
+
constructor(pattern: string, index: number, reason: string);
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { RegexParseError };
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/errors.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A pattern the parser refuses, with a real position rather than a generic message: `index` is the UTF-16 code unit offset into the source pattern string where parsing could not proceed, so a caller (or a test) can point at exactly what was wrong instead of re-deriving it from `message` alone.
|
|
4
|
+
*
|
|
5
|
+
* This is the only error this package's parser ever throws. There is no partial-AST return, no best-effort fallback -- a pattern either parses completely into a `RegexNode` or this is thrown, matching the "clear parse error, not a fuzzy runtime failure" goal a portable grammar exists for in the first place.
|
|
6
|
+
*/
|
|
7
|
+
declare class RegexParseError extends Error {
|
|
8
|
+
readonly pattern: string;
|
|
9
|
+
readonly index: number;
|
|
10
|
+
constructor(pattern: string, index: number, reason: string);
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { RegexParseError };
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* A pattern the parser refuses, with a real position rather than a generic message: `index` is the UTF-16 code unit offset into the source pattern string where parsing could not proceed, so a caller (or a test) can point at exactly what was wrong instead of re-deriving it from `message` alone.
|
|
4
|
+
*
|
|
5
|
+
* This is the only error this package's parser ever throws. There is no partial-AST return, no best-effort fallback -- a pattern either parses completely into a `RegexNode` or this is thrown, matching the "clear parse error, not a fuzzy runtime failure" goal a portable grammar exists for in the first place.
|
|
6
|
+
*/
|
|
7
|
+
var RegexParseError = class extends Error {
|
|
8
|
+
pattern;
|
|
9
|
+
index;
|
|
10
|
+
constructor(pattern, index, reason) {
|
|
11
|
+
super(`invalid pattern at index ${String(index)}: ${reason}\n ${pattern}\n ${" ".repeat(index)}^`);
|
|
12
|
+
this.name = "RegexParseError";
|
|
13
|
+
this.pattern = pattern;
|
|
14
|
+
this.index = index;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { RegexParseError };
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_ast = require("./ast.cjs");
|
|
3
|
+
const require_errors = require("./errors.cjs");
|
|
4
|
+
const require_shorthand_classes = require("./shorthand-classes.cjs");
|
|
5
|
+
const require_parser = require("./parser.cjs");
|
|
6
|
+
const require_nfa = require("./nfa.cjs");
|
|
7
|
+
const require_matcher = require("./matcher.cjs");
|
|
8
|
+
exports.RegexParseError = require_errors.RegexParseError;
|
|
9
|
+
exports.charClassMatches = require_ast.charClassMatches;
|
|
10
|
+
exports.compilePattern = require_matcher.compilePattern;
|
|
11
|
+
exports.compileToNfa = require_nfa.compileToNfa;
|
|
12
|
+
exports.expandShorthandClass = require_shorthand_classes.expandShorthandClass;
|
|
13
|
+
exports.matchesNfa = require_matcher.matchesNfa;
|
|
14
|
+
exports.parseRegex = require_parser.parseRegex;
|
|
15
|
+
exports.rangeContains = require_ast.rangeContains;
|
|
16
|
+
exports.testPattern = require_matcher.testPattern;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { a as CharClassNode, c as LiteralNode, d as RegexNode, f as RepeatNode, h as rangeContains, i as AnyCharNode, l as OptionalNode, m as charClassMatches, n as AnchorEndNode, o as CharRange, p as StarNode, r as AnchorStartNode, s as ConcatNode, t as AlternationNode, u as PlusNode } from "./ast-D8HLFxxv.cjs";
|
|
2
|
+
import { RegexParseError } from "./errors.cjs";
|
|
3
|
+
import { parseRegex } from "./parser.cjs";
|
|
4
|
+
import { ShorthandClassLetter, expandShorthandClass } from "./shorthand-classes.cjs";
|
|
5
|
+
import { CompiledNfa, NfaState, compileToNfa } from "./nfa.cjs";
|
|
6
|
+
import { CompiledPattern, compilePattern, matchesNfa, testPattern } from "./matcher.cjs";
|
|
7
|
+
export { type AlternationNode, type AnchorEndNode, type AnchorStartNode, type AnyCharNode, type CharClassNode, type CharRange, type CompiledNfa, type CompiledPattern, type ConcatNode, type LiteralNode, type NfaState, type OptionalNode, type PlusNode, type RegexNode, RegexParseError, type RepeatNode, type ShorthandClassLetter, type StarNode, charClassMatches, compilePattern, compileToNfa, expandShorthandClass, matchesNfa, parseRegex, rangeContains, testPattern };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { a as CharClassNode, c as LiteralNode, d as RegexNode, f as RepeatNode, h as rangeContains, i as AnyCharNode, l as OptionalNode, m as charClassMatches, n as AnchorEndNode, o as CharRange, p as StarNode, r as AnchorStartNode, s as ConcatNode, t as AlternationNode, u as PlusNode } from "./ast-D8HLFxxv.js";
|
|
2
|
+
import { RegexParseError } from "./errors.js";
|
|
3
|
+
import { parseRegex } from "./parser.js";
|
|
4
|
+
import { ShorthandClassLetter, expandShorthandClass } from "./shorthand-classes.js";
|
|
5
|
+
import { CompiledNfa, NfaState, compileToNfa } from "./nfa.js";
|
|
6
|
+
import { CompiledPattern, compilePattern, matchesNfa, testPattern } from "./matcher.js";
|
|
7
|
+
export { type AlternationNode, type AnchorEndNode, type AnchorStartNode, type AnyCharNode, type CharClassNode, type CharRange, type CompiledNfa, type CompiledPattern, type ConcatNode, type LiteralNode, type NfaState, type OptionalNode, type PlusNode, type RegexNode, RegexParseError, type RepeatNode, type ShorthandClassLetter, type StarNode, charClassMatches, compilePattern, compileToNfa, expandShorthandClass, matchesNfa, parseRegex, rangeContains, testPattern };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { charClassMatches, rangeContains } from "./ast.js";
|
|
2
|
+
import { RegexParseError } from "./errors.js";
|
|
3
|
+
import { expandShorthandClass } from "./shorthand-classes.js";
|
|
4
|
+
import { parseRegex } from "./parser.js";
|
|
5
|
+
import { compileToNfa } from "./nfa.js";
|
|
6
|
+
import { compilePattern, matchesNfa, testPattern } from "./matcher.js";
|
|
7
|
+
export { RegexParseError, charClassMatches, compilePattern, compileToNfa, expandShorthandClass, matchesNfa, parseRegex, rangeContains, testPattern };
|
package/dist/matcher.cjs
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_parser = require("./parser.cjs");
|
|
3
|
+
const require_nfa = require("./nfa.cjs");
|
|
4
|
+
//#region src/matcher.ts
|
|
5
|
+
/**
|
|
6
|
+
* Epsilon-closure of `seeds` at absolute input position `pos` (0-based, counting the boundary *before* the character at that index -- so `pos === length` means "after the last character"): follows every `split` unconditionally and every `anchorStart`/`anchorEnd` only when `pos` satisfies it, stopping at each `char` or `match` state reached, which are the only kinds `matchesNfa`'s step loop ever needs to see.
|
|
7
|
+
*
|
|
8
|
+
* `length` is threaded through only for the `anchorEnd` check (`pos === length`) rather than read off a captured string, so this function depends on nothing beyond the automaton and two numbers -- it has no notion of "the input," only of where the boundary it is being asked about sits relative to the input's own length.
|
|
9
|
+
*/
|
|
10
|
+
function closure(nfa, seeds, pos, length) {
|
|
11
|
+
const result = /* @__PURE__ */ new Set();
|
|
12
|
+
const visited = /* @__PURE__ */ new Set();
|
|
13
|
+
const stack = [...seeds];
|
|
14
|
+
while (stack.length > 0) {
|
|
15
|
+
const idx = stack.pop();
|
|
16
|
+
if (idx === void 0) continue;
|
|
17
|
+
if (visited.has(idx)) continue;
|
|
18
|
+
visited.add(idx);
|
|
19
|
+
const state = nfa.states[idx];
|
|
20
|
+
if (state === void 0) continue;
|
|
21
|
+
switch (state.kind) {
|
|
22
|
+
case "char":
|
|
23
|
+
case "match":
|
|
24
|
+
result.add(idx);
|
|
25
|
+
break;
|
|
26
|
+
case "split":
|
|
27
|
+
stack.push(state.to1, state.to2);
|
|
28
|
+
break;
|
|
29
|
+
case "anchorStart":
|
|
30
|
+
if (pos === 0) stack.push(state.to);
|
|
31
|
+
break;
|
|
32
|
+
case "anchorEnd": if (pos === length) stack.push(state.to);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
/** The `char` states in `current` whose test accepts `codeUnit`, mapped to their (not yet closed) successor. Never includes `match` states -- there is nothing past a match to step from. */
|
|
38
|
+
function step(nfa, current, codeUnit) {
|
|
39
|
+
const next = /* @__PURE__ */ new Set();
|
|
40
|
+
for (const idx of current) {
|
|
41
|
+
const state = nfa.states[idx];
|
|
42
|
+
if (state?.kind !== "char") continue;
|
|
43
|
+
if (state.test(codeUnit)) next.add(state.to);
|
|
44
|
+
}
|
|
45
|
+
return next;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Simulates `nfa` against `input` and reports whether any substring of `input` matches -- the same "search," not "full match," semantics a bare `RegExp.prototype.test()` call has without the `^.../$...` anchors pinning it down, since `^`/`$` are already ordinary states inside the automaton (see `nfa.ts`) rather than a separate mode this function chooses between.
|
|
49
|
+
*
|
|
50
|
+
* The technique is the standard "add a fresh thread at every position" Thompson NFA search (Cox, "Regular Expression Matching Can Be Simple And Fast"): a new candidate match start is injected into the active state set before each character is consumed, exactly as if `.*?` had been prepended to an anchored pattern, except no such prefix is ever built -- the injection is the runtime equivalent of it. Because injection happens unconditionally and `^`'s own position check is what actually restricts where a match may begin, a pattern beginning with `^` still only ever matches from position 0, and one that does not can begin anywhere.
|
|
51
|
+
*
|
|
52
|
+
* Runs in O(length x states) time and allocates no more than two small state sets at a time, regardless of the pattern's shape -- there is no exponential blow-up for any input this compiles, which is the whole reason this package builds an automaton instead of calling a backtracking engine.
|
|
53
|
+
*/
|
|
54
|
+
function matchesNfa(nfa, input) {
|
|
55
|
+
const length = input.length;
|
|
56
|
+
let current = closure(nfa, [nfa.start], 0, length);
|
|
57
|
+
if (current.has(nfa.matchState)) return true;
|
|
58
|
+
for (let pos = 0; pos < length; pos += 1) {
|
|
59
|
+
const codeUnit = input.charCodeAt(pos);
|
|
60
|
+
const stepped = step(nfa, current, codeUnit);
|
|
61
|
+
stepped.add(nfa.start);
|
|
62
|
+
current = closure(nfa, [...stepped], pos + 1, length);
|
|
63
|
+
if (current.has(nfa.matchState)) return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Parses `pattern` (this package's grammar; see README.md) and compiles it to a reference matcher.
|
|
69
|
+
*
|
|
70
|
+
* Prefer this over `testPattern` when the same pattern will be tested against more than one input: it parses and compiles exactly once, and the returned `CompiledPattern.test` is then only the O(length x states) simulation.
|
|
71
|
+
*
|
|
72
|
+
* @throws {RegexParseError} if `pattern` is not a valid pattern in this grammar.
|
|
73
|
+
*/
|
|
74
|
+
function compilePattern(pattern) {
|
|
75
|
+
const nfa = require_nfa.compileToNfa(require_parser.parseRegex(pattern));
|
|
76
|
+
return { test: (input) => matchesNfa(nfa, input) };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Parses `pattern` and tests it against `input` in one call. Equivalent to `compilePattern(pattern).test(input)`, and exactly as cheap for a single test -- prefer `compilePattern` instead when the same pattern will be reused, so the parse and compile steps happen once rather than once per input.
|
|
80
|
+
*
|
|
81
|
+
* @throws {RegexParseError} if `pattern` is not a valid pattern in this grammar.
|
|
82
|
+
*/
|
|
83
|
+
function testPattern(pattern, input) {
|
|
84
|
+
return compilePattern(pattern).test(input);
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
exports.compilePattern = compilePattern;
|
|
88
|
+
exports.matchesNfa = matchesNfa;
|
|
89
|
+
exports.testPattern = testPattern;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CompiledNfa } from "./nfa.cjs";
|
|
2
|
+
//#region src/matcher.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Simulates `nfa` against `input` and reports whether any substring of `input` matches -- the same "search," not "full match," semantics a bare `RegExp.prototype.test()` call has without the `^.../$...` anchors pinning it down, since `^`/`$` are already ordinary states inside the automaton (see `nfa.ts`) rather than a separate mode this function chooses between.
|
|
5
|
+
*
|
|
6
|
+
* The technique is the standard "add a fresh thread at every position" Thompson NFA search (Cox, "Regular Expression Matching Can Be Simple And Fast"): a new candidate match start is injected into the active state set before each character is consumed, exactly as if `.*?` had been prepended to an anchored pattern, except no such prefix is ever built -- the injection is the runtime equivalent of it. Because injection happens unconditionally and `^`'s own position check is what actually restricts where a match may begin, a pattern beginning with `^` still only ever matches from position 0, and one that does not can begin anywhere.
|
|
7
|
+
*
|
|
8
|
+
* Runs in O(length x states) time and allocates no more than two small state sets at a time, regardless of the pattern's shape -- there is no exponential blow-up for any input this compiles, which is the whole reason this package builds an automaton instead of calling a backtracking engine.
|
|
9
|
+
*/
|
|
10
|
+
declare function matchesNfa(nfa: CompiledNfa, input: string): boolean;
|
|
11
|
+
/** A parsed and compiled pattern, ready to test any number of inputs without re-parsing or re-compiling. */
|
|
12
|
+
interface CompiledPattern {
|
|
13
|
+
/** Whether any substring of `input` matches this pattern -- see `matchesNfa`. */
|
|
14
|
+
test: (input: string) => boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parses `pattern` (this package's grammar; see README.md) and compiles it to a reference matcher.
|
|
18
|
+
*
|
|
19
|
+
* Prefer this over `testPattern` when the same pattern will be tested against more than one input: it parses and compiles exactly once, and the returned `CompiledPattern.test` is then only the O(length x states) simulation.
|
|
20
|
+
*
|
|
21
|
+
* @throws {RegexParseError} if `pattern` is not a valid pattern in this grammar.
|
|
22
|
+
*/
|
|
23
|
+
declare function compilePattern(pattern: string): CompiledPattern;
|
|
24
|
+
/**
|
|
25
|
+
* Parses `pattern` and tests it against `input` in one call. Equivalent to `compilePattern(pattern).test(input)`, and exactly as cheap for a single test -- prefer `compilePattern` instead when the same pattern will be reused, so the parse and compile steps happen once rather than once per input.
|
|
26
|
+
*
|
|
27
|
+
* @throws {RegexParseError} if `pattern` is not a valid pattern in this grammar.
|
|
28
|
+
*/
|
|
29
|
+
declare function testPattern(pattern: string, input: string): boolean;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { CompiledPattern, compilePattern, matchesNfa, testPattern };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CompiledNfa } from "./nfa.js";
|
|
2
|
+
//#region src/matcher.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Simulates `nfa` against `input` and reports whether any substring of `input` matches -- the same "search," not "full match," semantics a bare `RegExp.prototype.test()` call has without the `^.../$...` anchors pinning it down, since `^`/`$` are already ordinary states inside the automaton (see `nfa.ts`) rather than a separate mode this function chooses between.
|
|
5
|
+
*
|
|
6
|
+
* The technique is the standard "add a fresh thread at every position" Thompson NFA search (Cox, "Regular Expression Matching Can Be Simple And Fast"): a new candidate match start is injected into the active state set before each character is consumed, exactly as if `.*?` had been prepended to an anchored pattern, except no such prefix is ever built -- the injection is the runtime equivalent of it. Because injection happens unconditionally and `^`'s own position check is what actually restricts where a match may begin, a pattern beginning with `^` still only ever matches from position 0, and one that does not can begin anywhere.
|
|
7
|
+
*
|
|
8
|
+
* Runs in O(length x states) time and allocates no more than two small state sets at a time, regardless of the pattern's shape -- there is no exponential blow-up for any input this compiles, which is the whole reason this package builds an automaton instead of calling a backtracking engine.
|
|
9
|
+
*/
|
|
10
|
+
declare function matchesNfa(nfa: CompiledNfa, input: string): boolean;
|
|
11
|
+
/** A parsed and compiled pattern, ready to test any number of inputs without re-parsing or re-compiling. */
|
|
12
|
+
interface CompiledPattern {
|
|
13
|
+
/** Whether any substring of `input` matches this pattern -- see `matchesNfa`. */
|
|
14
|
+
test: (input: string) => boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parses `pattern` (this package's grammar; see README.md) and compiles it to a reference matcher.
|
|
18
|
+
*
|
|
19
|
+
* Prefer this over `testPattern` when the same pattern will be tested against more than one input: it parses and compiles exactly once, and the returned `CompiledPattern.test` is then only the O(length x states) simulation.
|
|
20
|
+
*
|
|
21
|
+
* @throws {RegexParseError} if `pattern` is not a valid pattern in this grammar.
|
|
22
|
+
*/
|
|
23
|
+
declare function compilePattern(pattern: string): CompiledPattern;
|
|
24
|
+
/**
|
|
25
|
+
* Parses `pattern` and tests it against `input` in one call. Equivalent to `compilePattern(pattern).test(input)`, and exactly as cheap for a single test -- prefer `compilePattern` instead when the same pattern will be reused, so the parse and compile steps happen once rather than once per input.
|
|
26
|
+
*
|
|
27
|
+
* @throws {RegexParseError} if `pattern` is not a valid pattern in this grammar.
|
|
28
|
+
*/
|
|
29
|
+
declare function testPattern(pattern: string, input: string): boolean;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { CompiledPattern, compilePattern, matchesNfa, testPattern };
|