j2s/lib/regex.ts

212 lines
6.8 KiB
TypeScript
Raw Normal View History

2023-08-05 15:37:18 +00:00
interface RegExpResultIndices extends Array<[number, number]> {
groups?: { [name: string]: [number, number]; };
}
interface RegExpResult extends Array<string> {
groups?: { [name: string]: string; };
index: number;
input: string;
indices?: RegExpResultIndices;
escape(raw: string, flags: string): RegExp;
}
interface SymbolConstructor {
readonly match: unique symbol;
readonly matchAll: unique symbol;
readonly split: unique symbol;
readonly replace: unique symbol;
readonly search: unique symbol;
}
type ReplaceFunc = (match: string, ...args: any[]) => string;
interface Matcher {
[Symbol.match](target: string): RegExpResult | string[] | null;
[Symbol.matchAll](target: string): IterableIterator<RegExpResult>;
}
interface Splitter {
[Symbol.split](target: string, limit?: number, sensible?: boolean): string[];
}
interface Replacer {
[Symbol.replace](target: string, replacement: string): string;
}
interface Searcher {
[Symbol.search](target: string, reverse?: boolean, start?: number): number;
}
declare class RegExp implements Matcher, Splitter, Replacer, Searcher {
static escape(raw: any, flags?: string): RegExp;
prototype: RegExp;
exec(val: string): RegExpResult | null;
test(val: string): boolean;
toString(): string;
[Symbol.match](target: string): RegExpResult | string[] | null;
[Symbol.matchAll](target: string): IterableIterator<RegExpResult>;
[Symbol.split](target: string, limit?: number, sensible?: boolean): string[];
[Symbol.replace](target: string, replacement: string | ReplaceFunc): string;
[Symbol.search](target: string, reverse?: boolean, start?: number): number;
readonly dotAll: boolean;
readonly global: boolean;
readonly hasIndices: boolean;
readonly ignoreCase: boolean;
readonly multiline: boolean;
readonly sticky: boolean;
readonly unicode: boolean;
readonly source: string;
readonly flags: string;
lastIndex: number;
constructor(pattern?: string, flags?: string);
constructor(pattern?: RegExp, flags?: string);
}
(Symbol as any).replace = Symbol('Symbol.replace');
(Symbol as any).match = Symbol('Symbol.match');
(Symbol as any).matchAll = Symbol('Symbol.matchAll');
(Symbol as any).split = Symbol('Symbol.split');
(Symbol as any).search = Symbol('Symbol.search');
setProps(RegExp.prototype, {
[Symbol.typeName]: 'RegExp',
test(val) {
return !!this.exec(val);
},
toString() {
return '/' + this.source + '/' + this.flags;
},
[Symbol.match](target) {
if (this.global) {
const res: string[] = [];
let val;
while (val = this.exec(target)) {
res.push(val[0]);
}
this.lastIndex = 0;
return res;
}
else {
const res = this.exec(target);
if (!this.sticky) this.lastIndex = 0;
return res;
}
},
[Symbol.matchAll](target) {
let pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp;
return {
next: (): IteratorResult<RegExpResult, undefined> => {
const val = pattern?.exec(target);
if (val === null || val === undefined) {
pattern = undefined;
return { done: true };
}
else return { value: val };
},
[Symbol.iterator]() { return this; }
}
},
[Symbol.split](target, limit, sensible) {
const pattern = new this.constructor(this, this.flags + "g") as RegExp;
let match: RegExpResult | null;
let lastEnd = 0;
const res: string[] = [];
while ((match = pattern.exec(target)) !== null) {
let added: string[] = [];
if (match.index >= target.length) break;
if (match[0].length === 0) {
added = [ target.substring(lastEnd, pattern.lastIndex), ];
if (pattern.lastIndex < target.length) added.push(...match.slice(1));
}
else if (match.index - lastEnd > 0) {
added = [ target.substring(lastEnd, match.index), ...match.slice(1) ];
}
else {
for (let i = 1; i < match.length; i++) {
res[res.length - match.length + i] = match[i];
}
}
if (sensible) {
if (limit !== undefined && res.length + added.length >= limit) break;
else res.push(...added);
}
else {
for (let i = 0; i < added.length; i++) {
if (limit !== undefined && res.length >= limit) return res;
else res.push(added[i]);
}
}
lastEnd = pattern.lastIndex;
}
if (lastEnd < target.length) {
res.push(target.substring(lastEnd));
}
return res;
},
[Symbol.replace](target, replacement) {
const pattern = new this.constructor(this, this.flags + "d") as RegExp;
let match: RegExpResult | null;
let lastEnd = 0;
const res: string[] = [];
// log(pattern.toString());
while ((match = pattern.exec(target)) !== null) {
const indices = match.indices![0];
res.push(target.substring(lastEnd, indices[0]));
if (replacement instanceof Function) {
res.push(replacement(target.substring(indices[0], indices[1]), ...match.slice(1), indices[0], target));
}
else {
res.push(replacement);
}
lastEnd = indices[1];
if (!pattern.global) break;
}
if (lastEnd < target.length) {
res.push(target.substring(lastEnd));
}
return res.join('');
},
[Symbol.search](target, reverse, start) {
const pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp;
if (!reverse) {
pattern.lastIndex = (start as any) | 0;
const res = pattern.exec(target);
if (res) return res.index;
else return -1;
}
else {
start ??= target.length;
start |= 0;
let res: RegExpResult | null = null;
while (true) {
const tmp = pattern.exec(target);
if (tmp === null || tmp.index > start) break;
res = tmp;
}
if (res && res.index <= start) return res.index;
else return -1;
}
},
});