feat: add Python and Java regex engines

This commit is contained in:
2026-07-27 02:02:21 +02:00
parent 4951c966d4
commit 7079cde15f
95 changed files with 8866 additions and 251 deletions

View File

@@ -4,7 +4,7 @@ import type {
RegexReplacementRequest,
RegexReplacementResult,
} from "../model/match";
import { WorkerSupervisor } from "./WorkerSupervisor";
import { WorkerRequestError, WorkerSupervisor } from "./WorkerSupervisor";
import type {
EngineWorkerOperation,
EngineWorkerResult,
@@ -13,7 +13,7 @@ import {
ENGINE_WORKER_REGISTRY,
type EngineWorkerRegistry,
} from "./engine-registry";
import type { RegexFlavourId } from "../model/flavour";
import type { RegexEngineInfo, RegexFlavourId } from "../model/flavour";
export type EngineRuntimeState =
| { readonly status: "unloaded" }
@@ -28,6 +28,18 @@ export class EngineSupervisor {
RegexFlavourId,
WorkerSupervisor<EngineWorkerOperation, EngineWorkerResult>
>();
private readonly loaded = new Map<
RegexFlavourId,
{
readonly generation: number;
readonly info: RegexEngineInfo;
}
>();
private readonly loading = new Map<
RegexFlavourId,
Promise<RegexEngineInfo>
>();
private readonly operationRevisions = new Map<RegexFlavourId, number>();
private disposed = false;
constructor(registry: EngineWorkerRegistry = ENGINE_WORKER_REGISTRY) {
@@ -51,14 +63,76 @@ export class EngineSupervisor {
return supervisor;
}
async load(flavour: RegexFlavourId): Promise<RegexEngineInfo> {
const registration = this.registry.require(flavour);
const supervisor = this.supervisorFor(flavour);
const loaded = this.loaded.get(flavour);
if (
loaded &&
supervisor.hasWorker &&
loaded.generation === supervisor.currentGeneration
) {
return loaded.info;
}
const current = this.loading.get(flavour);
if (current && supervisor.hasWorker) return current;
const loading: Promise<RegexEngineInfo> = supervisor
.run({ kind: "load" }, registration.startupTimeoutMs)
.then((response) => {
if (response.kind !== "load") {
throw new Error(
"Engine worker returned the wrong load response kind",
);
}
if (response.info.flavour !== flavour) {
throw new Error(
`Engine worker for ${flavour} loaded a ${response.info.flavour} runtime.`,
);
}
this.loaded.set(flavour, {
generation: supervisor.currentGeneration,
info: response.info,
});
return response.info;
})
.finally(() => {
if (this.loading.get(flavour) === loading) {
this.loading.delete(flavour);
}
});
this.loading.set(flavour, loading);
return loading;
}
private async prepareOperation(flavour: RegexFlavourId): Promise<{
readonly revision: number;
readonly supervisor: WorkerSupervisor<
EngineWorkerOperation,
EngineWorkerResult
>;
}> {
const revision = (this.operationRevisions.get(flavour) ?? 0) + 1;
this.operationRevisions.set(flavour, revision);
const supervisor = this.supervisorFor(flavour);
if (supervisor.isRunning) supervisor.cancel();
await this.load(flavour);
if (this.operationRevisions.get(flavour) !== revision) {
throw new WorkerRequestError(
"cancelled",
`${this.registry.require(flavour).label} request was superseded`,
);
}
return { revision, supervisor };
}
async execute(
request: RegexExecutionRequest,
timeoutMs: number,
): Promise<RegexExecutionResult> {
const response = await this.supervisorFor(request.flavour).run(
const { supervisor } = await this.prepareOperation(request.flavour);
const response = await supervisor.run(
{ kind: "execute", request },
timeoutMs,
{ supersede: true },
);
if (response.kind !== "execute") {
throw new Error("Engine worker returned the wrong response kind");
@@ -75,10 +149,10 @@ export class EngineSupervisor {
request: RegexReplacementRequest,
timeoutMs: number,
): Promise<RegexReplacementResult> {
const response = await this.supervisorFor(request.flavour).run(
const { supervisor } = await this.prepareOperation(request.flavour);
const response = await supervisor.run(
{ kind: "replace", request },
timeoutMs,
{ supersede: true },
);
if (response.kind !== "replace") {
throw new Error("Engine worker returned the wrong response kind");
@@ -92,7 +166,13 @@ export class EngineSupervisor {
}
cancel(): void {
for (const supervisor of this.supervisors.values()) supervisor.cancel();
for (const [flavour, supervisor] of this.supervisors) {
this.operationRevisions.set(
flavour,
(this.operationRevisions.get(flavour) ?? 0) + 1,
);
supervisor.cancel();
}
}
dispose(): void {
@@ -100,5 +180,8 @@ export class EngineSupervisor {
this.disposed = true;
for (const supervisor of this.supervisors.values()) supervisor.dispose();
this.supervisors.clear();
this.loaded.clear();
this.loading.clear();
this.operationRevisions.clear();
}
}