feat: publish Regex Tools 0.2.0

This commit is contained in:
2026-07-27 01:06:57 +02:00
parent 873b9b218d
commit 4951c966d4
180 changed files with 35344 additions and 503 deletions

View File

@@ -0,0 +1,848 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { writeClipboardText } from "../browser/clipboard";
import {
AVAILABLE_REGEX_FLAVOURS,
defaultRegexOptions,
} from "../regex/flavours/flavour-registry";
import {
DEFAULT_REGEX_LIMITS,
utf8ByteLength,
} from "../regex/execution/request-limits";
import type {
RegexEngineOptions,
RegexFlavourId,
} from "../regex/model/flavour";
import type { RegexTestExpectation } from "../regex/tests/test-case.types";
import { SubjectMinimizer } from "../regex/minimization/SubjectMinimizer";
import type {
EngineFailureOracle,
SubjectMinimizationProgress,
SubjectMinimizationRequest,
SubjectMinimizationResult,
} from "../regex/minimization/minimization.types";
import "./MinimizePanel.css";
type MinimizationMode =
| "unit-test-failure"
| "capture-range-mismatch"
| "engine-timeout"
| "comparison-mismatch";
type UnitExpectationKind = RegexTestExpectation["kind"];
export interface SubjectMinimizerClient {
minimize(
request: SubjectMinimizationRequest,
onProgress?: (progress: SubjectMinimizationProgress) => void,
): Promise<SubjectMinimizationResult>;
cancel(): void;
dispose(): void;
}
export interface MinimizePanelProps {
readonly activeFlavour: RegexFlavourId;
readonly activeFlavourVersion: string;
readonly activeFlags: readonly string[];
readonly activeOptions: RegexEngineOptions;
readonly pattern: string;
readonly subject: string;
readonly replacement: string;
readonly scanAll: boolean;
readonly timeoutMs: number;
readonly onUseSubject: (subject: string) => void;
readonly onClose: () => void;
readonly createMinimizer?: () => SubjectMinimizerClient;
}
const ECMASCRIPT = AVAILABLE_REGEX_FLAVOURS.require("ecmascript");
const PCRE2 = AVAILABLE_REGEX_FLAVOURS.require("pcre2");
function flagsFor(
activeFlavour: RegexFlavourId,
activeFlags: readonly string[],
flavour: "ecmascript" | "pcre2",
): readonly string[] {
const definition = flavour === "ecmascript" ? ECMASCRIPT : PCRE2;
return activeFlavour === flavour
? definition.flags
.map((flag) => flag.value)
.filter((flag) => activeFlags.includes(flag))
: definition.defaultFlags;
}
function optionsFor(
activeFlavour: RegexFlavourId,
activeOptions: RegexEngineOptions,
flavour: "ecmascript" | "pcre2",
): RegexEngineOptions {
if (activeFlavour === flavour) return activeOptions;
return defaultRegexOptions(flavour === "ecmascript" ? ECMASCRIPT : PCRE2);
}
function parseCaptureSelector(value: string): {
readonly groupNumber?: number;
readonly groupName?: string;
} {
const trimmed = value.trim();
const number = Number(trimmed);
return trimmed !== "" && Number.isSafeInteger(number) && number > 0
? { groupNumber: number }
: { groupName: trimmed };
}
function resultLabel(result: SubjectMinimizationResult): string {
if (result.locallyMinimal) {
return "Locally minimal under the documented transforms";
}
return result.stopReason.replaceAll("-", " ");
}
function Observation({
label,
observation,
}: {
readonly label: string;
readonly observation: SubjectMinimizationResult["baseline"];
}) {
return (
<article className={`minimizer-observation is-${observation.status}`}>
<strong>{label}</strong>
<span>
{observation.status} ·{" "}
{observation.reproduced ? "predicate reproduced" : "not reproduced"}
</span>
<p>{observation.summary}</p>
{observation.fingerprint.length > 0 ? (
<code>{observation.fingerprint.join(" · ")}</code>
) : null}
{observation.sides?.length ? (
<small>
{observation.sides
.map(
(side) =>
`${side.flavour}: ${side.status}${
side.engineIdentity ? ` · ${side.engineIdentity}` : ""
}`,
)
.join(" | ")}
</small>
) : null}
</article>
);
}
export function MinimizePanel({
activeFlavour,
activeFlavourVersion,
activeFlags,
activeOptions,
pattern: initialPattern,
subject: initialSubject,
replacement: initialReplacement,
scanAll: initialScanAll,
timeoutMs: initialTimeoutMs,
onUseSubject,
onClose,
createMinimizer = () => new SubjectMinimizer(),
}: MinimizePanelProps) {
const [mode, setMode] = useState<MinimizationMode>("unit-test-failure");
const [subject, setSubject] = useState(initialSubject);
const [scanAll, setScanAll] = useState(initialScanAll);
const [unitKind, setUnitKind] =
useState<UnitExpectationKind>("should-not-match");
const [expectedCount, setExpectedCount] = useState(1);
const [matchIndex, setMatchIndex] = useState(0);
const [expectedValue, setExpectedValue] = useState("");
const [captureSelector, setCaptureSelector] = useState("1");
const [captureStatus, setCaptureStatus] = useState<
"participated" | "did-not-participate" | "matched-empty"
>("participated");
const [checkCaptureValue, setCheckCaptureValue] = useState(false);
const [expectedStart, setExpectedStart] = useState(0);
const [expectedEnd, setExpectedEnd] = useState(0);
const [durationLimitMs, setDurationLimitMs] = useState(10);
const [comparisonOperation, setComparisonOperation] = useState<
"match" | "replace"
>("match");
const [ecmaPattern, setEcmaPattern] = useState(initialPattern);
const [pcrePattern, setPcrePattern] = useState(initialPattern);
const [ecmaReplacement, setEcmaReplacement] = useState(initialReplacement);
const [pcreReplacement, setPcreReplacement] = useState(initialReplacement);
const [maximumEvaluations, setMaximumEvaluations] = useState(500);
const [maximumWallTimeMs, setMaximumWallTimeMs] = useState(15_000);
const [candidateTimeoutMs, setCandidateTimeoutMs] = useState(
Math.min(
DEFAULT_REGEX_LIMITS.advancedMaximumTimeoutMs,
Math.max(1, initialTimeoutMs),
),
);
const [running, setRunning] = useState(false);
const [status, setStatus] = useState(
"Select an exact failure predicate, then verify the baseline.",
);
const [progress, setProgress] = useState<SubjectMinimizationProgress>();
const [result, setResult] = useState<SubjectMinimizationResult>();
const [copyStatus, setCopyStatus] = useState("");
const minimizer = useRef<SubjectMinimizerClient | null>(null);
const revision = useRef(0);
useEffect(
() => () => {
revision.current += 1;
minimizer.current?.dispose();
minimizer.current = null;
},
[],
);
const ecmaFlags = useMemo(
() => flagsFor(activeFlavour, activeFlags, "ecmascript"),
[activeFlags, activeFlavour],
);
const pcreFlags = useMemo(
() => flagsFor(activeFlavour, activeFlags, "pcre2"),
[activeFlags, activeFlavour],
);
const ecmaOptions = useMemo(
() => optionsFor(activeFlavour, activeOptions, "ecmascript"),
[activeFlavour, activeOptions],
);
const pcreOptions = useMemo(
() => optionsFor(activeFlavour, activeOptions, "pcre2"),
[activeFlavour, activeOptions],
);
const unitExpectation = (): RegexTestExpectation => {
switch (unitKind) {
case "should-match":
case "should-not-match":
case "must-time-out":
return { kind: unitKind };
case "match-count":
return { kind: unitKind, count: expectedCount };
case "full-match":
return {
kind: unitKind,
matchIndex,
value: expectedValue,
};
case "capture":
return {
kind: unitKind,
matchIndex,
...parseCaptureSelector(captureSelector),
status: captureStatus,
...(checkCaptureValue ? { value: expectedValue } : {}),
};
case "replacement":
return { kind: unitKind, expected: expectedValue };
case "must-complete-within":
return { kind: unitKind, milliseconds: durationLimitMs };
}
};
const activeSide = {
flavour:
activeFlavour === "pcre2" ? ("pcre2" as const) : ("ecmascript" as const),
flavourVersion: activeFlavourVersion,
pattern: initialPattern,
flags: activeFlags,
options: activeOptions,
...(unitKind === "replacement" ? { replacement: initialReplacement } : {}),
};
const request = (): SubjectMinimizationRequest => {
let target: SubjectMinimizationRequest["target"];
if (mode === "comparison-mismatch") {
target = {
kind: "comparison-mismatch",
operation: comparisonOperation,
scanAll,
sides: [
{
flavour: "ecmascript",
flavourVersion: ECMASCRIPT.defaultVersion,
pattern: ecmaPattern,
flags: ecmaFlags,
options: ecmaOptions,
...(comparisonOperation === "replace"
? { replacement: ecmaReplacement }
: {}),
},
{
flavour: "pcre2",
flavourVersion: PCRE2.defaultVersion,
pattern: pcrePattern,
flags: pcreFlags,
options: pcreOptions,
...(comparisonOperation === "replace"
? { replacement: pcreReplacement }
: {}),
},
],
};
} else {
let oracle: EngineFailureOracle;
if (mode === "engine-timeout") {
oracle = { kind: "engine-timeout" };
} else if (mode === "capture-range-mismatch") {
oracle = {
kind: "capture-range-mismatch",
matchIndex,
...parseCaptureSelector(captureSelector),
expectedRange: {
startUtf16: expectedStart,
endUtf16: expectedEnd,
},
};
} else {
oracle = {
kind: "unit-test-failure",
expectation: unitExpectation(),
};
}
target = {
kind: "engine",
side: activeSide,
scanAll,
oracle,
};
}
return {
schemaVersion: 1,
subject,
target,
budgets: {
maximumEvaluations,
maximumWallTimeMs,
candidateTimeoutMs,
},
maximumMatches: 1_000,
maximumCaptureRows: 10_000,
maximumOutputBytes: 1024 * 1024,
};
};
const run = async () => {
const currentRevision = ++revision.current;
setRunning(true);
setResult(undefined);
setCopyStatus("");
setStatus("Verifying the baseline with the selected real engine worker…");
try {
minimizer.current ??= createMinimizer();
const minimized = await minimizer.current.minimize(request(), (value) => {
if (currentRevision !== revision.current) return;
setProgress(value);
setStatus(
`${value.phase.replaceAll("-", " ")} · ${value.evaluations}/${value.maximumEvaluations} evaluations · ${value.acceptedReductions} accepted`,
);
});
if (currentRevision !== revision.current) return;
setResult(minimized);
setStatus(resultLabel(minimized));
} catch (error) {
if (currentRevision !== revision.current) return;
setStatus(error instanceof Error ? error.message : String(error));
} finally {
if (currentRevision === revision.current) setRunning(false);
}
};
const cancel = () => {
revision.current += 1;
minimizer.current?.cancel();
setRunning(false);
setStatus("Cancelled; active syntax and engine workers were terminated.");
};
return (
<section
className="panel minimize-panel"
aria-labelledby="minimize-heading"
>
<header className="panel-heading">
<div>
<p className="eyebrow">Bounded failing-subject reduction</p>
<h2 id="minimize-heading">Minimize a reproducer</h2>
</div>
<button
type="button"
className="icon-button"
aria-label="Close subject minimizer"
data-dialog-initial-focus
onClick={onClose}
>
×
</button>
</header>
<div className="minimizer-body">
<p className="minimizer-advisory">
This reducer proves only local minimality under deterministic
Unicode-scalar chunk deletion, single-scalar deletion and canonical
replacement. It does not claim a globally or semantically minimal
example.
</p>
<section className="minimizer-config" aria-labelledby="minimize-target">
<header>
<h3 id="minimize-target">Exact predicate</h3>
<span>
{activeFlavour} · {utf8ByteLength(subject).toLocaleString()} bytes
</span>
</header>
<div className="minimizer-control-grid">
<label>
<span>Failure kind</span>
<select
aria-label="Minimization failure kind"
value={mode}
disabled={running}
onChange={(event) =>
setMode(event.target.value as MinimizationMode)
}
>
<option value="unit-test-failure">
Failing unit-test assertion
</option>
<option value="capture-range-mismatch">
Wrong capture range
</option>
<option value="engine-timeout">Exact engine timeout</option>
<option value="comparison-mismatch">
ECMAScript PCRE2 mismatch
</option>
</select>
</label>
<label className="check-control minimizer-check">
<input
type="checkbox"
checked={scanAll}
disabled={running}
onChange={(event) => setScanAll(event.target.checked)}
/>
Scan all
</label>
</div>
{mode === "unit-test-failure" ? (
<div className="minimizer-control-grid">
<label>
<span>Saved assertion</span>
<select
aria-label="Unit-test expectation"
value={unitKind}
disabled={running}
onChange={(event) =>
setUnitKind(event.target.value as UnitExpectationKind)
}
>
<option value="should-match">Should match</option>
<option value="should-not-match">Should not match</option>
<option value="match-count">Exact match count</option>
<option value="full-match">Full-match value</option>
<option value="capture">Capture status/value</option>
<option value="replacement">Replacement output</option>
<option value="must-complete-within">
Must complete within
</option>
<option value="must-time-out">Must time out</option>
</select>
</label>
{unitKind === "match-count" ? (
<label>
<span>Expected count</span>
<input
type="number"
value={expectedCount}
min={0}
max={DEFAULT_REGEX_LIMITS.maximumMatches}
onChange={(event) =>
setExpectedCount(Number(event.target.value))
}
/>
</label>
) : null}
{unitKind === "full-match" || unitKind === "capture" ? (
<label>
<span>Zero-based match index</span>
<input
type="number"
value={matchIndex}
min={0}
onChange={(event) =>
setMatchIndex(Number(event.target.value))
}
/>
</label>
) : null}
{unitKind === "capture" ? (
<>
<label>
<span>Capture number or name</span>
<input
type="text"
value={captureSelector}
onChange={(event) =>
setCaptureSelector(event.target.value)
}
/>
</label>
<label>
<span>Expected participation</span>
<select
value={captureStatus}
onChange={(event) =>
setCaptureStatus(
event.target.value as typeof captureStatus,
)
}
>
<option value="participated">Participated</option>
<option value="did-not-participate">
Did not participate
</option>
<option value="matched-empty">Matched empty</option>
</select>
</label>
<label className="check-control minimizer-check">
<input
type="checkbox"
checked={checkCaptureValue}
onChange={(event) =>
setCheckCaptureValue(event.target.checked)
}
/>
Assert value too
</label>
</>
) : null}
{unitKind === "full-match" ||
unitKind === "replacement" ||
(unitKind === "capture" && checkCaptureValue) ? (
<label className="minimizer-wide-control">
<span>Expected value</span>
<input
type="text"
value={expectedValue}
onChange={(event) => setExpectedValue(event.target.value)}
/>
</label>
) : null}
{unitKind === "must-complete-within" ? (
<label>
<span>Assertion milliseconds</span>
<input
type="number"
value={durationLimitMs}
min={1}
onChange={(event) =>
setDurationLimitMs(Number(event.target.value))
}
/>
</label>
) : null}
</div>
) : null}
{mode === "capture-range-mismatch" ? (
<div className="minimizer-control-grid">
<label>
<span>Zero-based match index</span>
<input
type="number"
value={matchIndex}
min={0}
onChange={(event) =>
setMatchIndex(Number(event.target.value))
}
/>
</label>
<label>
<span>Capture number or name</span>
<input
type="text"
value={captureSelector}
onChange={(event) => setCaptureSelector(event.target.value)}
/>
</label>
<label>
<span>Expected UTF-16 start</span>
<input
type="number"
value={expectedStart}
min={0}
max={subject.length}
onChange={(event) =>
setExpectedStart(Number(event.target.value))
}
/>
</label>
<label>
<span>Expected UTF-16 end</span>
<input
type="number"
value={expectedEnd}
min={0}
max={subject.length}
onChange={(event) =>
setExpectedEnd(Number(event.target.value))
}
/>
</label>
</div>
) : null}
{mode === "comparison-mismatch" ? (
<>
<div className="minimizer-control-grid">
<label>
<span>Operation</span>
<select
value={comparisonOperation}
onChange={(event) =>
setComparisonOperation(
event.target.value as "match" | "replace",
)
}
>
<option value="match">Match</option>
<option value="replace">Replace</option>
</select>
</label>
</div>
<div className="minimizer-variant-grid">
<label>
<span>ECMAScript pattern</span>
<textarea
value={ecmaPattern}
onChange={(event) => setEcmaPattern(event.target.value)}
/>
</label>
<label>
<span>PCRE2 pattern</span>
<textarea
value={pcrePattern}
onChange={(event) => setPcrePattern(event.target.value)}
/>
</label>
{comparisonOperation === "replace" ? (
<>
<label>
<span>ECMAScript replacement</span>
<textarea
value={ecmaReplacement}
onChange={(event) =>
setEcmaReplacement(event.target.value)
}
/>
</label>
<label>
<span>PCRE2 replacement</span>
<textarea
value={pcreReplacement}
onChange={(event) =>
setPcreReplacement(event.target.value)
}
/>
</label>
</>
) : null}
</div>
</>
) : null}
<label className="minimizer-subject">
<span>Failing subject · local text only</span>
<textarea
aria-label="Subject to minimize"
value={subject}
disabled={running}
onChange={(event) => setSubject(event.target.value)}
/>
</label>
</section>
<section className="minimizer-config" aria-labelledby="minimize-bounds">
<header>
<h3 id="minimize-bounds">Hard bounds</h3>
<span>Fixed deadline per candidate</span>
</header>
<div className="minimizer-control-grid">
<label>
<span>Candidate evaluations</span>
<input
type="number"
value={maximumEvaluations}
min={1}
max={DEFAULT_REGEX_LIMITS.maximumMinimizerRuns}
disabled={running}
onChange={(event) =>
setMaximumEvaluations(Number(event.target.value))
}
/>
</label>
<label>
<span>Aggregate wall time ms</span>
<input
type="number"
value={maximumWallTimeMs}
min={1}
max={60_000}
disabled={running}
onChange={(event) =>
setMaximumWallTimeMs(Number(event.target.value))
}
/>
</label>
<label>
<span>Each candidate ms</span>
<input
type="number"
value={candidateTimeoutMs}
min={1}
max={DEFAULT_REGEX_LIMITS.advancedMaximumTimeoutMs}
disabled={running}
onChange={(event) =>
setCandidateTimeoutMs(Number(event.target.value))
}
/>
</label>
</div>
</section>
<div className="minimizer-actions">
<button
type="button"
className="primary-button"
disabled={running}
onClick={() => void run()}
>
{running ? "Minimizing…" : "Verify & minimize"}
</button>
{running ? (
<button type="button" className="secondary-button" onClick={cancel}>
Cancel
</button>
) : null}
<p className="minimizer-status" aria-live="polite">
{status}
</p>
</div>
{progress ? (
<div className="minimizer-progress">
<progress
aria-label="Minimizer evaluation progress"
value={progress.evaluations}
max={progress.maximumEvaluations}
/>
<span>
{progress.currentScalars.toLocaleString()} scalars ·{" "}
{progress.currentUtf8Bytes.toLocaleString()} bytes ·{" "}
{progress.elapsedMs.toFixed(0)}/
{progress.maximumWallTimeMs.toLocaleString()} ms
</span>
</div>
) : null}
{result ? (
<section
className="minimizer-result"
aria-labelledby="minimize-result"
>
<header>
<div>
<p className="eyebrow">Exact target {result.targetIdentity}</p>
<h3 id="minimize-result">{resultLabel(result)}</h3>
</div>
<span className={`minimizer-result-badge is-${result.status}`}>
{result.status}
</span>
</header>
<dl>
<div>
<dt>Scalars</dt>
<dd>
{result.originalScalars.toLocaleString()} {" "}
{result.minimizedScalars.toLocaleString()}
</dd>
</div>
<div>
<dt>UTF-8 bytes</dt>
<dd>
{result.originalUtf8Bytes.toLocaleString()} {" "}
{result.minimizedUtf8Bytes.toLocaleString()}
</dd>
</div>
<div>
<dt>Evaluations</dt>
<dd>
{result.evaluations.toLocaleString()} /{" "}
{result.budgets.maximumEvaluations.toLocaleString()}
</dd>
</div>
<div>
<dt>Accepted reductions</dt>
<dd>{result.acceptedReductions.toLocaleString()}</dd>
</div>
<div>
<dt>Wall time</dt>
<dd>{result.elapsedMs.toFixed(1)} ms</dd>
</div>
<div>
<dt>Stop reason</dt>
<dd>{result.stopReason.replaceAll("-", " ")}</dd>
</div>
</dl>
<div className="minimizer-observation-grid">
<Observation
label="Baseline verification"
observation={result.baseline}
/>
<Observation
label="Final accepted candidate"
observation={result.final}
/>
</div>
<label className="minimizer-output">
<span>Minimized subject</span>
<textarea
aria-label="Minimized subject"
readOnly
value={result.minimizedSubject}
/>
</label>
<div className="minimizer-actions">
<button
type="button"
className="primary-button"
disabled={!result.final.reproduced}
onClick={() => onUseSubject(result.minimizedSubject)}
>
Use minimized subject
</button>
<button
type="button"
className="secondary-button"
onClick={() => {
void writeClipboardText(result.minimizedSubject)
.then(() => setCopyStatus("Copied locally."))
.catch((error: unknown) =>
setCopyStatus(
error instanceof Error ? error.message : String(error),
),
);
}}
>
Copy
</button>
<p aria-live="polite">{copyStatus}</p>
</div>
</section>
) : null}
</div>
</section>
);
}