-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathencoder.ts
78 lines (63 loc) · 2.14 KB
/
encoder.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import type { EncodedRegex, RegexElement, RegexSequence } from './types';
import { ensureElements, ensureText } from './utils';
export function encode(sequence: RegexSequence): EncodedRegex {
const elements = ensureElements(sequence);
const encoded = elements.map((n) => encodeElement(n));
if (encoded.length === 1) {
return encoded[0]!;
}
return {
precedence: 'sequence',
pattern: encoded
.map((n) => (n.precedence === 'disjunction' ? encodeAtomic(n) : n.pattern))
.join(''),
};
}
export function encodeAtomic(sequence: RegexSequence): string {
const encoded = encode(sequence);
return encoded.precedence === 'atom' ? encoded.pattern : `(?:${encoded.pattern})`;
}
function encodeElement(element: RegexElement): EncodedRegex {
if (typeof element === 'string') {
return encodeText(element);
}
if (element instanceof RegExp) {
return encodeRegExp(element);
}
if (typeof element === 'object') {
// EncodedRegex
if ('pattern' in element) {
return element;
}
// LazyEncodableRegex
if ('encode' in element) {
return element.encode();
}
}
throw new Error(`Unsupported element. Received: ${JSON.stringify(element, null, 2)}`);
}
function encodeText(text: string): EncodedRegex {
ensureText(text);
return {
// Optimize for single character case
precedence: text.length === 1 ? 'atom' : 'sequence',
pattern: escapeText(text),
};
}
function encodeRegExp(regexp: RegExp): EncodedRegex {
const pattern = regexp.source;
return {
// Encode at safe precedence
precedence: isAtomicPattern(pattern) ? 'atom' : 'disjunction',
pattern,
};
}
// This is intended to catch only some popular atomic patterns like char classes and groups.
function isAtomicPattern(pattern: string): boolean {
// Simple char, char class [...] or group (...)
return pattern.length === 1 || /^\[[^[\]]*\]$/.test(pattern) || /^\([^()]*\)$/.test(pattern);
}
// Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#escaping
function escapeText(text: string) {
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}