SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
f0634fb verified
Raw History Blame Contribute Delete
2.28 kB
// src/budget.ts
//
// Parse budget: a hard cap on wall-clock time and on the number of syntax
// nodes a single parse may create. The parser calls `budget.tick()` every
// time it creates a node; long scan loops (word runs, strings, heredoc
// bodies) call `budget.progress()` periodically so a pathological single
// token still hits the deadline without inflating the node count. When
// either limit is exceeded the methods throw `Aborted`, which the `parse`
// entry point catches and reports as `{ ok: false, reason: 'aborted' }`.
export const DEFAULT_TIMEOUT_MS = 50;
export const DEFAULT_MAX_NODES = 50_000;
/** Internal control-flow error. Never escapes the `parse` entry point. */
export class Aborted extends Error {
constructor(message: string) {
super(message);
this.name = 'Aborted';
}
}
export interface BudgetOptions {
/** Wall-clock limit in milliseconds. `Infinity` disables the time check. */
timeoutMs?: number;
/** Maximum number of nodes the parse may create. */
maxNodes?: number;
}
export class ParseBudget {
private readonly deadline: number;
private readonly maxNodes: number;
private nodeCount = 0;
constructor(options: BudgetOptions = {}) {
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
this.deadline = Date.now() + timeoutMs;
this.maxNodes = options.maxNodes ?? DEFAULT_MAX_NODES;
}
/** Number of nodes created so far. */
get nodesUsed(): number {
return this.nodeCount;
}
/**
* Account for one created node and re-check the deadline. Throws `Aborted`
* when the node cap is exceeded or the deadline has been reached.
*/
tick(): void {
this.nodeCount++;
if (this.nodeCount > this.maxNodes) {
throw new Aborted(`parse aborted: node budget exceeded (${this.nodeCount} > ${this.maxNodes})`);
}
if (Date.now() >= this.deadline) {
throw new Aborted(`parse aborted: timeout`);
}
}
/**
* Re-check the deadline WITHOUT counting a node. For long scan loops that
* run many iterations per produced node (character-level scanning), called
* at intervals so the deadline is still enforced promptly.
*/
progress(): void {
if (Date.now() >= this.deadline) {
throw new Aborted(`parse aborted: timeout`);
}
}
}