Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | 173x 173x 1668x 173x 323x 321x 173x 352x 352x 684x 684x 1x 351x 173x 173x | export type Vars = Readonly<Record<string, unknown>>;
export interface Step<S> {
readonly op: string;
readonly state: S;
readonly caption: string;
/** Source line this step came from, set with `at`. 1-based. */
readonly line?: number;
/** Variables to show with this step, set with `watch`. */
readonly vars?: Vars;
}
export interface Trace<S> {
readonly steps: readonly Step<S>[];
record(op: string, state: S, caption?: string): void;
/** Tags the steps recorded from now on with a 1-based source line. Records nothing itself. */
at(line: number): void;
/**
* Shows these variables with the steps recorded from now on. Merges with earlier calls;
* a value of `undefined` hides that variable. Records nothing itself.
*/
watch(vars: Record<string, unknown>): void;
/**
* How to draw a state. Structure tracers set this so `render` needs no view.
* Method syntax on purpose: it lets any trace be stored as `Trace<unknown>`, for example in a registry of mixed figures.
*/
view?(state: S): string;
/**
* The natural width and height `view` would draw a state at, worked out without drawing it.
* Optional: `render` uses it to size long traces quickly, and draws each step to measure it otherwise.
*/
size?(state: S): readonly [width: number, height: number];
}
/** Turns one state into SVG markup. Views must escape any text they put in the markup. */
export type View<S> = (state: S) => string;
/** Records a snapshot of `state` at every step. The first step is the initial state. */
export function createTrace<S>(initial: S, caption = ""): Trace<S> {
const steps: Step<S>[] = [];
let line: number | undefined;
let vars: Vars | undefined;
const record = (op: string, state: S, caption = "") => {
// Clone so later mutations by the algorithm don't rewrite earlier steps.
steps.push({
op,
state: structuredClone(state),
caption,
...(line !== undefined && { line }),
...(vars && { vars }),
});
};
const at = (n: number) => {
if (!Number.isInteger(n) || n < 1) throw new RangeError(`line ${n} must be a whole number from 1`);
line = n;
};
// A new object per call, so steps can share it safely: it never changes after this.
const watch = (next: Record<string, unknown>) => {
const copy: Record<string, unknown> = {};
for (const [name, value] of Object.entries(next)) {
try {
copy[name] = structuredClone(value);
} catch {
throw new TypeError(`watch: cannot copy "${name}"; functions, DOM nodes and class instances with private fields are not supported`);
}
}
vars = { ...vars, ...copy };
};
record("init", initial, caption);
return { steps, record, at, watch };
}
|