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,407 @@
import { useEffect, useRef, useState } from "react";
import { Pcre2TraceSupervisor } from "../regex/execution/Pcre2TraceSupervisor";
import { WorkerRequestError } from "../regex/execution/WorkerSupervisor";
import { DEFAULT_REGEX_LIMITS } from "../regex/execution/request-limits";
import type {
RegexEngineOptions,
RegexFlavourId,
} from "../regex/model/flavour";
import type { Pcre2TraceEvent, Pcre2TraceResult } from "../regex/model/trace";
import type { SourceRange } from "../regex/model/syntax";
const DEFAULT_TRACE_EVENTS = 5_000;
const DEFAULT_TRACE_BYTES = 2 * 1024 * 1024;
const MAXIMUM_RENDERED_TRACE_EVENTS = 2_000;
type TraceState =
| { readonly status: "idle"; readonly message: string }
| { readonly status: "running"; readonly message: string }
| { readonly status: "ready"; readonly message: string }
| { readonly status: "timeout"; readonly message: string }
| { readonly status: "error"; readonly message: string };
function movementLabel(event: Pcre2TraceEvent): string {
switch (event.movement.classification) {
case "first-event":
return "first";
case "same-position":
return "same position";
case "apparent-backtrack":
return "apparent backtrack";
case "forward":
return "forward";
}
}
export function Pcre2TracePanel({
active,
flavour,
pattern,
flags,
options,
subject,
timeoutMs,
onClose,
onSelectRanges,
}: {
readonly active: boolean;
readonly flavour: RegexFlavourId;
readonly pattern: string;
readonly flags: readonly string[];
readonly options: RegexEngineOptions;
readonly subject: string;
readonly timeoutMs: number;
readonly onClose: () => void;
readonly onSelectRanges: (
patternRange: SourceRange,
subjectRange: SourceRange,
) => void;
}) {
const supervisor = useRef<Pcre2TraceSupervisor | null>(null);
const requestRevision = useRef(0);
const [maximumEvents, setMaximumEvents] = useState(DEFAULT_TRACE_EVENTS);
const [maximumBytes, setMaximumBytes] = useState(DEFAULT_TRACE_BYTES);
const [completed, setCompleted] = useState<{
readonly result: Pcre2TraceResult;
readonly input: {
readonly pattern: string;
readonly subject: string;
readonly flags: string;
readonly options: string;
};
}>();
const [state, setState] = useState<TraceState>({
status: "idle",
message: "Run an isolated PCRE2 automatic-callout trace.",
});
const traceEngine = () => {
supervisor.current ??= new Pcre2TraceSupervisor();
return supervisor.current;
};
useEffect(() => {
return () => supervisor.current?.dispose();
}, []);
useEffect(() => {
if (active) return;
supervisor.current?.cancel();
}, [active]);
const limitsValid =
Number.isSafeInteger(maximumEvents) &&
maximumEvents >= 1 &&
maximumEvents <= DEFAULT_REGEX_LIMITS.maximumTraceEvents &&
Number.isSafeInteger(maximumBytes) &&
maximumBytes >= 32 &&
maximumBytes <= DEFAULT_REGEX_LIMITS.maximumTraceBytes;
const runTrace = async () => {
if (flavour !== "pcre2" || !limitsValid) return;
const revision = (requestRevision.current += 1);
setCompleted(undefined);
setState({
status: "running",
message: "Collecting reported callouts in a dedicated worker…",
});
try {
const next = await traceEngine().trace(
{
flavour,
pattern,
flags,
options,
subject,
maximumTraceEvents: maximumEvents,
maximumTraceBytes: maximumBytes,
},
timeoutMs,
);
if (revision !== requestRevision.current) return;
setCompleted({
result: next,
input: {
pattern,
subject,
flags: flags.join(""),
options: JSON.stringify(options),
},
});
const error = next.diagnostics.find(
(diagnostic) => diagnostic.severity === "error",
);
setState(
next.accepted
? {
status: "ready",
message: next.truncated
? "Bounded trace complete; collection stopped at a configured cap."
: "Bounded trace complete.",
}
: {
status: "error",
message: error?.message ?? "PCRE2 rejected the trace request.",
},
);
} catch (error) {
if (revision !== requestRevision.current) return;
if (error instanceof WorkerRequestError && error.kind === "timeout") {
setState({
status: "timeout",
message: `${error.message}; the trace worker was terminated.`,
});
} else if (
error instanceof WorkerRequestError &&
error.kind === "cancelled"
) {
setState({
status: "idle",
message: "Trace cancelled; its worker was terminated.",
});
} else {
setState({
status: "error",
message:
error instanceof Error
? error.message
: "The PCRE2 trace worker failed.",
});
}
}
};
const resultIsCurrent =
completed?.input.pattern === pattern &&
completed.input.subject === subject &&
completed.input.flags === flags.join("") &&
completed.input.options === JSON.stringify(options);
const visibleResult = resultIsCurrent ? completed.result : undefined;
const visibleState =
resultIsCurrent || state.status === "running"
? state
: {
status: "idle" as const,
message: "Inputs changed; run a new isolated trace.",
};
const renderedEvents =
visibleResult?.events.slice(0, MAXIMUM_RENDERED_TRACE_EVENTS) ?? [];
return (
<section className="panel trace-panel" aria-labelledby="trace-heading">
<header className="panel-heading">
<div>
<p className="eyebrow">PCRE2 10.47 · reported automatic callouts</p>
<h2 id="trace-heading">Execution trace</h2>
</div>
<button
type="button"
className="icon-button"
aria-label="Close execution trace"
data-dialog-initial-focus
onClick={onClose}
>
×
</button>
</header>
<p className="trace-disclosure">
This is an actual stream reported by PCRE2_AUTO_CALLOUT for one{" "}
<code>pcre2_match()</code> invocation. Movement labels are derived only
from adjacent reported subject positions; the stream is not a complete
record of every internal engine action.
</p>
<div className="trace-controls">
<label>
<span>Maximum events</span>
<input
type="number"
min={1}
max={DEFAULT_REGEX_LIMITS.maximumTraceEvents}
step={1}
value={maximumEvents}
onChange={(event) => setMaximumEvents(Number(event.target.value))}
/>
</label>
<label>
<span>Maximum serialized bytes</span>
<input
type="number"
min={32}
max={DEFAULT_REGEX_LIMITS.maximumTraceBytes}
step={1024}
value={maximumBytes}
onChange={(event) => setMaximumBytes(Number(event.target.value))}
/>
</label>
<button
type="button"
className="primary-button"
disabled={
flavour !== "pcre2" ||
!limitsValid ||
visibleState.status === "running"
}
onClick={() => void runTrace()}
>
{visibleState.status === "running" ? "Tracing…" : "Run trace"}
</button>
{visibleState.status === "running" ? (
<button
type="button"
className="secondary-button"
onClick={() => {
requestRevision.current += 1;
supervisor.current?.cancel();
setState({
status: "idle",
message: "Trace cancelled; its worker was terminated.",
});
}}
>
Cancel trace
</button>
) : null}
</div>
{flavour !== "pcre2" ? (
<p className="empty-state" role="status">
Select PCRE2 to enable its automatic-callout trace.
</p>
) : null}
<div
className={`trace-status status-${visibleState.status}`}
role="status"
aria-live="polite"
>
<strong>{visibleState.message}</strong>
{visibleResult?.accepted ? (
<small>
{visibleResult.events.length.toLocaleString()} complete retained
events of {visibleResult.totalEventCount.toLocaleString()} callback
event(s) observed before return ·{" "}
{visibleResult.traceBytes.toLocaleString()} serialized bytes · last
complete event{" "}
{visibleResult.lastCompleteEvent?.toLocaleString() ?? "none"} ·{" "}
native status {visibleResult.nativeMatchStatus} ·{" "}
{visibleResult.matched ? "matched" : "not matched or bounded stop"}{" "}
· {visibleResult.elapsedMs.toFixed(2)} ms
</small>
) : null}
</div>
{visibleResult?.diagnostics.length ? (
<ul className="trace-diagnostics">
{visibleResult.diagnostics.map((diagnostic) => (
<li
key={diagnostic.id}
className={`diagnostic-${diagnostic.severity}`}
>
<strong>{diagnostic.severity}</strong> {diagnostic.message}
</li>
))}
</ul>
) : null}
{visibleResult && visibleResult.events.length > renderedEvents.length ? (
<p className="render-limit-banner" role="status">
<strong>Trace viewer limited.</strong> Showing the first{" "}
{renderedEvents.length.toLocaleString()} of{" "}
{visibleResult.events.length.toLocaleString()} complete retained
events. The worker result remains bounded by both configured caps.
</p>
) : null}
{renderedEvents.length > 0 ? (
<div className="trace-table-scroll">
<table className="trace-table">
<thead>
<tr>
<th scope="col">Event</th>
<th scope="col">Callout</th>
<th scope="col">Pattern</th>
<th scope="col">Next item</th>
<th scope="col">Subject</th>
<th scope="col">Captures</th>
<th scope="col">Mark</th>
<th scope="col">Movement</th>
</tr>
</thead>
<tbody>
{renderedEvents.map((event) => (
<tr key={event.eventNumber}>
<td>
<button
type="button"
className="trace-event-select"
aria-label={`Select trace event ${event.eventNumber}`}
onClick={() =>
onSelectRanges(
{
startUtf16:
event.reported.nextPatternItem.startUtf16,
endUtf16: event.reported.nextPatternItem.endUtf16,
},
{
startUtf16: event.reported.subjectPosition.utf16,
endUtf16: event.reported.subjectPosition.utf16,
},
)
}
>
{event.eventNumber}
</button>
</td>
<td>{event.reported.calloutNumber}</td>
<td>
{event.reported.patternPosition.utf16} UTF-16
<small>
{event.reported.patternPosition.nativeByte} byte
</small>
</td>
<td>
<code>
{pattern.slice(
event.reported.nextPatternItem.startUtf16,
event.reported.nextPatternItem.endUtf16,
) || "end"}
</code>
<small>
{event.reported.nextPatternItem.startNativeByte}
{event.reported.nextPatternItem.endNativeByte} bytes
</small>
</td>
<td>
{event.reported.subjectPosition.utf16} UTF-16
<small>
{event.reported.subjectPosition.nativeByte} byte
</small>
</td>
<td>
top {event.reported.captureTop} · last{" "}
{event.reported.captureLast}
</td>
<td>
{event.reported.mark ? (
<code>{event.reported.mark}</code>
) : (
"—"
)}
</td>
<td>
<span
className={`trace-movement trace-${event.movement.classification}`}
title={event.movement.explanation}
>
{movementLabel(event)}
</span>
<small>
{event.movement.subjectDeltaBytes > 0 ? "+" : ""}
{event.movement.subjectDeltaBytes} bytes · derived
</small>
</td>
</tr>
))}
</tbody>
</table>
</div>
) : visibleResult?.accepted ? (
<p className="empty-state">No callout event was retained.</p>
) : null}
</section>
);
}