@the-w1zard/dx 1.0.12 → 1.0.13
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/dist/index.js +4 -3
- package/package.json +2 -2
- package/src/array.ts +301 -0
- package/src/boolean_byte.ts +23 -0
- package/src/class.ts +0 -0
- package/src/date.ts +660 -0
- package/src/error.ts +81 -0
- package/src/index.ts +45 -0
- package/src/iterator.ts +407 -0
- package/src/map.ts +513 -0
- package/src/map_bin.ts +179 -0
- package/src/map_byte.ts +418 -0
- package/src/map_dense.ts +167 -0
- package/src/map_rows.ts +145 -0
- package/src/number.ts +212 -0
- package/src/object.ts +291 -0
- package/src/promise.ts +309 -0
- package/src/reg_ex.ts +423 -0
- package/src/set.ts +198 -0
- package/src/set_byte.ts +223 -0
- package/src/soa_codec.ts +27 -0
- package/src/string.ts +175 -0
- package/src/unit8array.ts +374 -0
- package/src/url_search_params.ts +187 -0
- package/src/weak_map.ts +155 -0
- package/src/weak_set.ts +101 -0
package/src/reg_ex.ts
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
function escapeRegex(str: S): S {
|
|
2
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3
|
+
}
|
|
4
|
+
function escapeForClass(chars: S): S {
|
|
5
|
+
return chars.replace(/[\]\\^-]/g, "\\$&");
|
|
6
|
+
}
|
|
7
|
+
function toSource(input: RE2Input): S {
|
|
8
|
+
if (typeof input === "string") return input;
|
|
9
|
+
return input.source;
|
|
10
|
+
}
|
|
11
|
+
function isBalancedGroup(src: S): B {
|
|
12
|
+
if (src[0] !== "(" || src[src.length - 1] !== ")") return false;
|
|
13
|
+
let depth = 0;
|
|
14
|
+
for (let i = 0; i < src.length; i++) {
|
|
15
|
+
if (src[i] === "\\") { i++; continue; }
|
|
16
|
+
if (src[i] === "(") depth++;
|
|
17
|
+
else if (src[i] === ")") {
|
|
18
|
+
depth--;
|
|
19
|
+
if (depth === 0 && i !== src.length - 1) return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
function isBalancedClass(src: S): B {
|
|
25
|
+
if (src[0] !== "[" || src[src.length - 1] !== "]") return false;
|
|
26
|
+
for (let i = 1; i < src.length - 1; i++) {
|
|
27
|
+
if (src[i] === "\\") { i++; continue; }
|
|
28
|
+
if (src[i] === "]") return false;
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
function wrapForConcat(src: S): S {
|
|
33
|
+
if (src.length === 1) return src;
|
|
34
|
+
if (/^\\.$/.test(src)) return src;
|
|
35
|
+
if (/^\[.*\]$/.test(src) && isBalancedClass(src)) return src;
|
|
36
|
+
if (/^\(.*\)$/.test(src) && isBalancedGroup(src)) return src;
|
|
37
|
+
return `(?:${src})`;
|
|
38
|
+
}
|
|
39
|
+
function mergeFlags(base: S, parts: AR<RE2Input>): S {
|
|
40
|
+
const set = new Set(base.split(""));
|
|
41
|
+
for (const p of parts) {
|
|
42
|
+
if (p instanceof REC2 || p instanceof RegExp) p.flags.split("").forEach(f => set.add(f));
|
|
43
|
+
}
|
|
44
|
+
return [...set].join("");
|
|
45
|
+
}
|
|
46
|
+
export class REC2 implements RE2_IF {
|
|
47
|
+
#re: RegExp;
|
|
48
|
+
|
|
49
|
+
constructor(source: S, flags: S = "") {
|
|
50
|
+
this.#re = new RegExp(source, flags);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// [PASSTHROUGH]
|
|
54
|
+
// #region ══════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
55
|
+
get source(): S { return this.#re.source; }
|
|
56
|
+
get flags(): S { return this.#re.flags; }
|
|
57
|
+
get global(): B { return this.#re.global; }
|
|
58
|
+
get ignoreCase(): B { return this.#re.ignoreCase; }
|
|
59
|
+
get multiline(): B { return this.#re.multiline; }
|
|
60
|
+
get sticky(): B { return this.#re.sticky; }
|
|
61
|
+
get unicode(): B { return this.#re.unicode; }
|
|
62
|
+
get dotAll(): B { return this.#re.dotAll; }
|
|
63
|
+
get hasIndices(): B { return this.#re.hasIndices; }
|
|
64
|
+
toRegExp(): RegExp { return new RegExp(this.#re.source, this.#re.flags); }
|
|
65
|
+
// #endregion
|
|
66
|
+
// [BASE]
|
|
67
|
+
// #region ══════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
68
|
+
test(str: S): B { return new RegExp(this.#re.source, this.#re.flags).test(str); }
|
|
69
|
+
h(str: S): B { return this.test(str); }
|
|
70
|
+
exec(str: S): RegExpExecArray | null {
|
|
71
|
+
return new RegExp(this.#re.source, this.#re.flags).exec(str);
|
|
72
|
+
}
|
|
73
|
+
g(str: S): S | UD { return this.exec(str)?.[0]; }
|
|
74
|
+
at(str: S, n: N): S | UD { // auto-forces g internally
|
|
75
|
+
const all = this.matches(str);
|
|
76
|
+
const i = n < 0 ? all.length + n : n;
|
|
77
|
+
return all[i]?.match;
|
|
78
|
+
}
|
|
79
|
+
groups(str: S): OB<S> | UD { return this.exec(str)?.groups as OB<S> | UD; }
|
|
80
|
+
group(str: S, name: S | N): S | UD {
|
|
81
|
+
const m = this.exec(str);
|
|
82
|
+
if (!m) return undefined;
|
|
83
|
+
return typeof name === "number" ? m[name] : m.groups?.[name];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
index(str: S): N { return this.exec(str)?.index ?? -1; }
|
|
87
|
+
lastIndexOf(str: S): N { // auto-forces g internally
|
|
88
|
+
const all = this.matches(str);
|
|
89
|
+
return all.length ? all[all.length - 1].index : -1;
|
|
90
|
+
}
|
|
91
|
+
span(str: S): [N, N] | UD {
|
|
92
|
+
const m = this.exec(str);
|
|
93
|
+
return m ? [m.index, m.index + m[0].length] : undefined;
|
|
94
|
+
}
|
|
95
|
+
// #endregion
|
|
96
|
+
// [COLLECT]
|
|
97
|
+
// #region ══════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
98
|
+
all(str: S): AR<S> { return this.matches(str).map(m => m.match); }
|
|
99
|
+
matches(str: S): AR<RE2Match> {
|
|
100
|
+
const out: AR<RE2Match> = [];
|
|
101
|
+
for (const m of str.matchAll(this.#forceGlobal())) {
|
|
102
|
+
out.push({
|
|
103
|
+
match: m[0],
|
|
104
|
+
index: m.index!,
|
|
105
|
+
lastIndex: m.index! + m[0].length,
|
|
106
|
+
groups: m.groups as OB<S> | UD,
|
|
107
|
+
raw: m as RegExpExecArray,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
allGroups(str: S): AR<OB<S>> { return this.matches(str).map(m => m.groups ?? {}); }
|
|
113
|
+
allSpans(str: S): AR<[N, N]> { return this.matches(str).map(m => [m.index, m.lastIndex] as [N, N]); }
|
|
114
|
+
count(str: S): N { return this.matches(str).length; }
|
|
115
|
+
positions(str: S): AR<N> { return this.matches(str).map(m => m.index); }
|
|
116
|
+
// #endregion
|
|
117
|
+
// [REPLACE]
|
|
118
|
+
// #region ══════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
119
|
+
#parseArgs(args: AR<A>, str: S): [RegExpExecArray, OB<S> | UD, N] {
|
|
120
|
+
const groups = typeof args[args.length - 1] === "object" ? args.pop() : undefined;
|
|
121
|
+
const index = args[args.length - 2] as N;
|
|
122
|
+
const raw = [args[0], ...args.slice(1, -2)] as unknown as RegExpExecArray;
|
|
123
|
+
(raw as A).index = index;
|
|
124
|
+
(raw as A).input = str;
|
|
125
|
+
(raw as A).groups = groups;
|
|
126
|
+
return [raw, groups, index];
|
|
127
|
+
}
|
|
128
|
+
replace(str: S, fn: S | RE2ReplaceFn): S {
|
|
129
|
+
const re = new RegExp(this.#re.source, this.#re.flags);
|
|
130
|
+
if (typeof fn === "string") return str.replace(re, fn);
|
|
131
|
+
return str.replace(re, (...a: AR<A>) => {
|
|
132
|
+
const [raw, groups, index] = this.#parseArgs(a, str);
|
|
133
|
+
return fn(raw, groups, index, str);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
replaceFirst(str: S, fn: S | RE2ReplaceFn): S {
|
|
137
|
+
const re = new RegExp(this.#re.source, this.#re.flags.replace("g", ""));
|
|
138
|
+
if (typeof fn === "string") return str.replace(re, fn);
|
|
139
|
+
return str.replace(re, (...a: AR<A>) => {
|
|
140
|
+
const [raw, groups, index] = this.#parseArgs(a, str);
|
|
141
|
+
return fn(raw, groups, index, str);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
replaceAt(str: S, n: N, fn: S | RE2ReplaceFn): S { // auto-forces g internally
|
|
145
|
+
const total = this.matches(str).length;
|
|
146
|
+
const target = n < 0 ? total + n : n;
|
|
147
|
+
let i = 0;
|
|
148
|
+
return str.replace(this.#forceGlobal(), (...a: AR<A>) => {
|
|
149
|
+
const match = a[0] as S;
|
|
150
|
+
const isTarget = i === target;
|
|
151
|
+
i++;
|
|
152
|
+
if (!isTarget) return match;
|
|
153
|
+
if (typeof fn === "string") return fn;
|
|
154
|
+
const [raw, groups, index] = this.#parseArgs(a, str);
|
|
155
|
+
return fn(raw, groups, index, str);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
remove(str: S): S { return this.replace(str, ""); }
|
|
159
|
+
removeFirst(str: S): S { return this.replaceFirst(str, ""); }
|
|
160
|
+
split(str: S, limit?: N): AR<S> { return str.split(new RegExp(this.#re.source, this.#re.flags), limit); }
|
|
161
|
+
splitKeep(str: S): AR<S> {
|
|
162
|
+
const flags = this.flags.includes("g") ? this.flags : this.flags + "g";
|
|
163
|
+
const re = new RegExp(`(${this.#re.source})`, flags);
|
|
164
|
+
return str.split(re).filter(s => s !== "");
|
|
165
|
+
}
|
|
166
|
+
// #endregion
|
|
167
|
+
// [BUILD]
|
|
168
|
+
// #region ══════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
169
|
+
withFlags(flags: S): RE2_IF { return new REC2(this.#re.source, flags); }
|
|
170
|
+
addFlag(flag: RE2Flag): RE2_IF { return this.flags.includes(flag) ? this.clone() : new REC2(this.#re.source, this.flags + flag); }
|
|
171
|
+
removeFlag(flag: RE2Flag): RE2_IF { return new REC2(this.#re.source, this.flags.replace(flag, "")); }
|
|
172
|
+
toggleFlag(flag: RE2Flag): RE2_IF { return this.flags.includes(flag) ? this.removeFlag(flag) : this.addFlag(flag); }
|
|
173
|
+
withSource(src: S): RE2_IF { return new REC2(src, this.flags); }
|
|
174
|
+
and(...parts: AR<RE2Input>): RE2_IF {
|
|
175
|
+
const src = [this.#re.source, ...parts.map(toSource)].map(wrapForConcat).join("");
|
|
176
|
+
return new REC2(src, mergeFlags(this.flags, parts));
|
|
177
|
+
}
|
|
178
|
+
or(...parts: AR<RE2Input>): RE2_IF {
|
|
179
|
+
const src = `(?:${[this.#re.source, ...parts.map(toSource)].join("|")})`;
|
|
180
|
+
return new REC2(src, mergeFlags(this.flags, parts));
|
|
181
|
+
}
|
|
182
|
+
cap(expr?: RE2Input): RE2_IF {
|
|
183
|
+
return new REC2(`(${expr === undefined ? this.#re.source : toSource(expr)})`, this.flags);
|
|
184
|
+
}
|
|
185
|
+
namedGroup(name: S, expr: RE2Input): RE2_IF { return new REC2(`(?<${name}>${toSource(expr)})`, this.flags); }
|
|
186
|
+
nonCap(expr: RE2Input): RE2_IF { return new REC2(`(?:${toSource(expr)})`, this.flags); }
|
|
187
|
+
lookahead(expr: RE2Input): RE2_IF { return new REC2(`(?=${toSource(expr)})`, this.flags); }
|
|
188
|
+
notLookahead(expr: RE2Input): RE2_IF { return new REC2(`(?!${toSource(expr)})`, this.flags); }
|
|
189
|
+
lookbehind(expr: RE2Input): RE2_IF { return new REC2(`(?<=${toSource(expr)})`, this.flags); }
|
|
190
|
+
notLookbehind(expr: RE2Input): RE2_IF { return new REC2(`(?<!${toSource(expr)})`, this.flags); }
|
|
191
|
+
optional(expr?: RE2Input): RE2_IF { return new REC2(`${wrapForConcat(expr === undefined ? this.#re.source : toSource(expr))}?`, this.flags); }
|
|
192
|
+
many(expr?: RE2Input): RE2_IF { return new REC2(`${wrapForConcat(expr === undefined ? this.#re.source : toSource(expr))}+`, this.flags); }
|
|
193
|
+
maybeMany(expr?: RE2Input): RE2_IF { return new REC2(`${wrapForConcat(expr === undefined ? this.#re.source : toSource(expr))}*`, this.flags); }
|
|
194
|
+
repeat(n: N, max?: N, expr?: RE2Input): RE2_IF {
|
|
195
|
+
const src = wrapForConcat(expr === undefined ? this.#re.source : toSource(expr));
|
|
196
|
+
return new REC2(`${src}${max === undefined ? `{${n}}` : `{${n},${max}}`}`, this.flags);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* [BEST-EFFORT] Appends `?` after a trailing `*`/`+`/`?`/`{n,m}` to make it lazy.
|
|
200
|
+
* Only looks at the END of the source — it's not a regex parser, it won't find
|
|
201
|
+
* quantifiers buried mid-pattern. Meant to be chained right after .optional()/
|
|
202
|
+
* .many()/.maybeMany()/.repeat(), where the quantifier IS the last token.
|
|
203
|
+
* If no trailing quantifier is found, returns an unchanged clone.
|
|
204
|
+
*/
|
|
205
|
+
lazy(): RE2_IF {
|
|
206
|
+
const src = this.#re.source;
|
|
207
|
+
if (/[*+?]$/.test(src) || /\{\d+(,\d*)?\}$/.test(src)) return new REC2(`${src}?`, this.flags);
|
|
208
|
+
return this.clone();
|
|
209
|
+
}
|
|
210
|
+
// #endregion
|
|
211
|
+
// [DEBUG/SAFETY]
|
|
212
|
+
// #region ══════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
213
|
+
$trace(str: S): AR<RE2Match> { return this.matches(str); }
|
|
214
|
+
$explain(): AR<S> {
|
|
215
|
+
const flagNotes: AR<S> = [];
|
|
216
|
+
if (this.global) flagNotes.push("matches all occurrences (g)");
|
|
217
|
+
if (this.ignoreCase) flagNotes.push("case-insensitive (i)");
|
|
218
|
+
if (this.multiline) flagNotes.push("^/$ match per line (m)");
|
|
219
|
+
if (this.dotAll) flagNotes.push(". matches newlines (s)");
|
|
220
|
+
if (this.sticky) flagNotes.push("sticky from lastIndex (y)");
|
|
221
|
+
if (this.unicode) flagNotes.push("unicode mode (u)");
|
|
222
|
+
|
|
223
|
+
return [...flagNotes, "pattern:", ...explainSource(this.#re.source).map(l => ` ${l}`)];
|
|
224
|
+
}
|
|
225
|
+
$risky(): B { return /\([^)]*[+*][^)]*\)[+*]/.test(this.#re.source); }
|
|
226
|
+
$bench(str: S, iterations: N = 1000): N {
|
|
227
|
+
const re = new RegExp(this.#re.source, this.flags);
|
|
228
|
+
const start = performance.now();
|
|
229
|
+
for (let i = 0; i < iterations; i++) re.test(str);
|
|
230
|
+
return performance.now() - start;
|
|
231
|
+
}
|
|
232
|
+
// #endregion
|
|
233
|
+
// [CODECS]
|
|
234
|
+
// #region ══════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
235
|
+
toString(): S { return this.#re.toString(); }
|
|
236
|
+
equals(other: RE2Input): B {
|
|
237
|
+
const o = typeof other === "string" ? new RegExp(other)
|
|
238
|
+
: other instanceof RegExp ? other
|
|
239
|
+
: other; // remaining case is RE2_IF, which has .source/.flags already
|
|
240
|
+
return this.#re.source === o.source && this.#re.flags === o.flags;
|
|
241
|
+
}
|
|
242
|
+
clone(): RE2_IF { return new REC2(this.#re.source, this.flags); }
|
|
243
|
+
|
|
244
|
+
#forceGlobal(): RegExp {
|
|
245
|
+
const flags = this.flags.includes("g") ? this.flags : this.flags + "g";
|
|
246
|
+
return new RegExp(this.#re.source, flags);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
static fromRegExp(re: RegExp): REC2 { return new REC2(re.source, re.flags); }
|
|
250
|
+
// #endregion
|
|
251
|
+
}
|
|
252
|
+
// [PATTERN EXPLAIN ENGINE]
|
|
253
|
+
// ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
254
|
+
|
|
255
|
+
interface ExplainNode {
|
|
256
|
+
desc: S;
|
|
257
|
+
children?: AR<ExplainNode>;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function explainSource(source: S): AR<S> {
|
|
261
|
+
let i = 0;
|
|
262
|
+
const n = source.length;
|
|
263
|
+
const peek = (): S => source[i];
|
|
264
|
+
const eof = (): B => i >= n;
|
|
265
|
+
|
|
266
|
+
function parseAlt(): ExplainNode {
|
|
267
|
+
const branches = [parseSeq()];
|
|
268
|
+
while (!eof() && peek() === "|") { i++; branches.push(parseSeq()); }
|
|
269
|
+
return branches.length === 1 ? branches[0] : { desc: "one of:", children: branches };
|
|
270
|
+
}
|
|
271
|
+
function parseSeq(): ExplainNode {
|
|
272
|
+
const items: AR<ExplainNode> = [];
|
|
273
|
+
while (!eof() && peek() !== "|" && peek() !== ")") items.push(parseQuantified());
|
|
274
|
+
if (items.length === 0) return { desc: "(empty)" };
|
|
275
|
+
return items.length === 1 ? items[0] : { desc: "in sequence:", children: items };
|
|
276
|
+
}
|
|
277
|
+
function parseQuantified(): ExplainNode {
|
|
278
|
+
const atom = parseAtom();
|
|
279
|
+
if (eof()) return atom;
|
|
280
|
+
|
|
281
|
+
let quant: S | UD;
|
|
282
|
+
const c = peek();
|
|
283
|
+
if (c === "*") { i++; quant = "zero or more"; }
|
|
284
|
+
else if (c === "+") { i++; quant = "one or more"; }
|
|
285
|
+
else if (c === "?") { i++; quant = "optional (zero or one)"; }
|
|
286
|
+
else if (c === "{") {
|
|
287
|
+
const m = /^\{(\d+)(,(\d*))?\}/.exec(source.slice(i));
|
|
288
|
+
if (m) {
|
|
289
|
+
i += m[0].length;
|
|
290
|
+
quant = m[2] === undefined ? `exactly ${m[1]} time${m[1] === "1" ? "" : "s"}`
|
|
291
|
+
: m[3] === "" ? `${m[1]} or more times`
|
|
292
|
+
: `between ${m[1]} and ${m[3]} times`;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (!quant) return atom;
|
|
296
|
+
|
|
297
|
+
let lazy = false;
|
|
298
|
+
if (!eof() && peek() === "?") { i++; lazy = true; }
|
|
299
|
+
return { desc: `${atom.desc} — ${quant}${lazy ? " (lazy, as few as possible)" : ""}`, children: atom.children };
|
|
300
|
+
}
|
|
301
|
+
function parseAtom(): ExplainNode {
|
|
302
|
+
const c = peek();
|
|
303
|
+
if (c === "(") return parseGroup();
|
|
304
|
+
if (c === "[") return parseClass();
|
|
305
|
+
if (c === "^") { i++; return { desc: "start of string/line" }; }
|
|
306
|
+
if (c === "$") { i++; return { desc: "end of string/line" }; }
|
|
307
|
+
if (c === ".") { i++; return { desc: "any character" }; }
|
|
308
|
+
if (c === "\\") return parseEscape();
|
|
309
|
+
|
|
310
|
+
let lit = "";
|
|
311
|
+
while (!eof() && !"\\^$.|?*+()[]{}".includes(peek())) lit += source[i++];
|
|
312
|
+
if (lit) return { desc: `literally "${lit}"` };
|
|
313
|
+
i++;
|
|
314
|
+
return { desc: `literally "${c}"` };
|
|
315
|
+
}
|
|
316
|
+
function parseEscape(): ExplainNode {
|
|
317
|
+
i++;
|
|
318
|
+
const c = source[i++];
|
|
319
|
+
const map: OB<S> = {
|
|
320
|
+
d: "a digit", D: "a non-digit", w: "a word character", W: "a non-word character",
|
|
321
|
+
s: "whitespace", S: "non-whitespace", b: "a word boundary", B: "not a word boundary",
|
|
322
|
+
n: "a newline", t: "a tab", r: "a carriage return",
|
|
323
|
+
};
|
|
324
|
+
return { desc: map[c] ?? `literally "${c}"` };
|
|
325
|
+
}
|
|
326
|
+
function parseClass(): ExplainNode {
|
|
327
|
+
i++;
|
|
328
|
+
let negate = false;
|
|
329
|
+
if (peek() === "^") { negate = true; i++; }
|
|
330
|
+
let body = "";
|
|
331
|
+
while (!eof() && peek() !== "]") {
|
|
332
|
+
body += peek() === "\\" ? source[i++] + source[i++] : source[i++];
|
|
333
|
+
}
|
|
334
|
+
i++;
|
|
335
|
+
return { desc: `${negate ? "any character NOT" : "any character"} in [${body}]` };
|
|
336
|
+
}
|
|
337
|
+
function parseGroup(): ExplainNode {
|
|
338
|
+
i++; // consume '('
|
|
339
|
+
let kind = "capturing group";
|
|
340
|
+
|
|
341
|
+
if (peek() === "?") {
|
|
342
|
+
const c2 = source[i + 1];
|
|
343
|
+
if (c2 === ":") { i += 2; kind = "non-capturing group"; }
|
|
344
|
+
else if (c2 === "=") { i += 2; kind = "lookahead — must be followed by"; }
|
|
345
|
+
else if (c2 === "!") { i += 2; kind = "negative lookahead — must NOT be followed by"; }
|
|
346
|
+
else if (c2 === "<" && source[i + 2] === "=") { i += 3; kind = "lookbehind — must be preceded by"; }
|
|
347
|
+
else if (c2 === "<" && source[i + 2] === "!") { i += 3; kind = "negative lookbehind — must NOT be preceded by"; }
|
|
348
|
+
else if (c2 === "<") {
|
|
349
|
+
i += 2;
|
|
350
|
+
let nm = "";
|
|
351
|
+
while (source[i] !== ">") nm += source[i++];
|
|
352
|
+
i++;
|
|
353
|
+
kind = `named capturing group "${nm}"`;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const inner = parseAlt();
|
|
357
|
+
if (peek() === ")") i++;
|
|
358
|
+
return { desc: kind, children: [inner] };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const tree = parseAlt();
|
|
362
|
+
const lines: AR<S> = [];
|
|
363
|
+
(function render(node: ExplainNode, depth: N) {
|
|
364
|
+
lines.push(`${" ".repeat(depth)}${node.desc}`);
|
|
365
|
+
node.children?.forEach(c => render(c, depth + 1));
|
|
366
|
+
})(tree, 0);
|
|
367
|
+
|
|
368
|
+
return lines;
|
|
369
|
+
}
|
|
370
|
+
// [STATICS / PRESETS]
|
|
371
|
+
// ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
372
|
+
export const RECC2: RE2Static = Object.assign(
|
|
373
|
+
(input: RE2Input, flags?: S): RE2_IF => RECC2.of(input, flags),
|
|
374
|
+
{
|
|
375
|
+
of(input: RE2Input, flags?: S): RE2_IF {
|
|
376
|
+
if (typeof input === "string") return new REC2(input, flags ?? "");
|
|
377
|
+
if (input instanceof RegExp) return new REC2(input.source, flags ?? input.flags);
|
|
378
|
+
// remaining case is RE2_IF
|
|
379
|
+
return flags !== undefined ? input.withFlags(flags) : input.clone();
|
|
380
|
+
},
|
|
381
|
+
escape: escapeRegex,
|
|
382
|
+
literal(str: S): RE2_IF { return new REC2(escapeRegex(str)); },
|
|
383
|
+
seq(...parts: AR<RE2Input>): RE2_IF {
|
|
384
|
+
return new REC2(parts.map(p => wrapForConcat(toSource(p))).join(""), mergeFlags("", parts));
|
|
385
|
+
},
|
|
386
|
+
alt(...parts: AR<RE2Input>): RE2_IF {
|
|
387
|
+
return new REC2(`(?:${parts.map(toSource).join("|")})`, mergeFlags("", parts));
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
digit: new REC2("\\d"), word: new REC2("\\w"), space: new REC2("\\s"), any: new REC2("."),
|
|
391
|
+
notDigit: new REC2("\\D"), notWord: new REC2("\\W"), notSpace: new REC2("\\S"),
|
|
392
|
+
range(from: S, to: S): RE2_IF { return new REC2(`[${from}-${to}]`); },
|
|
393
|
+
oneOf(chars: S): RE2_IF { return new REC2(`[${escapeForClass(chars)}]`); },
|
|
394
|
+
noneOf(chars: S): RE2_IF { return new REC2(`[^${escapeForClass(chars)}]`); },
|
|
395
|
+
|
|
396
|
+
email: new REC2("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"),
|
|
397
|
+
url: new REC2("https?:\\/\\/[^\\s/$.?#].[^\\s]*", "i"),
|
|
398
|
+
uuid: new REC2("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", "i"),
|
|
399
|
+
ipv4: new REC2("(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)"),
|
|
400
|
+
ipv6: new REC2("(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}"),
|
|
401
|
+
hex: new REC2("[0-9a-fA-F]+"),
|
|
402
|
+
hexColor: new REC2("#(?:[0-9a-fA-F]{3}){1,2}\\b"),
|
|
403
|
+
slug: new REC2("[a-z0-9]+(?:-[a-z0-9]+)*"),
|
|
404
|
+
alphaNumeric: new REC2("[a-zA-Z0-9]+"),
|
|
405
|
+
integer: new REC2("-?\\d+"),
|
|
406
|
+
float: new REC2("-?\\d+(?:\\.\\d+)?"),
|
|
407
|
+
phoneLoose: new REC2("\\+?\\d[\\d\\s().-]{6,}\\d"),
|
|
408
|
+
isoDate: new REC2("\\d{4}-\\d{2}-\\d{2}"),
|
|
409
|
+
base64: new REC2("(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?"),
|
|
410
|
+
jwt: new REC2("[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+"),
|
|
411
|
+
semver: new REC2("\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?"),
|
|
412
|
+
creditCard: new REC2("\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{1,4}"),
|
|
413
|
+
htmlTag: new REC2("<\\/?[a-zA-Z][^>]*>"),
|
|
414
|
+
whitespaceOnly: new REC2("^\\s*$"),
|
|
415
|
+
},
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
(globalThis as A).REC2 = REC2;
|
|
419
|
+
(globalThis as A).RE2 = RECC2;
|
|
420
|
+
|
|
421
|
+
/* ====================================================================================================
|
|
422
|
+
|
|
423
|
+
============================================================= ⇦✦──⇦✦──✦──✦──← 'pew pew' 🧙 [REGEX] */
|
package/src/set.ts
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { M2I } from "./map";
|
|
2
|
+
|
|
3
|
+
export class SET2I<T = A> extends Set<T> implements SET2<T> {
|
|
4
|
+
public s = this.add;
|
|
5
|
+
public take(value: T): T | UD {
|
|
6
|
+
if (!this.delete(value)) return undefined as UD;
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
public peek(value: T): T | false { return this.has(value) ? value : false; }
|
|
10
|
+
public toggle(value: T): B {
|
|
11
|
+
if (this.has(value)) {
|
|
12
|
+
this.delete(value);
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
this.add(value);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
public first(): T | UD {
|
|
19
|
+
const result = this.values().next();
|
|
20
|
+
return result.done ? undefined as UD : result.value;
|
|
21
|
+
}
|
|
22
|
+
public last(): T | UD {
|
|
23
|
+
let result: T | UD = undefined as UD;
|
|
24
|
+
for (const value of this) result = value;
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
public union(other: Iterable<T>): SET2I<T> {
|
|
28
|
+
const out = new SET2I(this);
|
|
29
|
+
for (const value of other) out.add(value);
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
public intersection(other: Iterable<T>): SET2I<T> {
|
|
33
|
+
const values = new Set(other);
|
|
34
|
+
const out = new SET2I<T>();
|
|
35
|
+
for (const value of this) if (values.has(value)) out.add(value);
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
public difference(other: Iterable<T>): SET2I<T> {
|
|
39
|
+
const values = new Set(other);
|
|
40
|
+
const out = new SET2I<T>();
|
|
41
|
+
for (const value of this) if (!values.has(value)) out.add(value);
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
public symmetricDifference(other: Iterable<T>): SET2I<T> {
|
|
45
|
+
const values = new Set(other);
|
|
46
|
+
const out = new SET2I<T>();
|
|
47
|
+
|
|
48
|
+
for (const value of this) if (!values.has(value)) out.add(value);
|
|
49
|
+
for (const value of values) if (!this.has(value)) out.add(value);
|
|
50
|
+
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
public isSubsetOf(other: Iterable<T>): B {
|
|
54
|
+
const values = new Set(other);
|
|
55
|
+
for (const value of this) if (!values.has(value)) return false;
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
public isSupersetOf(other: Iterable<T>): B {
|
|
59
|
+
for (const value of other) if (!this.has(value)) return false;
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
public isDisjointFrom(other: Iterable<T>): B {
|
|
63
|
+
const values = new Set(other);
|
|
64
|
+
for (const value of this) if (values.has(value)) return false;
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
public equals(other: Iterable<T>): B {
|
|
68
|
+
const values = new Set(other);
|
|
69
|
+
return this.size === values.size && this.isSubsetOf(values);
|
|
70
|
+
}
|
|
71
|
+
public filter(fn: (v: T, set: this) => B): SET2I<T> {
|
|
72
|
+
const out = new SET2I<T>();
|
|
73
|
+
for (const value of this) if (fn(value, this)) out.add(value);
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
public map<R>(fn: (v: T, set: this) => R): SET2I<R> {
|
|
77
|
+
const out = new SET2I<R>();
|
|
78
|
+
for (const value of this) out.add(fn(value, this));
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
public partition(fn: (v: T, set: this) => B): [SET2I<T>, SET2I<T>] {
|
|
82
|
+
const yes = new SET2I<T>();
|
|
83
|
+
const no = new SET2I<T>();
|
|
84
|
+
|
|
85
|
+
for (const value of this) {
|
|
86
|
+
(fn(value, this) ? yes : no).add(value);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return [yes, no];
|
|
90
|
+
}
|
|
91
|
+
public differenceBy<K>(other: Iterable<T>, fn: (v: T) => K): SET2I<T> {
|
|
92
|
+
const keys = new Set<K>();
|
|
93
|
+
for (const value of other) keys.add(fn(value));
|
|
94
|
+
|
|
95
|
+
const out = new SET2I<T>();
|
|
96
|
+
for (const value of this) if (!keys.has(fn(value))) out.add(value);
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
public uniqueBy<K>(fn: (v: T) => K): SET2I<T> {
|
|
100
|
+
const keys = new Set<K>();
|
|
101
|
+
const out = new SET2I<T>();
|
|
102
|
+
|
|
103
|
+
for (const value of this) {
|
|
104
|
+
const key = fn(value);
|
|
105
|
+
if (keys.has(key)) continue;
|
|
106
|
+
keys.add(key);
|
|
107
|
+
out.add(value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
public find(fn: (v: T, set: this) => B): T | UD {
|
|
113
|
+
for (const value of this) if (fn(value, this)) return value;
|
|
114
|
+
return undefined as UD;
|
|
115
|
+
}
|
|
116
|
+
public every(fn: (v: T, set: this) => B): B {
|
|
117
|
+
for (const value of this) if (!fn(value, this)) return false;
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
public some(fn: (v: T, set: this) => B): B {
|
|
121
|
+
for (const value of this) if (fn(value, this)) return true;
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
public reduce<R>(fn: (acc: R, v: T, set: this) => R, initial: R): R {
|
|
125
|
+
let result = initial;
|
|
126
|
+
for (const value of this) result = fn(result, value, this);
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
public addAll(values: Iterable<T>): this {
|
|
130
|
+
for (const value of values) this.add(value);
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
public deleteAll(values: Iterable<T>): N {
|
|
134
|
+
let count = 0;
|
|
135
|
+
for (const value of values) if (this.delete(value)) count++;
|
|
136
|
+
return count as N;
|
|
137
|
+
}
|
|
138
|
+
public retainAll(values: Iterable<T>): this {
|
|
139
|
+
const keep = new Set(values);
|
|
140
|
+
for (const value of this) if (!keep.has(value)) this.delete(value);
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
public update(values: Iterable<T>): this {
|
|
144
|
+
return this.addAll(values);
|
|
145
|
+
}
|
|
146
|
+
public replace(values: Iterable<T>): this {
|
|
147
|
+
const replacement = new Set(values);
|
|
148
|
+
this.clear();
|
|
149
|
+
for (const value of replacement) this.add(value);
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
public swap(a: T, b: T): B {
|
|
153
|
+
if (!this.has(a)) return false;
|
|
154
|
+
this.delete(a);
|
|
155
|
+
this.add(b);
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
public clone(): this {
|
|
159
|
+
return new SET2I(this) as this;
|
|
160
|
+
}
|
|
161
|
+
public toArray(): T[] {
|
|
162
|
+
return [...this];
|
|
163
|
+
}
|
|
164
|
+
public toMap<K, R>(fn: (v: T) => readonly [K, R]): M2I<K, R> {
|
|
165
|
+
const out = new M2<K, R>();
|
|
166
|
+
for (const value of this) {
|
|
167
|
+
const [key, result] = fn(value);
|
|
168
|
+
out.set(key, result);
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
public toObject(fn?: (v: T) => S): OB<T> {
|
|
173
|
+
const out = {} as OB<T>;
|
|
174
|
+
for (const value of this) {
|
|
175
|
+
out[(fn ? fn(value) : String(value)) as S] = value;
|
|
176
|
+
}
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
public toJSON(): T[] {
|
|
180
|
+
return this.toArray();
|
|
181
|
+
}
|
|
182
|
+
static fromArray<T>(values: T[]): SET2I<T> {
|
|
183
|
+
return new SET2I(values);
|
|
184
|
+
}
|
|
185
|
+
static fromObject<O extends Record<string, A>>(obj: O): SET2I<O[keyof O]> {
|
|
186
|
+
return new SET2I(Object.values(obj) as O[keyof O][]);
|
|
187
|
+
}
|
|
188
|
+
static fromEntries<T>(entries: Iterable<readonly [unknown, T]>): SET2I<T> {
|
|
189
|
+
const out = new SET2I<T>();
|
|
190
|
+
for (const [, value] of entries) out.add(value);
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
static from<T>(values: Iterable<T>): SET2I<T> {
|
|
194
|
+
return new SET2I(values);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
globalThis.SET2 = SET2I;
|