Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions examples/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/Procedures/procExecRegExp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('procExecRegExp', () => {

test('RequestExecRegExp bad regex', () => {
const req: RequestExecRegExp = createRequest(requestTypeExecRegExp, { text: 'two words', regexp: /\[/g });
Object.assign(req.data, { regexp: '/[/g' });
Object.assign(req.data, { regexp: { source: '/[/g' } });
const result = procExecRegExp(req);
const response = isErrorResponse(result) ? result : undefined;
expect(isExecRegExpResponse(result)).toBe(false);
Expand Down
3 changes: 2 additions & 1 deletion src/Procedures/procExecRegExp.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ExecRegExpResult } from '../helpers/evaluateRegExp.js';
import { execRegExp, toRegExp } from '../helpers/evaluateRegExp.js';
import type { RegExpLike } from '../helpers/regexp.js';
import { format } from '../util/format.js';
import {
createErrorResponse,
Expand All @@ -18,7 +19,7 @@ export const requestTypeExecRegExp: ExecRegExpRequestType = 'ExecRegExp';

export interface RequestExecRegExpData {
text: string;
regexp: RegExp;
regexp: RegExpLike;
}

export type RequestExecRegExp = Request<ExecRegExpRequestType, RequestExecRegExpData>;
Expand Down
2 changes: 1 addition & 1 deletion src/Procedures/procMatchAllRegExp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('procMatchAllRegExp', () => {

test('RequestExecRegExp bad regex', () => {
const req: RequestMatchAllRegExp = createRequestMatchAllRegExp({ text: 'two words', regexp: /\[/g });
Object.assign(req.data, { regexp: '/[/g' });
Object.assign(req.data, { regexp: { source: '[', flags: 'g' } });
const result = procMatchAllRegExp(req);
const response = isErrorResponse(result) ? result : undefined;
expect(isMatchAllRegExpResponse(result)).toBe(false);
Expand Down
3 changes: 2 additions & 1 deletion src/Procedures/procMatchAllRegExp.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { MatchAllRegExpResult } from '../helpers/evaluateRegExp.js';
import { matchAllRegExp, toRegExp } from '../helpers/evaluateRegExp.js';
import type { RegExpLike } from '../helpers/regexp.js';
import { format } from '../util/format.js';
import type { ErrorResponse, Request, Response } from './procedure.js';
import { createErrorResponse, createRequest, createResponse, isRequestType, isResponseType } from './procedure.js';
Expand All @@ -10,7 +11,7 @@ export type MatchAllRegExpResponseType = MatchAllRegExpRequestType;

export interface RequestMatchAllRegExpData {
text: string;
regexp: RegExp;
regexp: RegExpLike;
}

export type RequestMatchAllRegExp = Request<MatchAllRegExpRequestType, RequestMatchAllRegExpData>;
Expand Down
2 changes: 1 addition & 1 deletion src/Procedures/procMatchAllRegExpArray.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('procMatchRegExpArray', () => {

test('RequestExecRegExp bad regex', () => {
const req: Request = createRequestMatchRegExpArray({ text: 'two words', regexps: [/\[/g] });
Object.assign(req.data as string[], { regexps: ['/[/g'] }); // Intentionally malformed regex
Object.assign(req.data as string[], { regexps: [{ source: '[', flags: 'g' }] }); // Intentionally malformed regex
const result = procMatchAllRegExpArray(req);
const response = isErrorResponse(result) ? result : undefined;
expect(isMatchRegExpArrayResponse(result)).toBe(false);
Expand Down
3 changes: 2 additions & 1 deletion src/Procedures/procMatchAllRegExpArray.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { MatchAllRegExpArrayResult } from '../helpers/evaluateRegExp.js';
import { matchAllRegExpArray, toRegExp } from '../helpers/evaluateRegExp.js';
import type { RegExpLike } from '../helpers/regexp.js';
import { format } from '../util/format.js';
import type { ErrorResponse, Request, Response } from './procedure.js';
import { createErrorResponse, createRequest, createResponse, isRequestType, isResponseType } from './procedure.js';
Expand All @@ -10,7 +11,7 @@ export type MatchRegExpArrayResponseType = MatchRegExpArrayRequestType;

export interface RequestMatchRegExpArrayData {
text: string;
regexps: RegExp[];
regexps: RegExpLike[];
}

export type RequestMatchAllRegExpArray = Request<MatchRegExpArrayRequestType, RequestMatchRegExpArrayData>;
Expand Down
2 changes: 1 addition & 1 deletion src/Procedures/procMatchAllRegExpAsRange.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('procMatchAllRegExpAsRange', () => {

test('RequestExecRegExp bad regex', () => {
const req: RequestMatchAllRegExpAsRange = createRequestMatchAllRegExpAsRange({ text: 'two words', regexp: /\[/g });
Object.assign(req.data, { regexp: '/[/g' });
Object.assign(req.data, { regexp: { source: '[', flags: 'g' } });
const result = procMatchAllRegExpAsRange(req);
const response = isErrorResponse(result) ? result : undefined;
expect(isMatchAllRegExpAsRangeResponse(result)).toBe(false);
Expand Down
3 changes: 2 additions & 1 deletion src/Procedures/procMatchAllRegExpAsRange.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { MatchAllToRangesRegExpResult } from '../helpers/evaluateRegExp.js';
import { matchAllToRangesRegExp, toRegExp } from '../helpers/evaluateRegExp.js';
import type { RegExpLike } from '../helpers/regexp.js';
import { format } from '../util/format.js';
import type { ErrorResponse, Request, Response } from './procedure.js';
import { createErrorResponse, createRequest, createResponse, isRequestType, isResponseType } from './procedure.js';
Expand All @@ -10,7 +11,7 @@ export type MatchAllRegExpAsRangeResponseType = MatchAllRegExpAsRangeRequestType

export interface RequestMatchAllRegExpAsRangeData {
text: string;
regexp: RegExp;
regexp: RegExpLike;
}

export type RequestMatchAllRegExpAsRange = Request<MatchAllRegExpAsRangeRequestType, RequestMatchAllRegExpAsRangeData>;
Expand Down
2 changes: 1 addition & 1 deletion src/Procedures/procMatchRegExp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('procMatchRegExp', () => {
});

test('RequestExecRegExp bad regex', () => {
const req: Request = createRequestMatchRegExp({ text: 'two words', regexp: '/[/g' });
const req: Request = createRequestMatchRegExp({ text: 'two words', regexp: { source: '[', flags: 'g' } });
const result = procMatchRegExp(req);
const response = isErrorResponse(result) ? result : undefined;
expect(isMatchRegExpResponse(result)).toBe(false);
Expand Down
3 changes: 2 additions & 1 deletion src/Procedures/procMatchRegExp.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { MatchRegExpResult } from '../helpers/evaluateRegExp.js';
import { matchRegExp, toRegExp } from '../helpers/evaluateRegExp.js';
import type { RegExpLike } from '../helpers/regexp.js';
import { format } from '../util/format.js';
import type { ErrorResponse, Request, Response } from './procedure.js';
import { createErrorResponse, createRequest, createResponse, isRequestType, isResponseType } from './procedure.js';
Expand All @@ -10,7 +11,7 @@ export type MatchRegExpResponseType = MatchRegExpRequestType;

export interface RequestMatchRegExpData {
text: string;
regexp: RegExp | string;
regexp: RegExpLike;
}

export type RequestMatchRegExp = Request<MatchRegExpRequestType, RequestMatchRegExpData>;
Expand Down
21 changes: 13 additions & 8 deletions src/RegExpWorker.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { describe, expect, test } from 'vitest';

Expand Down Expand Up @@ -34,14 +35,18 @@ expect.extend({
// cspell:ignore hellothere

describe('RegExpWorker', () => {
test(
'matchAll',
run(async (w) => {
const r = await w.matchAll('hello\nthere', /\w/g);
expect(r.matches.map((m) => m[0])).toEqual('hellothere'.split(''));
expect(r.elapsedTimeMs).toBeGreaterThan(0);
}),
);
test.each`
text | regexp | expected
${'hello\nthere'} | ${/\w/g} | ${'hellothere'.split('')}
${'Good Morning'} | ${/\b\w+/g} | ${['Good', 'Morning']}
${'Good Morning.'} | ${/Good/} | ${['Good']}
${'Good Morning.'} | ${/\b\w+/g} | ${['Good', 'Morning']}
`('matchAll $test $regexp', async ({ text, regexp, expected }) => {
await using worker = cr();
const r = await worker.matchAll(text, regexp);
expect(r.matches.map((m) => m[0])).toEqual(expected);
expect(r.elapsedTimeMs).toBeGreaterThan(0);
});

test(
'set timeout',
Expand Down
141 changes: 128 additions & 13 deletions src/RegExpWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ import type {
MatchAllToRangesRegExpResult,
MatchRegExpResult,
} from './helpers/evaluateRegExp.js';
import { type MatchAllRegExpResult } from './helpers/evaluateRegExp.js';
import type { MatchAllRegExpResult } from './helpers/evaluateRegExp.js';
import { normalizeRegExp, type RegExpLike } from './helpers/regexp.js';
import type { RequestMatchRegExp } from './Procedures/index.js';
import type {
RequestExecRegExp,
RequestMatchAllRegExp,
RequestMatchAllRegExpArray,
RequestMatchAllRegExpAsRange,
Response,
} from './Procedures/index.js';
import {
createRequestExecRegExp,
createRequestMatchAllRegExp,
createRequestMatchRegExp,
createRequestMatchRegExpArray,
type RequestExecRegExp,
type RequestMatchAllRegExp,
type RequestMatchAllRegExpArray,
type RequestMatchAllRegExpAsRange,
type Response,
} from './Procedures/index.js';
import { createRequestMatchAllRegExpAsRange } from './Procedures/procMatchAllRegExpAsRange.js';
import { Scheduler } from './scheduler/index.js';
Expand All @@ -24,6 +27,18 @@ import type { CreateWorker } from './worker/di.js';

export type { ExecRegExpResult, MatchAllRegExpArrayResult, MatchAllRegExpResult, MatchRegExpResult } from './helpers/evaluateRegExp.js';

if (typeof Symbol.dispose === 'undefined') {
// Polyfill for Symbol.dispose if not available
// This is necessary for environments that do not support the dispose protocol natively
(Symbol as unknown as { dispose: symbol }).dispose = Symbol.for('dispose');
}

if (typeof Symbol.asyncDispose === 'undefined') {
// Polyfill for Symbol.dispose if not available
// This is necessary for environments that do not support the dispose protocol natively
(Symbol as unknown as { asyncDispose: symbol }).asyncDispose = Symbol.for('asyncDispose');
}

export class RegExpWorkerBase {
private scheduler: Scheduler;
public dispose: () => Promise<void> = () => this._dispose();
Expand All @@ -44,7 +59,8 @@ export class RegExpWorkerBase {
* @param text - The text to search within.
* @param timeLimitMs - Optional time limit in milliseconds for the operation.
*/
public exec(regExp: RegExp, text: string, timeLimitMs?: number): Promise<ExecRegExpResult> {
public exec(regExp: RegExpLike, text: string, timeLimitMs?: number): Promise<ExecRegExpResult> {
regExp = normalizeRegExp(regExp);
const req = createRequestExecRegExp({ regexp: regExp, text });
return this.makeRequest(req, timeLimitMs);
}
Expand All @@ -55,7 +71,8 @@ export class RegExpWorkerBase {
* @param regExp - The regular expression to match against the text.
* @param timeLimitMs - Optional time limit in milliseconds for the operation.
*/
public matchAll(text: string, regExp: RegExp, timeLimitMs?: number): Promise<MatchAllRegExpResult> {
public matchAll(text: string, regExp: RegExpLike, timeLimitMs?: number): Promise<MatchAllRegExpResult> {
regExp = normalizeRegExp(regExp);
const req = createRequestMatchAllRegExp({ regexp: regExp, text });
return this.makeRequest(req, timeLimitMs);
}
Expand All @@ -66,7 +83,8 @@ export class RegExpWorkerBase {
* @param regExp - The regular expression to match against the text.
* @param timeLimitMs - Optional time limit in milliseconds for the operation.
*/
public match(text: string, regExp: RegExp, timeLimitMs?: number): Promise<MatchRegExpResult> {
public match(text: string, regExp: RegExpLike, timeLimitMs?: number): Promise<MatchRegExpResult> {
regExp = normalizeRegExp(regExp);
const req = createRequestMatchRegExp({ regexp: regExp, text });
return this.makeRequest(req, timeLimitMs);
}
Expand All @@ -77,19 +95,21 @@ export class RegExpWorkerBase {
* @param regExps - An array of regular expressions to match against the text.
* @param timeLimitMs - Optional time limit in milliseconds for the operation.
*/
public matchAllArray(text: string, regExp: RegExp[], timeLimitMs?: number): Promise<MatchAllRegExpArrayResult> {
public matchAllArray(text: string, regExp: RegExpLike[], timeLimitMs?: number): Promise<MatchAllRegExpArrayResult> {
regExp = regExp.map(normalizeRegExp);
const req = createRequestMatchRegExpArray({ regexps: regExp, text });
return this.makeRequest(req, timeLimitMs);
}

/**
* Runs text.matchAll against an array of RegExps in a worker.
* @param text - The text to search within.
* @param regExps - An array of regular expressions to match against the text.
* @param regExp - A regular expressions to match against the text.
* @param timeLimitMs - Optional time limit in milliseconds for the operation.
*/
public async matchAllAsRangePairs(text: string, regexp: RegExp, timeLimitMs?: number): Promise<MatchAllAsRangePairsResult> {
const req = createRequestMatchAllRegExpAsRange({ regexp, text });
public async matchAllAsRangePairs(text: string, regExp: RegExpLike, timeLimitMs?: number): Promise<MatchAllAsRangePairsResult> {
regExp = normalizeRegExp(regExp);
const req = createRequestMatchAllRegExpAsRange({ regexp: regExp, text });
const result = await this.makeRequest(req, timeLimitMs);
return {
elapsedTimeMs: result.elapsedTimeMs,
Expand All @@ -114,6 +134,14 @@ export class RegExpWorkerBase {
return this.scheduler.scheduleRequest(req, timeLimitMs).then(extractResult, timeoutRejection) as Promise<MatchAllRegExpResult>;
}

[Symbol.dispose](): void {
this._dispose().catch(() => {});
}

[Symbol.asyncDispose](): Promise<void> {
return this._dispose().catch(() => {});
}

/**
* Shuts down the background Worker and rejects any pending scheduled items.
*/
Expand Down Expand Up @@ -176,3 +204,90 @@ function mapToRanges(flatRange: Uint32Array): RangePair[] {
export function createRegExpWorker(createWorker: CreateWorker, timeoutMs?: number, stopIdleWorkerAfterMs?: number): RegExpWorkerBase {
return new RegExpWorkerBase(createWorker, timeoutMs, stopIdleWorkerAfterMs);
}

export async function createAndApplyToWorker<T>(
createRegExpWorker: CreateRegExpWorker,
fn: (worker: RegExpWorkerBase) => Promise<T>,
timeoutMs?: number,
stopIdleWorkerAfterMs?: number,
): Promise<T> {
await using worker = createRegExpWorker(timeoutMs, stopIdleWorkerAfterMs);
return await fn(worker);
}

export type CreateRegExpWorker<T extends RegExpWorkerBase = RegExpWorkerBase> = (timeoutMs?: number, stopIdleWorkerAfterMs?: number) => T;

/**
* Run text.matchAll against a RegExp in a worker.
* @param text - The text to search within.
* @param regExp - The regular expression to match against the text.
* @param timeLimitMs - Optional time limit in milliseconds for the operation.
*/
export function crWorkerMatchAll(
createRegExpWorker: CreateRegExpWorker,
text: string,
regExp: RegExp,
timeLimitMs?: number,
): Promise<MatchAllRegExpResult> {
return createAndApplyToWorker(createRegExpWorker, (worker) => worker.matchAll(text, regExp, timeLimitMs));
}

/**
* Run text.matchAll against a RegExp in a worker and return the matches as [start, end] range pairs.
* @param text - The text to search within.
* @param regExp - The regular expression to match against the text.
* @param timeLimitMs - Optional time limit in milliseconds for the operation.
*/
export function crWorkerMatchAllAsRangePairs(
createRegExpWorker: CreateRegExpWorker,
text: string,
regExp: RegExp,
timeLimitMs?: number,
): Promise<MatchAllAsRangePairsResult> {
return createAndApplyToWorker(createRegExpWorker, (worker) => worker.matchAllAsRangePairs(text, regExp, timeLimitMs));
}

/**
* Runs text.matchAll against an array of RegExps in a worker.
* @param text - The text to search within.
* @param regExps - An array of regular expressions to match against the text.
* @param timeLimitMs - Optional time limit in milliseconds for the operation.
*/
export function crWorkerMatchAllArray(
createRegExpWorker: CreateRegExpWorker,
text: string,
regExps: RegExp[],
timeLimitMs?: number,
): Promise<MatchAllRegExpArrayResult> {
return createAndApplyToWorker(createRegExpWorker, (worker) => worker.matchAllArray(text, regExps, timeLimitMs));
}

/**
* Run RegExp.exec in a worker.
* @param regExp - The regular expression to execute.
* @param text - The text to search within.
* @param timeLimitMs - Optional time limit in milliseconds for the operation.
*/
export function crWorkerExec(
createRegExpWorker: CreateRegExpWorker,
regExp: RegExp,
text: string,
timeLimitMs?: number,
): Promise<ExecRegExpResult> {
return createAndApplyToWorker(createRegExpWorker, (worker) => worker.exec(regExp, text, timeLimitMs));
}

/**
* Run text.match with a RegExp in a worker.
* @param text - The text to search within.
* @param regExp - The regular expression to match against the text.
* @param timeLimitMs - Optional time limit in milliseconds for the operation.
*/
export function crWorkerMatch(
createRegExpWorker: CreateRegExpWorker,
text: string,
regExp: RegExp,
timeLimitMs?: number,
): Promise<MatchRegExpResult> {
return createAndApplyToWorker(createRegExpWorker, (worker) => worker.match(text, regExp, timeLimitMs));
}
Loading