diff --git a/compiler/src/bin/bolt.ts b/compiler/src/bin/bolt.ts index 83b2b2d33..8392f6146 100644 --- a/compiler/src/bin/bolt.ts +++ b/compiler/src/bin/bolt.ts @@ -15,7 +15,7 @@ import BoltToJS from "../passes/BoltToJS" import { stripExtension } from "../util" import { sync as which } from "which" import { spawnSync } from "child_process" -import { ConsoleDiagnostics, DiagnosticStore, TypeMismatchDiagnostic } from "../diagnostics" +import { ConsoleDiagnostics, DiagnosticKind, DiagnosticStore, TypeMismatchDiagnostic } from "../diagnostics" import { Syntax, SyntaxKind, TextFile, isExpression, visitEachChild } from "../cst" import { Analyser, Checker, parseSourceFile } from ".." import { typesEqual } from "../types" @@ -163,15 +163,24 @@ program.command('verify', { hidden: true }) const realPath = path.resolve(fileName); const text = fs.readFileSync(realPath, 'utf-8'); const file = new TextFile(fileName, text); + const sourceFile = parseSourceFile(file, diagnostics); if (!sourceFile) { process.exit(1); } + const analyser = new Analyser(); analyser.addSourceFile(sourceFile); const checker = new Checker(analyser, diagnostics); checker.check(sourceFile); + const realDiagnostics = new ConsoleDiagnostics(); + + let annotationTotalCount = 0; + let annotationErrorCount = 0; + let diagnosticTotalCount = diagnostics.size; + let diagnosticErrorCount = 0; + const visit = (node: Syntax) => { if (isExpression(node)) { for (const annotation of node.annotations) { @@ -180,13 +189,37 @@ program.command('verify', { hidden: true }) const expected = checker.getTypeOfNode(annotation.typeExpr); if (!typesEqual(actual, expected)) { realDiagnostics.add(new TypeMismatchDiagnostic(actual, expected, [ node ], [])); + annotationErrorCount++; } + annotationTotalCount++; } } } visitEachChild(node, visit); } visit(sourceFile); + + const uncaughtDiagnostics = new Set(diagnostics); + + for (const [line, comment] of file.comments) { + if (comment[0].kind === SyntaxKind.At && comment[1].kind === SyntaxKind.Identifier && comment[1].text === 'expect_diagnostic' && comment[2].kind === SyntaxKind.StringLiteral) { + for (const diagnostic of uncaughtDiagnostics) { + if (diagnostic.position && diagnostic.position.line === line+1 && DiagnosticKind[diagnostic.kind] === comment[2].contents) { + uncaughtDiagnostics.delete(diagnostic); + } + } + } + } + + for (const diagnostic of uncaughtDiagnostics) { + realDiagnostics.add(diagnostic); + } + + console.log(`${annotationTotalCount} type annotation(s) verified, ${annotationErrorCount} error(s).`); + console.log(`${diagnosticTotalCount} diagnostic(s) generated, ${uncaughtDiagnostics.size} unexpected.`); + if (realDiagnostics.hasError) { + process.exit(1); + } }); program.parse(); diff --git a/compiler/src/cst.ts b/compiler/src/cst.ts index 5540bc7bd..81cb3197b 100644 --- a/compiler/src/cst.ts +++ b/compiler/src/cst.ts @@ -66,6 +66,8 @@ export class TextRange { export class TextFile { + public comments = new Map(); + public constructor( public origPath: string, public text: string, diff --git a/compiler/src/diagnostics.ts b/compiler/src/diagnostics.ts index 7a7ad52e1..37fc887a6 100644 --- a/compiler/src/diagnostics.ts +++ b/compiler/src/diagnostics.ts @@ -36,7 +36,7 @@ const enum Level { Fatal, } -const enum DiagnosticKind { +export enum DiagnosticKind { UnexpectedChar, UnexpectedToken, KindMismatch, @@ -56,6 +56,8 @@ abstract class DiagnosticBase { public abstract level: Level; + public abstract position: TextPosition | undefined; + } export class UnexpectedCharDiagnostic extends DiagnosticBase { @@ -89,12 +91,16 @@ export class UnexpectedTokenDiagnostic extends DiagnosticBase { super(); } + public get position(): TextPosition { + return this.actual.getStartPosition(); + } + } export class TypeclassDeclaredTwiceDiagnostic extends DiagnosticBase { public readonly kind = DiagnosticKind.TypeclassDecaredTwice; - + public level = Level.Error; public constructor( @@ -102,7 +108,11 @@ export class TypeclassDeclaredTwiceDiagnostic extends DiagnosticBase { public origDecl: ClassDeclaration, ) { super(); - } + } + + public get position(): TextPosition { + return this.name.getStartPosition(); + } } @@ -120,6 +130,10 @@ export class TypeclassNotFoundDiagnostic extends DiagnosticBase { super(); } + public get position(): TextPosition | undefined { + return this.node?.getFirstToken().getStartPosition(); + } + } export class TypeclassNotImplementedDiagnostic extends DiagnosticBase { @@ -136,6 +150,10 @@ export class TypeclassNotImplementedDiagnostic extends DiagnosticBase { super(); } + public get position(): TextPosition | undefined { + return this.node?.getFirstToken().getStartPosition(); + } + } export class BindingNotFoundDiagnostic extends DiagnosticBase { @@ -152,6 +170,10 @@ export class BindingNotFoundDiagnostic extends DiagnosticBase { super(); } + public get position(): TextPosition { + return this.node.getFirstToken().getStartPosition(); + } + } export class TypeMismatchDiagnostic extends DiagnosticBase { @@ -169,6 +191,10 @@ export class TypeMismatchDiagnostic extends DiagnosticBase { super(); } + public get position(): TextPosition | undefined { + return this.trace[0]?.getFirstToken().getStartPosition(); + } + } export class TupleIndexOutOfRangeDiagnostic extends DiagnosticBase { @@ -184,6 +210,10 @@ export class TupleIndexOutOfRangeDiagnostic extends DiagnosticBase { super(); } + public get position(): TextPosition | undefined { + return undefined; + } + } export class FieldNotFoundDiagnostic extends DiagnosticBase { @@ -201,6 +231,10 @@ export class FieldNotFoundDiagnostic extends DiagnosticBase { super(); } + public get position(): TextPosition | undefined { + return this.cause?.getFirstToken().getStartPosition(); + } + } export class KindMismatchDiagnostic extends DiagnosticBase { @@ -217,6 +251,10 @@ export class KindMismatchDiagnostic extends DiagnosticBase { super(); } + public get position(): TextPosition | undefined { + return this.origin?.getFirstToken().getStartPosition(); + } + } export class ModuleNotFoundDiagnostic extends DiagnosticBase { @@ -232,6 +270,10 @@ export class ModuleNotFoundDiagnostic extends DiagnosticBase { super(); } + public get position(): TextPosition | undefined { + return this.node.getFirstToken().getStartPosition(); + } + } export type Diagnostic @@ -256,10 +298,14 @@ export interface Diagnostics { export class DiagnosticStore implements Diagnostics { private storage: Diagnostic[] = []; - + public hasError = false; public hasFatal = false; + public get size(): number { + return this.storage.length; + } + public add(diagnostic: Diagnostic): void { this.storage.push(diagnostic); if (diagnostic.level >= Level.Error) { @@ -270,8 +316,8 @@ export class DiagnosticStore implements Diagnostics { } } - public getDiagnostics(): Iterable { - return this.storage; + public [Symbol.iterator](): IterableIterator { + return this.storage[Symbol.iterator](); } } diff --git a/compiler/src/index.ts b/compiler/src/index.ts index 8d6c4d47b..f5a80e023 100644 --- a/compiler/src/index.ts +++ b/compiler/src/index.ts @@ -4,7 +4,7 @@ import { ParseError, Parser } from "./parser"; import { Punctuator, ScanError, Scanner } from "./scanner"; export function parseSourceFile(file: TextFile, diagnostics: Diagnostics): SourceFile | null { - const scanner = new Scanner(file.text, 0, diagnostics, file); + const scanner = new Scanner(file.text, diagnostics, file); const punctuated = new Punctuator(scanner); const parser = new Parser(file, punctuated); let sourceFile; diff --git a/compiler/src/scanner.ts b/compiler/src/scanner.ts index 50ce14dbe..ffd7319ef 100644 --- a/compiler/src/scanner.ts +++ b/compiler/src/scanner.ts @@ -97,14 +97,13 @@ export class ScanError extends Error { export class Scanner extends BufferedStream { - private currLine = 1; - private currColumn = 1; + private textOffset = 0; public constructor( public text: string, - public textOffset: number = 0, public diagnostics: Diagnostics, private file: TextFile, + public currPos: TextPosition = new TextPosition(0, 1, 1), ) { super(); } @@ -115,14 +114,18 @@ export class Scanner extends BufferedStream { } private getChar(): string { - const ch = this.textOffset < this.text.length - ? this.text[this.textOffset++] - : EOF - if (ch === '\n') { - this.currLine++; - this.currColumn = 1; + let ch; + if (this.textOffset < this.text.length) { + ch = this.text[this.textOffset++]; + this.currPos.offset++; } else { - this.currColumn++; + ch = EOF; + } + if (ch === '\n') { + this.currPos.line++; + this.currPos.column = 1; + } else { + this.currPos.column++; } return ch; } @@ -141,11 +144,7 @@ export class Scanner extends BufferedStream { } private getCurrentPosition(): TextPosition { - return new TextPosition( - this.textOffset, - this.currLine, - this.currColumn - ); + return this.currPos.clone(); } public read(): Token { @@ -162,13 +161,18 @@ export class Scanner extends BufferedStream { continue; } if (c0 === '#') { + const line = this.currPos.line; this.getChar(); + let text = ''; for (;;) { const c1 = this.getChar(); if (c1 === '\n' || c1 === EOF) { break; } + text += c1; } + const scanner = new Scanner(text, this.diagnostics, this.file, this.getCurrentPosition()); + this.file.comments.set(line, scanner.getAll()); continue; } @@ -406,6 +410,18 @@ export class Scanner extends BufferedStream { } + public getAll(): Token[] { + const tokens = []; + for (;;) { + const t0 = this.get(); + if (t0.kind === SyntaxKind.EndOfFile) { + break; + } + tokens.push(t0); + } + return tokens; + } + } const enum FrameType {