From 7f7b461db195951006eb46fc760c35c8ddca41eb Mon Sep 17 00:00:00 2001 From: ivanjermakov Date: Fri, 26 Apr 2024 19:47:52 +0200 Subject: [PATCH] Ast: invalid chaining error; error handling refactoring --- src/ast/expr.ts | 6 ++++-- src/cli.ts | 15 +++++++++++++++ src/index.ts | 22 ++++++---------------- src/package/build.ts | 8 +++++++- src/semantic/error.ts | 18 ++++++++++++++---- 5 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/ast/expr.ts b/src/ast/expr.ts index 5cfbbdc..d1cf0b1 100644 --- a/src/ast/expr.ts +++ b/src/ast/expr.ts @@ -1,6 +1,7 @@ import { ParseNode, filterNonAstNodes } from '../parser' -import { Context } from '../scope' +import { Context, addError } from '../scope' import { Typed, Virtual } from '../semantic' +import { invalidOperatorChainError } from '../semantic/error' import { assert } from '../util/todo' import { AstNode } from './index' import { BinaryOp, PostfixOp, associativityMap, buildBinaryOp, buildPostfixOp, precedenceMap } from './op' @@ -49,7 +50,8 @@ export const buildExpr = (node: ParseNode, ctx: Context): Expr => { const o1Assoc = associativityMap.get(o1.kind)! const o2Assoc = associativityMap.get(o2.kind)! if (o1Prec === o2Prec && o1Assoc === 'none' && o2Assoc === 'none') { - throw Error(`cannot chain operators \`${o1.kind}\` and \`${o2.kind}\``) + addError(ctx, invalidOperatorChainError(ctx, o1, o2)) + break } if ((o1Assoc !== 'right' && o1Prec === o2Prec) || o1Prec < o2Prec) { operatorStack.pop() diff --git a/src/cli.ts b/src/cli.ts index 49a93df..4647577 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,3 +1,7 @@ +import { colorError, prettySourceMessage } from './error' +import { getSpan } from './parser' +import { Context } from './scope' + export const parseOption = (longName: string): string | undefined => { const arg = process.argv.find(a => a.startsWith(`--${longName}`)) if (arg?.includes('=')) { @@ -5,3 +9,14 @@ export const parseOption = (longName: string): string | undefined => { } return arg !== undefined ? '' : undefined } + +export const reportErrors = (ctx: Context): void | never => { + if (ctx.errors.length > 0) { + for (const error of ctx.errors) { + console.error( + prettySourceMessage(colorError(error.message), getSpan(error.node.parseNode), error.source, error.notes) + ) + } + process.exit(1) + } +} diff --git a/src/index.ts b/src/index.ts index b3e7f36..dd93320 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,9 @@ import { existsSync, readFileSync, statSync } from 'fs' import { basename, dirname, join } from 'path' import { fileURLToPath } from 'url' -import { parseOption } from './cli' +import { parseOption, reportErrors } from './cli' import { fromCmd } from './config' -import { colorError, colorWarning, prettySourceMessage } from './error' +import { colorWarning, prettySourceMessage } from './error' import { Package } from './package' import { buildModule } from './package/build' import { emitPackage } from './package/emit' @@ -111,6 +111,8 @@ if (isDir) { lib = [std] } +reportErrors(ctx) + const packages = [...lib, pkg] const std = packages.find(pkg => pkg.name === 'std') @@ -137,26 +139,14 @@ if (ctx.config.libCheck) { } assert(ctx.moduleStack.length === 0, ctx.moduleStack.length.toString()) -if (ctx.errors.length > 0) { - for (const error of ctx.errors) { - console.error( - prettySourceMessage( - colorError(error.message), - getSpan(error.node.parseNode), - error.module.source, - error.notes - ) - ) - } - process.exit(1) -} +reportErrors(ctx) for (const warning of ctx.warnings) { console.error( prettySourceMessage( colorWarning(warning.message), getSpan(warning.node.parseNode), - warning.module.source, + warning.source, warning.notes ) ) diff --git a/src/package/build.ts b/src/package/build.ts index c71f1a1..172f5ec 100644 --- a/src/package/build.ts +++ b/src/package/build.ts @@ -10,9 +10,12 @@ import { Source } from '../source' export const buildModule = ( source: Source, vid: VirtualIdentifier, + // TODO: might be better off being separate `AstContext` with only errors and source ctx: Context, compiled = false ): Module | undefined => { + const dummyModule: Module = { source: source } + ctx.moduleStack.push(dummyModule) const tokens = tokenize(source.code) const errorTokens = tokens.filter(t => erroneousTokenKinds.includes(t.kind)) if (errorTokens.length > 0) { @@ -34,5 +37,8 @@ export const buildModule = ( } const mod = /mod\.no$/.test(source.filepath) - return buildModuleAst(root, vid, source, mod, ctx, compiled) + const ast = buildModuleAst(root, vid, source, mod, ctx, compiled) + + ctx.moduleStack.pop() + return ast } diff --git a/src/semantic/error.ts b/src/semantic/error.ts index ff119f5..98f0265 100644 --- a/src/semantic/error.ts +++ b/src/semantic/error.ts @@ -1,7 +1,7 @@ import { Arg, AstNode, Module, Param } from '../ast' import { Expr } from '../ast/expr' import { MatchClause, MatchExpr, PatternExpr } from '../ast/match' -import { CallOp } from '../ast/op' +import { BinaryOp, CallOp } from '../ast/op' import { Identifier, Name, Operand } from '../ast/operand' import { FnDef, ImplDef, Statement, VarDef } from '../ast/statement' import { Type } from '../ast/type' @@ -9,13 +9,14 @@ import { FieldDef } from '../ast/type-def' import { Context } from '../scope' import { vidToString } from '../scope/util' import { MethodDef } from '../scope/vid' +import { Source } from '../source' import { VirtualFnType, VirtualType, virtualTypeToString } from '../typecheck' -import { unreachable } from '../util/todo' +import { assert, unreachable } from '../util/todo' import { MatchTree, unmatchedPaths } from './exhaust' export interface SemanticError { code: number - module: Module + source: Source node: AstNode message: string notes: string[] @@ -27,7 +28,10 @@ export const semanticError = ( node: AstNode, message: string, notes: string[] = [] -): SemanticError => ({ code, module: ctx.moduleStack.at(-1)!, node, message, notes }) +): SemanticError => { + assert(ctx.moduleStack.length > 0) + return { code, source: ctx.moduleStack.at(-1)!.source, node, message, notes } +} export const notFoundError = ( ctx: Context, @@ -250,6 +254,12 @@ export const nonDestructurableTypeError = ( return semanticError(32, ctx, patternExpr, msg) } +export const invalidOperatorChainError = (ctx: Context, o1: BinaryOp, o2: BinaryOp): SemanticError => { + const msg = `invalid operator chaining: \`${o1.kind}\` and \`${o2.kind}\`` + // TODO: composite location spans + return semanticError(33, ctx, o1, msg) +} + export const unexpectedTypeError = ( ctx: Context, node: AstNode,