e01ddf1MATLAB-syntax scripts on WebGPU: fused kernels, tic/toc timing, CPU comparisonJeremy Magland 1/**
2 * Compile failures, reported in coordinates of the script the user edits.
3 *
4 * Failures arrive from three places, each with its own idea of position:
5 * numbl's parser (a `position` offset), numbl's lowerer (`UnsupportedConstruct`
6 * / `JitTypeError`, with a `span`), and this project's planner and WGSL
7 * emitter (`UnsupportedOnGpu`, carrying the numbl span it was given). All of
8 * them are offsets into the script, so they need only be turned into a line
9 * and column for display.
10 */
12/** A compile failure located in the script source. */
13export class ScriptCompileError extends Error {
14 /** Offset into the script, when the failure has a position. */
15 readonly start?: number;
16 readonly end?: number;
18 constructor(
19 message: string,
20 opts: { start?: number; end?: number; cause?: unknown } = {},
21 ) {
22 super(message, { cause: opts.cause });
23 this.name = 'ScriptCompileError';
24 this.start = opts.start;
25 this.end = opts.end;
26 }
27}
29/** Raised for a construct the WGSL backend cannot express. Mirrors numbl's
30 * own decline discipline: fail at compile time with a source span, never
31 * silently produce something that computes the wrong thing. */
32export class UnsupportedOnGpu extends Error {
33 readonly span?: unknown;
34 constructor(message: string, span?: unknown) {
35 super(message);
36 this.name = 'UnsupportedOnGpu';
37 this.span = span;
38 }
39}
41/** Extract whatever position information an error carries. */
42function positionOf(e: unknown): { start?: number; end?: number } {
43 const span = (e as { span?: { start?: unknown; end?: unknown } }).span;
44 if (span && typeof span.start === 'number') {
45 return {
46 start: span.start,
47 end: typeof span.end === 'number' ? span.end : undefined,
48 };
49 }
50 // numbl's parser SyntaxError reports a bare offset.
51 const position = (e as { position?: unknown }).position;
52 if (typeof position === 'number') return { start: position };
53 return {};
54}
56/** Normalize any thrown value into a located `ScriptCompileError`. */
57export function asCompileError(e: unknown): ScriptCompileError {
58 if (e instanceof ScriptCompileError) return e;
59 const { start, end } = positionOf(e);
60 const raw = e instanceof Error ? e.message : String(e);
61 // numbl's parse errors read as bare token complaints out of context.
62 const message =
63 (e as Error)?.name === 'SyntaxError' ? `MATLAB syntax error: ${raw}` : raw;
64 return new ScriptCompileError(message, { start, end, cause: e });
65}
67/** Run `fn`, locating any compile failure in the script. */
68export function inScript<T>(fn: () => T): T {
69 try {
70 return fn();
71 } catch (e) {
72 throw asCompileError(e);
73 }
74}
76/** Render a failure for display: message and 1-based line/column. */
77export function formatFailure(e: unknown, source: string): string {
78 const message = e instanceof Error ? e.message : String(e);
79 if (!(e instanceof ScriptCompileError)) return message;
80 if (e.start !== undefined && e.start <= source.length) {
81 const before = source.slice(0, e.start);
82 const line = before.split('\n').length;
83 const column = e.start - before.lastIndexOf('\n');
84 return `${message} (line ${line}, column ${column})`;
85 }
86 return message;
87}