/ concept-collection / turing-surface
Sign in
concept-collection / turing-surface
105 lines · 3.6 KBCodeBlameHistory
2 * Compile failures, reported in coordinates of the model file 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 WGSL emitter
7 * (`UnsupportedOnGpu`, carrying the numbl span it was given). All of them are
8 * offsets into the whole model file — the file is parsed once, and each function
9 * is specialized from that one AST — so they need only be turned into a line and
10 * column for the editor.
11 */
13/** A compile failure located in the full model source. */
14export class ModelCompileError extends Error {
15 /** Offset into the whole .m file, when the failure has a position. */
16 readonly start?: number;
17 readonly end?: number;
18 /** Name of the model function being compiled. */
19 readonly fn?: string;
21 constructor(
22 message: string,
23 opts: { start?: number; end?: number; fn?: string; cause?: unknown } = {},
24 ) {
25 super(message, { cause: opts.cause });
26 this.name = 'ModelCompileError';
27 this.start = opts.start;
28 this.end = opts.end;
29 this.fn = opts.fn;
30 }
33/** Extract whatever position information an error carries. */
34function positionOf(e: unknown): { start?: number; end?: number } {
35 const span = (e as { span?: { start?: unknown; end?: unknown } }).span;
36 if (span && typeof span.start === 'number') {
37 return {
38 start: span.start,
39 end: typeof span.end === 'number' ? span.end : undefined,
40 };
41 }
42 // numbl's parser SyntaxError reports a bare offset.
43 const position = (e as { position?: unknown }).position;
44 if (typeof position === 'number') return { start: position };
45 return {};
48/** Normalize any thrown value into a located `ModelCompileError`. */
49function asCompileError(e: unknown, fn?: string): ModelCompileError {
50 if (e instanceof ModelCompileError) return e;
51 const { start, end } = positionOf(e);
52 const raw = e instanceof Error ? e.message : String(e);
53 // numbl's parse errors read as bare token complaints out of context.
54 const message =
55 (e as Error)?.name === 'SyntaxError' ? `MATLAB syntax error: ${raw}` : raw;
56 return new ModelCompileError(message, { fn, start, end, cause: e });
59/**
60 * Run `fn`, locating any compile failure in the model file. Use for whole-file
61 * phases (parsing) that belong to no single function.
62 */
63export function inModel<T>(fn: () => T): T {
64 try {
65 return fn();
66 } catch (e) {
67 throw asCompileError(e);
68 }
71/** Run `fn`, attributing any compile failure to the model function `name`. */
72export function inFunction<T>(name: string, fn: () => T): T {
73 try {
74 return fn();
75 } catch (e) {
76 throw asCompileError(e, name);
77 }
80/** Async form of `inFunction`. */
81export async function inFunctionAsync<T>(
82 name: string,
83 fn: () => Promise<T>,
84): Promise<T> {
85 try {
86 return await fn();
87 } catch (e) {
88 throw asCompileError(e, name);
89 }
92/** Render a failure for display: message, section, and 1-based line/column. */
93export function formatFailure(e: unknown, source: string): string {
94 const message = e instanceof Error ? e.message : String(e);
95 if (!(e instanceof ModelCompileError)) return message;
96 const where: string[] = [];
97 if (e.start !== undefined && e.start <= source.length) {
98 const before = source.slice(0, e.start);
99 const line = before.split('\n').length;
100 const column = e.start - before.lastIndexOf('\n');
101 where.push(`line ${line}, column ${column}`);
102 }
103 if (e.fn) where.push(`in ${e.fn}()`);
104 return where.length ? `${message} (${where.join(', ')})` : message;
moveopenescclose