/* eslint-disable no-use-before-define */ import { Range } from './loc.js' // TODO: at some point, plumb filenames throughout the entire system so we can report them! type ErrorDetails = { phase: string, range?: Range message: string, src?: string hint?: string } const errorDetails = (phase: string, message: string, range?: Range, src?: string, hint?: string) => ({ phase, range, message, src, hint }) // TODO: make Error carry an array of ErrorDetails so we can report multiple errors type Error = { tag: 'error', details: ErrorDetails[] // eslint-disable-next-line no-use-before-define andThen: (f: (_:T) => Result) => Result asyncAndThen: (f: (_:T) => Promise>) => Promise> } function detailsToCompleteString (details: ErrorDetails): string { const msg = [ `:${details.range ? details.range.start.column : ''}:${details.range ? details.range.start.line : ''}: ${details.phase} error:`, ` ${details.message}` ] if (details.src) { msg.push(` In program: ${details.src}`) } if (details.hint) { msg.push(` Hint: ${details.hint}`) } return msg.join('\n') } function allDetailsToCompleteString (details: ErrorDetails[]): string { return details.map(detailsToCompleteString).join('\n') } function detailsToMsgString (details: ErrorDetails): string { const msg = [`${details.phase} error: ${details.message}`] if (details.hint) { msg.push('\n') msg.push(`Hint: ${details.hint}`) } return msg.join('\n') } function errorToString (err: Error) { return err.details.map(detailsToCompleteString).join('\n') } type Ok = { tag: 'ok', value: T // eslint-disable-next-line no-use-before-define andThen: (f: (_:T) => Result) => Result asyncAndThen: (f: (_:T) => Promise>) => Promise> } type Result = Error | Ok function errors (errs: ErrorDetails[]): Error { return { tag: 'error', details: errs, andThen: f => errors(errs), asyncAndThen: f => Promise.resolve(errors(errs)) } } function error (phase: string, message: string, range?: Range, src?: string, hint?: string): Error { return errors([{ phase, range, message, src, hint }]) } function ok (x: T): Ok { return { tag: 'ok', value: x, andThen: f => f(x), asyncAndThen: async f => f(x) } } function rethrow (err: Error) : Error { // N.B., safe because Error's type variable is a phantom return err as any as Error } function join (arr: Result[]): Result { const result = [] for (const e of arr) { switch (e.tag) { case 'error': return rethrow(e) case 'ok': result.push(e.value) } } return ok(result) } function detailsToResult (errs: ErrorDetails[]): Result { return errs.length > 0 ? errors(errs) : ok(null) } class ICE extends Error { constructor (fn: string, reason: string, opts?: ErrorOptions) { super(`ICE (${fn}): ${reason}`, opts) } } export { Result, Error, ErrorDetails, errorDetails, error, errors, ok, detailsToCompleteString, allDetailsToCompleteString, detailsToMsgString, errorToString, rethrow, join, detailsToResult, ICE }