diff --git a/src/ast.ts b/src/ast.ts index 9b6e15e..05f6b9d 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1,29 +1,31 @@ +import { Loc } from "./diagnostics.ts"; + export function create( - line: number, + loc: Loc, tag: Tag, kind: Omit, ): Node { - return Node.create(line, tag, kind); + return Node.create(loc, tag, kind); } export class Node { private static idCounter = 0; static create( - line: number, + loc: Loc, tag: Tag, kind: Omit, ): Node { return new Node( Node.idCounter++, - line, + loc, { tag, ...kind } as NodeKind & { tag: Tag }, ); } private constructor( public id: number, - public line: number, + public loc: Loc, public kind: NodeKind, ) {} diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 4d7b245..b0e2e9c 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -15,11 +15,13 @@ export type FileInfo = { export function printDiagnostics( filename: string, - line: number, + loc: Loc, severity: "error" | "info", message: string, text?: string, ) { + const line = loc.line; + const severityColor = ({ "error": "red", "info": "blue", diff --git a/src/front/check.ts b/src/front/check.ts index 3f503f9..c5281c6 100644 --- a/src/front/check.ts +++ b/src/front/check.ts @@ -1,5 +1,5 @@ import * as ast from "../ast.ts"; -import { printDiagnostics } from "../diagnostics.ts"; +import { Loc, printDiagnostics } from "../diagnostics.ts"; import { Ty } from "../ty.ts"; import { builtins } from "./builtins.ts"; import { ResolveMap } from "./resolve.ts"; @@ -46,14 +46,14 @@ export class Checker { this.assertCompatible( exprTy, explicitTy, - sym.stmt.kind.expr.line, + sym.stmt.kind.expr.loc, ); } return exprTy; } if (sym.tag === "FnParam") { if (!node.kind.ty) { - this.error(node.line, `parameter must have a type`); + this.error(node.loc, `parameter must have a type`); this.fail(); } return this.check(node.kind.ty); @@ -91,13 +91,13 @@ export class Checker { for (const value of node.kind.values) { const valueTy = this.check(value); if (ty) { - this.assertCompatible(ty, valueTy, value.line); + this.assertCompatible(ty, valueTy, value.loc); } else { ty = valueTy; } } if (!ty) { - this.error(node.line, `could not infer type of empty array`); + this.error(node.loc, `could not infer type of empty array`); this.fail(); } const length = node.kind.values.length; @@ -120,7 +120,7 @@ export class Checker { return Ty.create("Slice", { ty: exprTy.kind.ty }); } this.error( - node.line, + node.loc, `cannot use index operator on '${exprTy.pretty()}' with '${argTy.pretty()}'`, ); this.fail(); @@ -150,7 +150,7 @@ export class Checker { } } this.error( - node.line, + node.loc, `operator '${node.kind.tok}' cannot be applied to type '${exprTy.pretty()}'`, ); this.fail(); @@ -167,7 +167,7 @@ export class Checker { ); if (!binaryOp) { this.error( - node.line, + node.loc, `operator '${node.kind.tok}' cannot be applied to types '${left.pretty()}' and '${right.pretty()}'`, ); this.fail(); @@ -180,7 +180,7 @@ export class Checker { const operandTy = operandExpr && this.check(operandExpr); if (operandTy && !operandTy.compatibleWith(Ty.Int)) { this.error( - operandExpr.line, + operandExpr.loc, `range operand must be '${Ty.Int.pretty()}', not '${operandTy.pretty()}'`, ); this.fail(); @@ -198,7 +198,7 @@ export class Checker { case "bool": return Ty.Bool; default: - this.error(node.line, `unknown type '${node.kind.ident}'`); + this.error(node.loc, `unknown type '${node.kind.ident}'`); } } @@ -216,14 +216,14 @@ export class Checker { const lengthTy = this.check(node.kind.length); if (!lengthTy.compatibleWith(Ty.Int)) { this.error( - node.kind.length.line, + node.kind.length.loc, `for array length, expected 'int', got '${lengthTy.pretty()}'`, ); this.fail(); } if (!node.kind.length.is("IntExpr")) { this.error( - node.kind.length.line, + node.kind.length.loc, `array length must be an 'int' expression`, ); this.fail(); @@ -254,11 +254,11 @@ export class Checker { : Ty.Void; if (!ty.compatibleWith(retTy)) { this.error( - node.line, + node.loc, `type '${ty.pretty()}' not compatible with return type '${retTy.pretty()}'`, ); this.info( - stmt.kind.retTy?.line ?? stmt.line, + stmt.kind.retTy?.loc ?? stmt.loc, `return type '${retTy}' defined here`, ); this.fail(); @@ -282,7 +282,7 @@ export class Checker { if (!callableTy) { this.error( - node.line, + node.loc, `type '${calleeTy.pretty()}' not callable`, ); this.fail(); @@ -293,12 +293,12 @@ export class Checker { const params = callableTy.kind.params; if (args.length !== params.length) { this.error( - node.line, + node.loc, `incorrect amount of arguments. got ${args.length} expected ${params.length}`, ); if (calleeTy.is("FnStmt")) { this.info( - calleeTy.kind.stmt.line, + calleeTy.kind.stmt.loc, "function defined here", ); } @@ -307,14 +307,14 @@ export class Checker { for (const i of args.keys()) { if (!args[i].compatibleWith(params[i])) { this.error( - node.kind.args[i].line, + node.kind.args[i].loc, `type '${args[i].pretty()}' not compatible with type '${ params[i].pretty() }', for argument ${i}`, ); if (calleeTy.is("FnStmt")) { this.info( - calleeTy.kind.stmt.kind.params[i].line, + calleeTy.kind.stmt.kind.params[i].loc, `parameter '${ calleeTy.kind.stmt.kind.params[i] .as("Param").kind.ident @@ -327,30 +327,30 @@ export class Checker { return callableTy.kind.retTy; } - private assertCompatible(left: Ty, right: Ty, line: number): void { + private assertCompatible(left: Ty, right: Ty, loc: Loc): void { if (!left.compatibleWith(right)) { this.error( - line, + loc, `type '${left.pretty()}' not compatible with type '${right.pretty()}'`, ); this.fail(); } } - private error(line: number, message: string) { + private error(loc: Loc, message: string) { printDiagnostics( this.filename, - line, + loc, "error", message, this.text, ); } - private info(line: number, message: string) { + private info(loc: Loc, message: string) { printDiagnostics( this.filename, - line, + loc, "info", message, this.text, diff --git a/src/front/parse.ts b/src/front/parse.ts index 7d52acb..b50f90d 100644 --- a/src/front/parse.ts +++ b/src/front/parse.ts @@ -11,7 +11,7 @@ export function parse( export class Parser { private toks: Tok[]; private idx = 0; - private currentLine = 1; + private currentLoc: Loc = { idx: 0, line: 1, col: 1 }; private prevTok: Tok | null = null; constructor( @@ -148,7 +148,7 @@ export class Parser { } } - parseRangeTail(loc: number, begin: ast.Node | null, tok: string): ast.Node { + parseRangeTail(loc: Loc, begin: ast.Node | null, tok: string): ast.Node { const limit: ast.RangeLimit = tok === ".." ? "Exclusive" : "Inclusive"; let end: ast.Node | null = null; if (![";", ",", ")", "]"].some((tok) => this.test(tok))) { @@ -319,7 +319,7 @@ export class Parser { } } - private mustEat(type: string, loc: number = this.loc()): Tok { + private mustEat(type: string, loc = this.loc()): Tok { const tok = this.current; if (tok.type !== type) { this.error( @@ -333,7 +333,7 @@ export class Parser { return tok; } - private error(message: string, loc: number): never { + private error(message: string, loc: Loc): never { printDiagnostics(this.filename, loc, "error", message, this.text); throw new Error(); Deno.exit(1); @@ -353,7 +353,7 @@ export class Parser { } this.idx += 1; if (!this.done) { - this.currentLine = this.current.line; + this.currentLoc = this.current.loc; } } @@ -361,8 +361,8 @@ export class Parser { return !this.done && this.current.type == type; } - private loc(): number { - return this.currentLine; + private loc(): Loc { + return this.currentLoc; } private get current(): Tok { @@ -374,8 +374,7 @@ export class Parser { } } -export type Tok = { type: string; value: string; line: number }; -export type Tok2 = { type: string; value: string; loc: Loc }; +export type Tok = { type: string; value: string; loc: Loc }; const keywordPattern = /^(?:(?:fn)|(?:return)|(?:let)|(?:if)|(?:else)|(?:while)|(?:break)|(?:or)|(?:and)|(?:not)|(?:mut))/; @@ -384,13 +383,13 @@ const operatorPattern2 = /((?:\->)|(?:==)|(?:!=)|(?:<=)|(?:>=)|(?:<<)|(?:>>)|(?:\.\*)|(?:\.\.)|(?:\.\.=)|[\n\(\)\{\}\[\]\,\.\;\:\!\=\<\>\&\^\|\+\-\*\/\%])/g; export function tokenize(text: string): Tok[] { - return new Lexer() + return new Lexer() .add(/[ \t\r\n]+/, (_) => null) .add(/\/\/[^\n]*/, (_) => null) .add(operatorPattern2, (loc, value) => ({ type: value, value, loc })) .add(/[a-zA-Z_][a-zA-Z0-9_]*/, (loc, value) => { const type = keywordPattern.test(value) ? value : "ident"; - return ({ type, value, loc }); + return { type, value, loc }; }) .add(/0|(?:[1-9][0-9]*)/, (loc, value) => { return { type: "int", value, loc }; @@ -398,21 +397,20 @@ export function tokenize(text: string): Tok[] { .add(/./, (loc, value) => { return null; }) - .lex(text) - .map(({ type, value, loc: { line } }) => ({ type, value, line })); + .lex(text); } -type LexRule = { +type LexRule = { pattern: RegExp; - action: LexAction; + action: LexAction; }; -type LexAction = (loc: Loc, match: string) => TokT | null; +type LexAction = (loc: Loc, match: string) => Tok | null; -class Lexer { - private rules: LexRule[] = []; +class Lexer { + private rules: LexRule[] = []; - add(pattern: RegExp, action: LexAction): this { + add(pattern: RegExp, action: LexAction): this { this.rules.push({ pattern: new RegExp(`^(?:${pattern.source})`), action, @@ -420,8 +418,8 @@ class Lexer { return this; } - lex(text: string): TokT[] { - const toks: TokT[] = []; + lex(text: string): Tok[] { + const toks: Tok[] = []; let idx = 0; let line = 1; let col = 1; diff --git a/src/front/resolve.ts b/src/front/resolve.ts index 6ec65e4..4895dd4 100644 --- a/src/front/resolve.ts +++ b/src/front/resolve.ts @@ -83,7 +83,7 @@ export function resolve( if (sym === null) { printDiagnostics( filename, - node.line, + node.loc, "error", `undefined symbol '${k.ident}'`, text,