PureYAMLStreaming/WasmSite/main.js

748 lines
25 KiB
JavaScript

import {
ConsoleStdout,
File,
OpenFile,
WASI,
} from "./vendor/browser_wasi_shim/index.js";
const runButton = document.querySelector("#runButton");
const streamButton = document.querySelector("#streamButton");
const sampleSelect = document.querySelector("#sampleSelect");
const urlInput = document.querySelector("#urlInput");
const statusElement = document.querySelector("#status");
const outputElement = document.querySelector("#output");
const runLogElement = document.querySelector("#runLog");
const copyRunLogButton = document.querySelector("#copyRunLogButton");
const streamDocumentElement = document.querySelector("#streamDocument");
const isolationMetric = document.querySelector("#isolationMetric");
const sabMetric = document.querySelector("#sabMetric");
const workerMetric = document.querySelector("#workerMetric");
const modeMetric = document.querySelector("#modeMetric");
const bytesMetric = document.querySelector("#bytesMetric");
const fetchMetric = document.querySelector("#fetchMetric");
const parseMetric = document.querySelector("#parseMetric");
const throughputMetric = document.querySelector("#throughputMetric");
const documentsMetric = document.querySelector("#documentsMetric");
const params = new URLSearchParams(location.search);
const wasmURL = params.get("wasm") || "./pureyaml-streaming-wasm-smoke.wasm.gz";
let modulePromise = null;
let runStartedAt = performance.now();
const streamControlIndex = {
read: 0,
write: 1,
available: 2,
closed: 3,
error: 4,
sequence: 5,
totalWritten: 6,
readCalls: 7,
waitCount: 8,
};
const streamControlCount = 16;
const streamBufferCapacity = 1024 * 1024;
const documentRenderBudget = 64 * 1024;
let documentRenderQueue = [];
let documentRenderScheduled = false;
let documentRenderResolvers = [];
const samples = [
{
name: "GitHub REST API OpenAPI (8.39 MiB)",
url: "https://api.apis.guru/v2/specs/github.com/1.1.4/openapi.yaml",
},
{
name: "Loket.nl API OpenAPI (10.62 MiB)",
url: "https://api.apis.guru/v2/specs/loket.nl/V2/openapi.yaml",
},
{
name: "Zuora Billing API OpenAPI (6.46 MiB)",
url: "https://api.apis.guru/v2/specs/zuora.com/2021-08-20/openapi.yaml",
},
{
name: "Amazon EC2 OpenAPI (5.36 MiB)",
url: "https://api.apis.guru/v2/specs/amazonaws.com/ec2/2016-11-15/openapi.yaml",
},
{
name: "Zoom API OpenAPI (4.80 MiB)",
url: "https://api.apis.guru/v2/specs/zoom.us/2.0.0/openapi.yaml",
},
{
name: "UniCourt Enterprise APIs OpenAPI (4.29 MiB)",
url: "https://api.apis.guru/v2/specs/unicourt.com/1.0.0/openapi.yaml",
},
{
name: "Kubernetes Swagger (4.26 MiB)",
url: "https://api.apis.guru/v2/specs/kubernetes.io/unversioned/swagger.yaml",
},
{
name: "Autotask PSA Swagger (3.90 MiB)",
url: "https://api.apis.guru/v2/specs/autotask.net/v1/swagger.yaml",
},
{
name: "Google Document AI Warehouse OpenAPI (3.61 MiB)",
url: "https://api.apis.guru/v2/specs/googleapis.com/contentwarehouse/v1/openapi.yaml",
},
{
name: "Stripe API OpenAPI (3.48 MiB)",
url: "https://api.apis.guru/v2/specs/stripe.com/2022-11-15/openapi.yaml",
},
{
name: "Google Compute Engine OpenAPI (3.32 MiB)",
url: "https://api.apis.guru/v2/specs/googleapis.com/compute/v1/openapi.yaml",
},
{
name: "DocuSign REST API OpenAPI (3.14 MiB)",
url: "https://api.apis.guru/v2/specs/docusign.net/v2.1/openapi.yaml",
},
];
for (const sample of samples) {
const option = document.createElement("option");
option.value = sample.url;
option.textContent = sample.name;
sampleSelect.append(option);
}
urlInput.value = samples[0].url;
runButton.addEventListener("click", () => {
runBenchmark().catch((error) => {
renderFailure(error);
});
});
streamButton.addEventListener("click", () => {
runStreamingBenchmark().catch((error) => {
renderFailure(error);
});
});
copyRunLogButton.addEventListener("click", () => {
copyRunLog().catch((error) => {
logStep(`Unable to copy run log: ${String(error?.message || error)}`, "error");
});
});
sampleSelect.addEventListener("change", () => {
urlInput.value = sampleSelect.value;
});
updateStreamingCapabilities();
async function runBenchmark() {
const sourceURL = urlInput.value.trim();
if (!sourceURL) {
throw new Error("Choose or enter a YAML URL first.");
}
runButton.disabled = true;
streamButton.disabled = true;
statusElement.className = "status idle";
statusElement.textContent = "Preparing...";
outputElement.textContent = "";
resetMetrics();
resetLog();
resetStreamDocument("Waiting for buffered YAML fetch to complete.");
logStep("Queued benchmark run.");
logStep(`Selected source: ${sourceURL}`);
logStep(`Loading WASM module: ${wasmURL}`);
const moduleLoadStart = performance.now();
const module = await loadWasmModule(wasmURL);
const moduleLoadMS = performance.now() - moduleLoadStart;
logStep(`WASM module ready in ${formatDuration(moduleLoadMS)}.`);
statusElement.textContent = "Fetching YAML...";
logStep("Fetching YAML bytes in the browser. This fetch is buffered before WASI starts.");
const fetchStart = performance.now();
const response = await fetch(sourceURL, { cache: "no-store" });
if (!response.ok) {
throw new Error(`Failed to fetch YAML: HTTP ${response.status}`);
}
const yamlBytes = new Uint8Array(await response.arrayBuffer());
const fetchMS = performance.now() - fetchStart;
replaceStreamDocument(decodeYAMLBytes(yamlBytes));
await waitForDocumentFlush();
await nextPaint();
logStep(`Fetched HTTP ${response.status}, ${formatBytes(yamlBytes.byteLength)} in ${formatDuration(fetchMS)}.`);
logStep("Handing buffered response to SwiftWASIHTTPClient.HostHTTPClient through WASI stdin.");
logStep("Preparing WASI file descriptors.");
let stdoutText = "";
let stderrText = "";
const fds = [
new OpenFile(new File(yamlBytes)),
ConsoleStdout.lineBuffered((line) => {
stdoutText += `${line}\n`;
}),
ConsoleStdout.lineBuffered((line) => {
stderrText += `${line}\n`;
}),
];
logStep("Creating WASI instance.");
const wasi = new WASI(["pureyaml-streaming-wasm-smoke", sourceURL, String(response.status)], [], fds);
statusElement.textContent = "Running Swift parser...";
logStep("Starting Swift parser. The browser tab can pause while WebAssembly is executing.");
await nextPaint();
const parseStart = performance.now();
const instance = await WebAssembly.instantiate(module, {
wasi_snapshot_preview1: wasi.wasiImport,
});
logStep("WebAssembly instance created; entering WASI _start.");
await nextPaint();
wasi.start(instance);
const wallClockParseMS = performance.now() - parseStart;
logStep(`WASI process returned in ${formatDuration(wallClockParseMS)}.`);
logStep("Parsing Swift JSON result from stdout.");
const parsed = parseSmokeOutput(stdoutText);
const ok = parsed?.ok === true;
if (!Number.isFinite(parsed?.parseMilliseconds)) {
throw new Error("WASM output did not include a finite Swift parseMilliseconds value.");
}
const parseMS = parsed.parseMilliseconds;
const throughput = parseMS > 0 ? yamlBytes.byteLength / (parseMS / 1000) : 0;
if (!Number.isFinite(parsed?.chunkCount) || !Number.isFinite(parsed?.chunkSize)) {
throw new Error("WASM output did not include finite Swift chunk instrumentation.");
}
logStep(`Swift transport: ${parsed.transport || "unknown"}; HTTP status: ${parsed.httpStatusCode ?? "n/a"}.`);
logStep(`Swift parse reported ${formatDuration(parseMS)}.`);
logStep(
`Swift ChunkedUTF8Reader consumed ${parsed.chunkCount} chunk(s) at chunkSize ${formatBytes(parsed.chunkSize)}.`,
);
logStep(`Largest Swift chunk observed: ${formatBytes(parsed.maxChunkByteCount ?? 0)}.`);
logStep(`Document scanner emitted ${parsed.scannerDocumentSourceCount ?? 0} document source(s).`);
logStep(`Parsed ${parsed.documentCount ?? 0} document(s).`);
logStep(`Throughput: ${formatBytes(throughput)}/s.`);
statusElement.className = ok ? "status pass" : "status fail";
statusElement.textContent = ok ? "Passed" : "Failed";
bytesMetric.textContent = formatBytes(yamlBytes.byteLength);
fetchMetric.textContent = formatDuration(fetchMS);
parseMetric.textContent = formatDuration(parseMS);
throughputMetric.textContent = `${formatBytes(throughput)}/s`;
documentsMetric.textContent = parsed?.documentCount ?? "-";
outputElement.textContent = [
`url: ${sourceURL}`,
`moduleLoad: ${formatDuration(moduleLoadMS)}`,
`fetch: ${formatDuration(fetchMS)}`,
`swiftParse: ${formatDuration(parseMS)}`,
`processWallClock: ${formatDuration(wallClockParseMS)}`,
`throughput: ${formatBytes(throughput)}/s`,
"",
"stdout:",
stdoutText || "(empty)",
"",
"stderr:",
stderrText || "(empty)",
].join("\n");
logStep(ok ? "Benchmark completed successfully." : "Benchmark finished with parser failure.");
resetButtons();
}
async function runStreamingBenchmark() {
const sourceURL = urlInput.value.trim();
if (!sourceURL) {
throw new Error("Choose or enter a YAML URL first.");
}
if (!streamingSupported()) {
throw new Error("True streaming mode requires cross-origin isolation, SharedArrayBuffer, Worker, and Atomics.wait.");
}
runButton.disabled = true;
streamButton.disabled = true;
statusElement.className = "status idle";
statusElement.textContent = "Streaming...";
outputElement.textContent = "";
resetMetrics();
resetLog();
resetStreamDocument("Waiting for streaming YAML chunks.");
logStep("Queued true browser-to-WASI streaming run.");
logStep(`Selected source: ${sourceURL}`);
const sharedBuffer = new SharedArrayBuffer(
streamControlCount * Int32Array.BYTES_PER_ELEMENT + streamBufferCapacity,
);
const control = new Int32Array(sharedBuffer, 0, streamControlCount);
const bytes = new Uint8Array(sharedBuffer, streamControlCount * Int32Array.BYTES_PER_ELEMENT, streamBufferCapacity);
let worker = null;
let stdoutText = "";
let stderrText = "";
let workerDone;
const workerDonePromise = new Promise((resolve, reject) => {
workerDone = { resolve, reject };
});
logStep("Starting Worker-owned WASI runtime.");
const fetchStart = performance.now();
const response = await fetch(sourceURL, { cache: "no-store" });
if (!response.ok) {
throw new Error(`Failed to fetch YAML: HTTP ${response.status}`);
}
if (!response.body) {
throw new Error("This browser did not expose a ReadableStream response body.");
}
worker = new Worker("./stream-worker.js", { type: "module" });
worker.addEventListener("message", (event) => {
const message = event.data || {};
if (message.type === "log") {
logStep(message.message);
} else if (message.type === "stdout") {
stdoutText += `${message.line}\n`;
} else if (message.type === "stderr") {
stderrText += `${message.line}\n`;
} else if (message.type === "done") {
stdoutText = message.stdoutText;
stderrText = message.stderrText;
logStep(`Worker fd_read calls: ${message.readCalls}; ring-buffer waits: ${message.waitCount}.`);
workerDone.resolve(message);
} else if (message.type === "error") {
workerDone.reject(new Error(message.message));
}
});
worker.postMessage({
type: "start",
wasmURL,
sourceURL,
statusCode: response.status,
sharedBuffer,
capacity: streamBufferCapacity,
});
logStep(`Streaming HTTP ${response.status} response body into SharedArrayBuffer.`);
const reader = response.body.getReader();
const documentDecoder = new TextDecoder();
let networkChunkCount = 0;
let fetchedByteCount = 0;
try {
while (true) {
const { value, done } = await reader.read();
if (done) {
break;
}
networkChunkCount += 1;
fetchedByteCount += value.byteLength;
appendStreamDocumentChunk(documentDecoder.decode(value, { stream: true }));
await writeToRingBuffer(value, control, bytes);
logStep(`Network chunk ${networkChunkCount}: wrote ${formatBytes(value.byteLength)} to ring buffer.`);
}
} catch (error) {
Atomics.store(control, streamControlIndex.error, 1);
Atomics.store(control, streamControlIndex.closed, 1);
wakeRingBuffer(control);
worker?.terminate();
throw error;
}
appendStreamDocumentChunk(documentDecoder.decode());
await waitForDocumentFlush();
Atomics.store(control, streamControlIndex.closed, 1);
wakeRingBuffer(control);
const fetchMS = performance.now() - fetchStart;
logStep(`Network stream finished: ${networkChunkCount} chunk(s), ${formatBytes(fetchedByteCount)} in ${formatDuration(fetchMS)}.`);
let workerResult;
try {
workerResult = await workerDonePromise;
} catch (error) {
worker?.terminate();
throw error;
}
worker?.terminate();
renderBenchmarkResult({
sourceURL,
moduleLoadMS: workerResult.moduleLoadMS,
fetchMS,
wallClockParseMS: workerResult.processWallClockMS,
stdoutText,
stderrText,
inputByteCount: fetchedByteCount,
workerResult,
modeLabel: "true streaming",
});
}
function renderBenchmarkResult({
sourceURL,
moduleLoadMS,
fetchMS,
wallClockParseMS,
stdoutText,
stderrText,
inputByteCount,
modeLabel,
}) {
logStep("Parsing Swift JSON result from stdout.");
const parsed = parseSmokeOutput(stdoutText);
const ok = parsed?.ok === true;
if (!Number.isFinite(parsed?.parseMilliseconds)) {
throw new Error("WASM output did not include a finite Swift parseMilliseconds value.");
}
const parseMS = parsed.parseMilliseconds;
const throughput = parseMS > 0 ? inputByteCount / (parseMS / 1000) : 0;
if (!Number.isFinite(parsed?.chunkCount) || !Number.isFinite(parsed?.chunkSize)) {
throw new Error("WASM output did not include finite Swift chunk instrumentation.");
}
logStep(`Swift transport: ${parsed.transport || "unknown"}; HTTP status: ${parsed.httpStatusCode ?? "n/a"}.`);
logStep(`Swift parse reported ${formatDuration(parseMS)}.`);
logStep(
`Swift ChunkedUTF8Reader consumed ${parsed.chunkCount} chunk(s) at chunkSize ${formatBytes(parsed.chunkSize)}.`,
);
logStep(`Largest Swift chunk observed: ${formatBytes(parsed.maxChunkByteCount ?? 0)}.`);
logStep(`Document scanner emitted ${parsed.scannerDocumentSourceCount ?? 0} document source(s).`);
logStep(`Swift stdin read calls: ${parsed.stdinReadCount ?? "n/a"}; max stdin read: ${formatBytes(parsed.maxStdinReadByteCount ?? 0)}.`);
logStep(`Parsed ${parsed.documentCount ?? 0} document(s).`);
logStep(`Throughput: ${formatBytes(throughput)}/s.`);
statusElement.className = ok ? "status pass" : "status fail";
statusElement.textContent = ok ? "Passed" : "Failed";
bytesMetric.textContent = formatBytes(inputByteCount);
fetchMetric.textContent = formatDuration(fetchMS);
parseMetric.textContent = formatDuration(parseMS);
throughputMetric.textContent = `${formatBytes(throughput)}/s`;
documentsMetric.textContent = parsed?.documentCount ?? "-";
outputElement.textContent = [
`url: ${sourceURL}`,
`mode: ${modeLabel}`,
`moduleLoad: ${formatDuration(moduleLoadMS)}`,
`fetch: ${formatDuration(fetchMS)}`,
`swiftParse: ${formatDuration(parseMS)}`,
`processWallClock: ${formatDuration(wallClockParseMS)}`,
`throughput: ${formatBytes(throughput)}/s`,
"",
"stdout:",
stdoutText || "(empty)",
"",
"stderr:",
stderrText || "(empty)",
].join("\n");
logStep(ok ? "Benchmark completed successfully." : "Benchmark finished with parser failure.");
resetButtons();
}
async function writeToRingBuffer(chunk, control, bytes) {
let offset = 0;
while (offset < chunk.byteLength) {
const available = Atomics.load(control, streamControlIndex.available);
if (available >= streamBufferCapacity) {
logStep("Ring buffer full; fetch pump waiting for Worker fd_read to resume.", "muted");
await waitForRingSpace(control);
logStep("Ring buffer has capacity again; fetch pump resumed.", "muted");
continue;
}
const write = Atomics.load(control, streamControlIndex.write);
const space = streamBufferCapacity - available;
const byteCount = Math.min(chunk.byteLength - offset, space, streamBufferCapacity - write);
bytes.set(chunk.subarray(offset, offset + byteCount), write);
Atomics.store(control, streamControlIndex.write, (write + byteCount) % streamBufferCapacity);
Atomics.add(control, streamControlIndex.available, byteCount);
Atomics.add(control, streamControlIndex.totalWritten, byteCount);
offset += byteCount;
wakeRingBuffer(control);
}
}
async function waitForRingSpace(control) {
while (Atomics.load(control, streamControlIndex.available) >= streamBufferCapacity) {
await new Promise((resolve) => setTimeout(resolve, 4));
}
}
function wakeRingBuffer(control) {
Atomics.add(control, streamControlIndex.sequence, 1);
Atomics.notify(control, streamControlIndex.sequence);
}
async function loadWasmModule(url) {
if (!modulePromise) {
modulePromise = loadWasmBytes(url).then((bytes) => WebAssembly.compile(bytes));
}
return modulePromise;
}
async function loadWasmBytes(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch WASM: HTTP ${response.status}`);
}
const bytes = await response.arrayBuffer();
if (!url.endsWith(".gz")) {
return bytes;
}
if (!("DecompressionStream" in globalThis)) {
throw new Error("This browser does not support DecompressionStream.");
}
const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip"));
return new Response(stream).arrayBuffer();
}
function parseSmokeOutput(text) {
try {
return JSON.parse(text);
} catch {
return null;
}
}
function renderFailure(error) {
statusElement.className = "status fail";
statusElement.textContent = "Failed";
outputElement.textContent = String(error?.stack || error);
logStep(`Failed: ${String(error?.message || error)}`, "error");
resetButtons();
}
function resetButtons() {
runButton.disabled = false;
streamButton.disabled = !streamingSupported();
}
function updateStreamingCapabilities() {
const isolated = crossOriginIsolated === true;
const hasSharedArrayBuffer = "SharedArrayBuffer" in globalThis;
const hasWorker = "Worker" in globalThis;
const hasAtomicsWait = typeof Atomics?.wait === "function";
isolationMetric.textContent = isolated ? "yes" : "no";
sabMetric.textContent = hasSharedArrayBuffer ? "yes" : "no";
workerMetric.textContent = hasWorker && hasAtomicsWait ? "yes" : "no";
modeMetric.textContent = streamingSupported() ? "streaming enabled" : "buffered fallback";
resetButtons();
}
function streamingSupported() {
return crossOriginIsolated === true
&& "SharedArrayBuffer" in globalThis
&& "Worker" in globalThis
&& typeof Atomics?.wait === "function";
}
function resetMetrics() {
bytesMetric.textContent = "-";
fetchMetric.textContent = "-";
parseMetric.textContent = "-";
throughputMetric.textContent = "-";
documentsMetric.textContent = "-";
}
async function copyRunLog() {
const text = runLogElement.innerText.trim();
if (!text) {
return;
}
try {
if ("clipboard" in navigator && window.isSecureContext) {
await navigator.clipboard.writeText(text);
} else {
copyTextWithTextarea(text);
}
} catch {
copyTextWithTextarea(text);
}
const previousLabel = copyRunLogButton.textContent;
copyRunLogButton.textContent = "Copied";
window.setTimeout(() => {
copyRunLogButton.textContent = previousLabel;
}, 1500);
}
function copyTextWithTextarea(text) {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.setAttribute("readonly", "");
textarea.style.position = "fixed";
textarea.style.opacity = "0";
document.body.append(textarea);
textarea.select();
document.execCommand("copy");
textarea.remove();
}
function resetStreamDocument(message) {
documentRenderQueue = [];
documentRenderResolvers.splice(0).forEach((resolve) => resolve());
streamDocumentElement.replaceChildren(document.createTextNode(message));
streamDocumentElement.scrollTop = 0;
}
function replaceStreamDocument(text) {
documentRenderQueue = [];
documentRenderResolvers.splice(0).forEach((resolve) => resolve());
streamDocumentElement.replaceChildren();
appendStreamDocumentChunk(text || "(empty document)");
streamDocumentElement.scrollTop = 0;
}
function appendStreamDocumentChunk(text) {
if (!text) {
return;
}
if (streamDocumentElement.childNodes.length === 1 && streamDocumentElement.textContent.startsWith("Waiting for")) {
streamDocumentElement.replaceChildren();
}
documentRenderQueue.push(text);
scheduleDocumentFlush();
}
function decodeYAMLBytes(bytes) {
return new TextDecoder().decode(bytes);
}
function scheduleDocumentFlush() {
if (documentRenderScheduled) {
return;
}
documentRenderScheduled = true;
requestAnimationFrame(flushDocumentRenderQueue);
}
function flushDocumentRenderQueue() {
documentRenderScheduled = false;
let remainingBudget = documentRenderBudget;
const fragment = document.createDocumentFragment();
while (documentRenderQueue.length > 0 && remainingBudget > 0) {
const next = documentRenderQueue.shift();
const piece = next.length > remainingBudget ? next.slice(0, remainingBudget) : next;
const rest = next.length > remainingBudget ? next.slice(remainingBudget) : "";
fragment.append(renderYAMLSyntax(piece));
remainingBudget -= piece.length;
if (rest) {
documentRenderQueue.unshift(rest);
}
}
streamDocumentElement.append(fragment);
streamDocumentElement.scrollTop = streamDocumentElement.scrollHeight;
if (documentRenderQueue.length > 0) {
scheduleDocumentFlush();
return;
}
documentRenderResolvers.splice(0).forEach((resolve) => resolve());
}
function waitForDocumentFlush() {
if (!documentRenderScheduled && documentRenderQueue.length === 0) {
return Promise.resolve();
}
return new Promise((resolve) => {
documentRenderResolvers.push(resolve);
});
}
function renderYAMLSyntax(text) {
const fragment = document.createDocumentFragment();
for (const segment of text.split(/(\r?\n)/)) {
if (segment === "\n" || segment === "\r\n") {
fragment.append(document.createTextNode(segment));
} else {
renderYAMLLine(segment, fragment);
}
}
return fragment;
}
function renderYAMLLine(line, fragment) {
const commentIndex = findYAMLCommentIndex(line);
const source = commentIndex >= 0 ? line.slice(0, commentIndex) : line;
const comment = commentIndex >= 0 ? line.slice(commentIndex) : "";
const keyMatch = source.match(/^(\s*(?:-\s*)?)([A-Za-z0-9_.-]+)(\s*:)(.*)$/);
if (source.trim() === "---" || source.trim() === "...") {
appendSyntaxSpan(fragment, source, "yaml-marker");
} else if (keyMatch) {
fragment.append(document.createTextNode(keyMatch[1]));
appendSyntaxSpan(fragment, keyMatch[2], "yaml-key");
appendSyntaxSpan(fragment, keyMatch[3], "yaml-punctuation");
appendYAMLValue(fragment, keyMatch[4]);
} else {
appendYAMLValue(fragment, source);
}
if (comment) {
appendSyntaxSpan(fragment, comment, "yaml-comment");
}
}
function appendYAMLValue(fragment, value) {
const trimmed = value.trim();
const leading = value.slice(0, value.length - value.trimStart().length);
const trailingStart = value.length - value.trimEnd().length;
const trailing = trailingStart > 0 ? value.slice(value.length - trailingStart) : "";
if (leading) {
fragment.append(document.createTextNode(leading));
}
if (/^(true|false|null|~)$/i.test(trimmed)) {
appendSyntaxSpan(fragment, trimmed, "yaml-literal");
} else if (/^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(trimmed)) {
appendSyntaxSpan(fragment, trimmed, "yaml-number");
} else if (/^(['"]).*\1$/.test(trimmed)) {
appendSyntaxSpan(fragment, trimmed, "yaml-string");
} else {
fragment.append(document.createTextNode(trimmed));
}
if (trailing) {
fragment.append(document.createTextNode(trailing));
}
}
function appendSyntaxSpan(fragment, text, className) {
const span = document.createElement("span");
span.className = className;
span.textContent = text;
fragment.append(span);
}
function findYAMLCommentIndex(line) {
let quote = null;
for (let index = 0; index < line.length; index += 1) {
const character = line[index];
if ((character === "'" || character === "\"") && line[index - 1] !== "\\") {
quote = quote === character ? null : quote || character;
}
if (character === "#" && quote === null && (index === 0 || /\s/.test(line[index - 1]))) {
return index;
}
}
return -1;
}
function formatDuration(milliseconds) {
if (milliseconds < 1000) {
return `${milliseconds.toFixed(1)} ms`;
}
return `${(milliseconds / 1000).toFixed(3)} s`;
}
function formatBytes(byteCount) {
if (byteCount < 1024) {
return `${byteCount.toFixed(0)} B`;
}
if (byteCount < 1024 * 1024) {
return `${(byteCount / 1024).toFixed(1)} KiB`;
}
return `${(byteCount / 1024 / 1024).toFixed(2)} MiB`;
}
function resetLog() {
runStartedAt = performance.now();
runLogElement.replaceChildren();
}
function logStep(message, level = "info") {
const entry = document.createElement("div");
entry.className = `log-entry ${level}`;
const elapsed = (performance.now() - runStartedAt) / 1000;
entry.textContent = `[+${elapsed.toFixed(3)}s] ${message}`;
runLogElement.append(entry);
runLogElement.scrollTop = runLogElement.scrollHeight;
}
function nextPaint() {
return new Promise((resolve) => {
requestAnimationFrame(() => {
requestAnimationFrame(resolve);
});
});
}