diff --git a/compiler/src/bin/bolt.ts b/compiler/src/bin/bolt.ts index 24117892e..068cb4f01 100644 --- a/compiler/src/bin/bolt.ts +++ b/compiler/src/bin/bolt.ts @@ -15,6 +15,10 @@ 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 { Syntax, SyntaxKind, TextFile, isExpression, visitEachChild } from "../cst" +import { Analyser, Checker, parseSourceFile } from ".." +import { typesEqual } from "../types" function debug(value: any) { console.error(util.inspect(value, { colors: true, depth: Infinity })); @@ -73,15 +77,17 @@ program .version('0.0.1') .option('-C, --work-dir', 'Act as if run from this directory', '.'); -program.command('build', 'Build a set of Bolt sources') +program.command('build', { isDefault: true }) + .description('Build a set of Bolt sources') .argument('', 'Path to the Bolt program to compile') + .option('-C, --work-dir', 'Act as if run from this directory', '.') .option('--no-typecheck', 'Skip type-checking') .option('--no-emit', 'Do not output compiled files') .option('-t, --target ', 'What to compile to', 'c') - .action((file, opts) => { + .action((fileName, opts) => { const cwd = opts.workDir; - const filename = path.resolve(cwd, file); + const filePath = path.resolve(cwd, fileName); const shouldTypecheck = opts.typecheck; const shouldEmit = opts.emit; @@ -101,7 +107,7 @@ program.command('build', 'Build a set of Bolt sources') process.exit(1); } - const program = new Program([ filename ]); + const program = new Program([ filePath ]); if (program.diagnostics.hasError) { process.exit(1); } @@ -148,5 +154,39 @@ program.command('build', 'Build a set of Bolt sources') }); +program.command('verify', { hidden: true }) + .description('Run verification tests') + .argument('', 'File with verification source') + .action((fileName, _opts) => { + + const diagnostics = new DiagnosticStore(); + 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(); + const checker = new Checker(analyser, diagnostics); + checker.check(sourceFile); + const realDiagnostics = new ConsoleDiagnostics(); + const visit = (node: Syntax) => { + if (isExpression(node)) { + for (const annotation of node.annotations) { + if (annotation.kind === SyntaxKind.TypeAnnotation) { + const actual = checker.getTypeOfNode(node); + const expected = checker.getTypeOfNode(annotation.typeExpr); + if (!typesEqual(actual, expected)) { + realDiagnostics.add(new TypeMismatchDiagnostic(actual, expected, [ node ], [])); + } + } + } + } + visitEachChild(node, visit); + } + visit(sourceFile); + }); + program.parse(); diff --git a/compiler/src/checker.ts b/compiler/src/checker.ts index b940c55b9..116d801f1 100644 --- a/compiler/src/checker.ts +++ b/compiler/src/checker.ts @@ -26,11 +26,12 @@ import { TypeclassDeclaredTwiceDiagnostic, FieldNotFoundDiagnostic, TypeMismatchDiagnostic, + TupleIndexOutOfRangeDiagnostic, } from "./diagnostics"; import { assert, assertNever, isEmpty, MultiMap, toStringTag, InspectFn, implementationLimitation } from "./util"; import { Analyser } from "./analysis"; import { InspectOptions } from "util"; -import { TypeKind, TApp, TArrow, TCon, TField, TNil, TNominal, TPresent, TTuple, TUniVar, TVSet, TVSub, Type, TypeBase, TAbsent, TRigidVar, TVar } from "./types"; +import { TypeKind, TApp, TArrow, TCon, TField, TNil, TNominal, TPresent, TTuple, TUniVar, TVSet, TVSub, Type, TypeBase, TAbsent, TRigidVar, TVar, TTupleIndex } from "./types"; import { CClass, CEmpty, CEqual, CMany, Constraint, ConstraintKind, ConstraintSet } from "./constraints"; // export class Qual { @@ -691,7 +692,10 @@ export class Checker { { const tupleType = this.simplifyType(type.tupleType); if (tupleType.kind === TypeKind.Tuple) { - // TODO check bounds and add diagnostic + if (type.index >= tupleType.elementTypes.length) { + this.diagnostics.add(new TupleIndexOutOfRangeDiagnostic(type.index, tupleType)); + return type; + } const newType = tupleType.elementTypes[type.index]; type.set(newType); return newType; @@ -1295,10 +1299,19 @@ export class Checker { public inferExpression(node: Expression): Type { + for (const annotation of node.annotations) { + if (annotation.kind === SyntaxKind.TypeAnnotation) { + this.inferTypeExpression(annotation.typeExpr, false, false); + } + } + + let type: Type; + switch (node.kind) { case SyntaxKind.NestedExpression: - return this.inferExpression(node.expression); + type = this.inferExpression(node.expression); + break; case SyntaxKind.MatchExpression: { @@ -1308,7 +1321,7 @@ export class Checker { } else { exprType = this.createTypeVar(); } - let resultType: Type = this.createTypeVar(); + type = this.createTypeVar(); for (const arm of node.arms) { const context = this.getContext(); const newEnv = new TypeEnv(context.env); @@ -1330,7 +1343,7 @@ export class Checker { ); this.addConstraint( new CEqual( - resultType, + type, this.inferExpression(arm.expression), arm.expression ) @@ -1338,13 +1351,14 @@ export class Checker { this.popContext(newContext); } if (node.expression === null) { - resultType = new TArrow(exprType, resultType); + type = new TArrow(exprType, type); } - return resultType; + break; } case SyntaxKind.TupleExpression: - return new TTuple(node.elements.map(el => this.inferExpression(el)), node); + type = new TTuple(node.elements.map(el => this.inferExpression(el)), node); + break; case SyntaxKind.ReferenceExpression: { @@ -1360,36 +1374,48 @@ export class Checker { } const scheme = this.lookup(node, Symkind.Var); if (scheme === null) { - //this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name)); - return this.createTypeVar(); + //this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name)); + type = this.createTypeVar(); + break; } - const type = this.instantiate(scheme, node); + type = this.instantiate(scheme, node); type.node = node; - return type; + break; } case SyntaxKind.MemberExpression: { - let type = this.inferExpression(node.expression); + type = this.inferExpression(node.expression); for (const [_dot, name] of node.path) { - const newFieldType = this.createTypeVar(name); - const newRestType = this.createTypeVar(); - this.addConstraint( - new CEqual( - type, - new TField(name.text, new TPresent(newFieldType), newRestType, name), - node, - ) - ); - type = newFieldType; + switch (name.kind) { + case SyntaxKind.Identifier: + { + const newFieldType = this.createTypeVar(name); + const newRestType = this.createTypeVar(); + this.addConstraint( + new CEqual( + type, + new TField(name.text, new TPresent(newFieldType), newRestType, name), + node, + ) + ); + type = newFieldType; + break; + } + case SyntaxKind.Integer: + type = new TTupleIndex(type, Number(name.value)); + break; + default: + assertNever(name); + } } - return type; + break; } case SyntaxKind.CallExpression: { const opType = this.inferExpression(node.func); - const retType = this.createTypeVar(node); + type = this.createTypeVar(node); const paramTypes = []; for (const arg of node.args) { paramTypes.push(this.inferExpression(arg)); @@ -1397,32 +1423,31 @@ export class Checker { this.addConstraint( new CEqual( opType, - TArrow.build(paramTypes, retType), + TArrow.build(paramTypes, type), node ) ); - return retType; + break; } case SyntaxKind.ConstantExpression: { - let ty; switch (node.token.kind) { case SyntaxKind.StringLiteral: - ty = this.getStringType(); + type = this.getStringType(); break; case SyntaxKind.Integer: - ty = this.getIntType(); + type = this.getIntType(); break; } - ty = ty.shallowClone(); - ty.node = node; - return ty; + type = type.shallowClone(); + type.node = node; + break; } case SyntaxKind.StructExpression: { - let type: Type = new TNil(node); + type = new TNil(node); for (const member of node.members) { switch (member.kind) { case SyntaxKind.StructExpressionField: @@ -1447,7 +1472,9 @@ export class Checker { throw new Error(`Unexpected ${member}`); } } - return TField.sort(type); + // FIXME build a type rather than sorting it + type = TField.sort(type); + break; } case SyntaxKind.InfixExpression: @@ -1458,17 +1485,17 @@ export class Checker { return this.createTypeVar(); } const opType = this.instantiate(scheme, node.operator); - const retType = this.createTypeVar(); const leftType = this.inferExpression(node.left); const rightType = this.inferExpression(node.right); + type = this.createTypeVar(); this.addConstraint( new CEqual( - new TArrow(leftType, new TArrow(rightType, retType)), + new TArrow(leftType, new TArrow(rightType, type)), opType, node, ), ); - return retType; + break; } default: @@ -1476,13 +1503,17 @@ export class Checker { } + node.inferredType = type; + + return type; + } - public inferTypeExpression(node: TypeExpression, introduceTypeVars = false): Type { + public inferTypeExpression(node: TypeExpression, introduceTypeVars = false, checkKind = true): Type { let type; - if (!node.inferredKind) { + if (checkKind && !node.inferredKind) { type = this.createTypeVar(); @@ -1496,16 +1527,17 @@ export class Checker { if (scheme === null) { // this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name)); type = this.createTypeVar(); - } else { - type = this.instantiate(scheme, node.name); - // It is not guaranteed that `type` is copied during instantiation, - // so the following check ensures that we really are holding a copy - // that we can mutate. - if (type === scheme.type) { - type = type.shallowClone(); - } - type.node = node; + break; } + type = this.instantiate(scheme, node.name); + // It is not guaranteed that `type` is copied during instantiation, + // so the following check ensures that we really are holding a copy + // that we can mutate. + if (type === scheme.type) { + type = type.shallowClone(); + } + // Mutate the type + type.node = node; break; } @@ -2281,6 +2313,11 @@ export class Checker { return this.classDecls.get(name) ?? null; } + public getTypeOfNode(node: Syntax): Type { + assert(node.inferredType !== undefined); + return this.simplifyType(node.inferredType); + } + // private *findInstanceContext(type: TCon, clazz: ClassDeclaration): Iterable { // for (const instance of clazz.getInstances()) { // assert(instance.types.length === 1); diff --git a/compiler/src/cst.ts b/compiler/src/cst.ts index 079b79d41..5540bc7bd 100644 --- a/compiler/src/cst.ts +++ b/compiler/src/cst.ts @@ -97,6 +97,7 @@ export const enum SyntaxKind { VBar, Dot, DotDot, + At, Comma, Colon, Equals, @@ -125,6 +126,10 @@ export const enum SyntaxKind { BlockStart, EndOfFile, + // Annotations + TypeAnnotation, + ExpressionAnnotation, + // Type expressions ReferenceTypeExpression, ArrowTypeExpression, @@ -225,6 +230,7 @@ export type Syntax | StructDeclarationField | EnumDeclarationElement | TypeAssert + | Annotation | Declaration | Statement | Expression @@ -824,6 +830,20 @@ export class Dot extends TokenBase { } +export class At extends TokenBase { + + public readonly kind = SyntaxKind.At; + + public get text(): string { + return '@'; + } + + public clone(): At { + return new At(this.startPos); + } + +} + export class Comma extends TokenBase { public readonly kind = SyntaxKind.Comma; @@ -1190,6 +1210,7 @@ export type Token | CustomOperator | Integer | StringLiteral + | At | Comma | Dot | DotDot @@ -1777,11 +1798,47 @@ export type Pattern | DisjunctivePattern | LiteralPattern +export class TypeAnnotation extends SyntaxBase { + + public readonly kind = SyntaxKind.TypeAnnotation; + + public constructor( + public at: At, + public colon: Colon, + public typeExpr: TypeExpression, + ) { + super(); + } + + public clone(): TypeAnnotation { + return new TypeAnnotation( + this.at.clone(), + this.colon.clone(), + this.typeExpr.clone() + ); + } + + public getFirstToken(): Token { + return this.at; + } + + public getLastToken(): Token { + return this.typeExpr.getLastToken(); + } + +} + +export type Annotation + = TypeAnnotation + +export type Annotations = Annotation[]; + export class TupleExpression extends SyntaxBase { public readonly kind = SyntaxKind.TupleExpression; public constructor( + public annotations: Annotations, public lparen: LParen, public elements: Expression[], public rparen: RParen, @@ -1791,6 +1848,7 @@ export class TupleExpression extends SyntaxBase { public clone(): TupleExpression { return new TupleExpression( + this.annotations.map(a => a.clone()), this.lparen.clone(), this.elements.map(element => element.clone()), this.rparen.clone() @@ -1812,6 +1870,7 @@ export class NestedExpression extends SyntaxBase { public readonly kind = SyntaxKind.NestedExpression; public constructor( + public annotations: Annotations, public lparen: LParen, public expression: Expression, public rparen: RParen, @@ -1821,6 +1880,7 @@ export class NestedExpression extends SyntaxBase { public clone(): NestedExpression { return new NestedExpression( + this.annotations.map(a => a.clone()), this.lparen.clone(), this.expression.clone(), this.rparen.clone(), @@ -1842,13 +1902,17 @@ export class ConstantExpression extends SyntaxBase { public readonly kind = SyntaxKind.ConstantExpression; public constructor( + public annotations: Annotations, public token: Integer | StringLiteral, ) { super(); } public clone(): ConstantExpression { - return new ConstantExpression( this.token.clone() ); + return new ConstantExpression( + this.annotations.map(a => a.clone()), + this.token.clone() + ); } public getFirstToken(): Token { @@ -1866,6 +1930,7 @@ export class CallExpression extends SyntaxBase { public readonly kind = SyntaxKind.CallExpression; public constructor( + public annotations: Annotations, public func: Expression, public args: Expression[], ) { @@ -1874,6 +1939,7 @@ export class CallExpression extends SyntaxBase { public clone(): CallExpression { return new CallExpression( + this.annotations.map(a => a.clone()), this.func.clone(), this.args.map(arg => arg.clone()), ); @@ -1955,6 +2021,7 @@ export class StructExpression extends SyntaxBase { public readonly kind = SyntaxKind.StructExpression; public constructor( + public annotations: Annotations, public lbrace: LBrace, public members: StructExpressionElement[], public rbrace: RBrace, @@ -1964,6 +2031,7 @@ export class StructExpression extends SyntaxBase { public clone(): StructExpression { return new StructExpression( + this.annotations.map(a => a.clone()), this.lbrace.clone(), this.members.map(member => member.clone()), this.rbrace.clone(), @@ -1980,6 +2048,38 @@ export class StructExpression extends SyntaxBase { } +export class FunctionExpression extends SyntaxBase { + + public readonly kind = SyntaxKind.FunctionExpression; + + public constructor( + public annotations: Annotations, + public backslash: Backslash, + public params: Param[], + public body: Body, + ) { + super(); + } + + public clone(): FunctionExpression { + return new FunctionExpression( + this.annotations.map(a => a.clone()), + this.backslash.clone(), + this.params.map(param => param.clone()), + this.body.clone(), + ); + } + + public getFirstToken(): Token { + return this.backslash; + } + + public getLastToken(): Token { + return this.body.getLastToken(); + } + +} + export class MatchArm extends SyntaxBase { public readonly kind = SyntaxKind.MatchArm; @@ -2010,42 +2110,13 @@ export class MatchArm extends SyntaxBase { } -export class FunctionExpression extends SyntaxBase { - - public readonly kind = SyntaxKind.FunctionExpression; - - public constructor( - public backslash: Backslash, - public params: Param[], - public body: Body, - ) { - super(); - } - - public clone(): FunctionExpression { - return new FunctionExpression( - this.backslash.clone(), - this.params.map(param => param.clone()), - this.body.clone(), - ); - } - - public getFirstToken(): Token { - return this.backslash; - } - - public getLastToken(): Token { - return this.body.getLastToken(); - } - - -} export class MatchExpression extends SyntaxBase { public readonly kind = SyntaxKind.MatchExpression; public constructor( + public annotations: Annotations, public matchKeyword: MatchKeyword, public expression: Expression | null, public arms: MatchArm[], @@ -2055,6 +2126,7 @@ export class MatchExpression extends SyntaxBase { public clone(): MatchExpression { return new MatchExpression( + this.annotations.map(a => a.clone()), this.matchKeyword.clone(), this.expression !== null ? this.expression.clone() : null, this.arms.map(arm => arm.clone()), @@ -2062,6 +2134,9 @@ export class MatchExpression extends SyntaxBase { } public getFirstToken(): Token { + if (this.annotations.length > 0) { + return this.annotations[0].getFirstToken(); + } return this.matchKeyword; } @@ -2082,6 +2157,7 @@ export class ReferenceExpression extends SyntaxBase { public readonly kind = SyntaxKind.ReferenceExpression; public constructor( + public annotations: Annotations, public modulePath: Array<[IdentifierAlt, Dot]>, public name: Identifier | IdentifierAlt, ) { @@ -2090,6 +2166,7 @@ export class ReferenceExpression extends SyntaxBase { public clone(): ReferenceExpression { return new ReferenceExpression( + this.annotations.map(a => a.clone()), this.modulePath.map(([name, dot]) => [name.clone(), dot.clone()]), this.name.clone(), ); @@ -2113,14 +2190,16 @@ export class MemberExpression extends SyntaxBase { public readonly kind = SyntaxKind.MemberExpression; public constructor( + public annotations: Annotations, public expression: Expression, - public path: [Dot, Identifier][], + public path: [Dot, Identifier | Integer][], ) { super(); } public clone(): MemberExpression { return new MemberExpression( + this.annotations.map(a => a.clone()), this.expression.clone(), this.path.map(([dot, name]) => [dot.clone(), name.clone()]), ); @@ -2141,6 +2220,7 @@ export class PrefixExpression extends SyntaxBase { public readonly kind = SyntaxKind.PrefixExpression; public constructor( + public annotations: Annotations, public operator: ExprOperator, public expression: Expression, ) { @@ -2149,6 +2229,7 @@ export class PrefixExpression extends SyntaxBase { public clone(): PrefixExpression { return new PrefixExpression( + this.annotations.map(a => a.clone()), this.operator.clone(), this.expression.clone(), ); @@ -2169,6 +2250,7 @@ export class PostfixExpression extends SyntaxBase { public readonly kind = SyntaxKind.PostfixExpression; public constructor( + public annotations: Annotations, public expression: Expression, public operator: ExprOperator, ) { @@ -2177,6 +2259,7 @@ export class PostfixExpression extends SyntaxBase { public clone(): PostfixExpression { return new PostfixExpression( + this.annotations.map(a => a.clone()), this.expression.clone(), this.operator.clone(), ); @@ -2197,6 +2280,7 @@ export class InfixExpression extends SyntaxBase { public readonly kind = SyntaxKind.InfixExpression; public constructor( + public annotations: Annotations, public left: Expression, public operator: ExprOperator, public right: Expression, @@ -2206,6 +2290,7 @@ export class InfixExpression extends SyntaxBase { public clone(): InfixExpression { return new InfixExpression( + this.annotations.map(a => a.clone()), this.left.clone(), this.operator.clone(), this.right.clone(), @@ -2222,6 +2307,26 @@ export class InfixExpression extends SyntaxBase { } +export function isExpression(node: Syntax): node is Expression { + switch (node.kind) { + case SyntaxKind.MemberExpression: + case SyntaxKind.CallExpression: + case SyntaxKind.StructExpression: + case SyntaxKind.ReferenceExpression: + case SyntaxKind.ConstantExpression: + case SyntaxKind.TupleExpression: + case SyntaxKind.MatchExpression: + case SyntaxKind.NestedExpression: + case SyntaxKind.PrefixExpression: + case SyntaxKind.InfixExpression: + case SyntaxKind.PostfixExpression: + case SyntaxKind.FunctionExpression: + return true; + default: + return false; + } +} + export type Expression = MemberExpression | CallExpression @@ -3233,7 +3338,7 @@ export function isToken(value: any): value is Token { && value instanceof TokenBase; } -export function vistEachChild(node: T, proc: (node: Syntax) => Syntax | undefined): Syntax { +export function visitEachChild(node: T, proc: (node: Syntax) => Syntax | void): Syntax { const newArgs = []; let changed = false; diff --git a/compiler/src/diagnostics.ts b/compiler/src/diagnostics.ts index 08b29fde9..7a7ad52e1 100644 --- a/compiler/src/diagnostics.ts +++ b/compiler/src/diagnostics.ts @@ -1,6 +1,6 @@ import { Kind, KindType } from "./checker"; -import { type Type, TypeKind } from "./types" +import { type Type, TypeKind, TTuple } from "./types" import { ClassConstraint, ClassDeclaration, IdentifierAlt, InstanceDeclaration, Syntax, SyntaxKind, TextFile, TextPosition, TextRange, Token } from "./cst"; import { assertNever, countDigits, IndentWriter } from "./util"; @@ -41,6 +41,7 @@ const enum DiagnosticKind { UnexpectedToken, KindMismatch, TypeMismatch, + TupleIndexOutOfRange, TypeclassNotFound, TypeclassDecaredTwice, TypeclassNotImplemented, @@ -170,6 +171,21 @@ export class TypeMismatchDiagnostic extends DiagnosticBase { } +export class TupleIndexOutOfRangeDiagnostic extends DiagnosticBase { + + public readonly kind = DiagnosticKind.TupleIndexOutOfRange; + + public level = Level.Error; + + public constructor( + public index: number, + public tupleType: TTuple, + ) { + super(); + } + +} + export class FieldNotFoundDiagnostic extends DiagnosticBase { public readonly kind = DiagnosticKind.FieldNotFound; @@ -225,6 +241,7 @@ export type Diagnostic | TypeclassNotImplementedDiagnostic | BindingNotFoundDiagnostic | TypeMismatchDiagnostic + | TupleIndexOutOfRangeDiagnostic | UnexpectedTokenDiagnostic | FieldNotFoundDiagnostic | KindMismatchDiagnostic @@ -510,6 +527,8 @@ export function describeType(type: Type): string { } return out + ')'; } + case TypeKind.TupleIndex: + return describeType(type.tupleType) + '.' + type.index; case TypeKind.Nominal: { return type.decl.name.text; diff --git a/compiler/src/parser.ts b/compiler/src/parser.ts index 1ebaced65..677406e23 100644 --- a/compiler/src/parser.ts +++ b/compiler/src/parser.ts @@ -68,6 +68,11 @@ import { AssignStatement, ForallTypeExpression, TypeExpressionWithConstraints, + Annotation, + TypeAnnotation, + Annotations, + ExprOperator, + Integer, } from "./cst" import { Stream } from "./util"; @@ -322,16 +327,55 @@ export class Parser { return new ArrowTypeExpression(paramTypes, returnType); } - public parseConstantExpression(): ConstantExpression { + private parseAnnotations(inline = true): Annotation[] { + const annotations = []; + for (;;) { + const t0 = this.tokens.peek(); + if (t0.kind !== SyntaxKind.At) { + break; + } + this.tokens.get(); + const t1 = this.tokens.peek(); + if (t1.kind === SyntaxKind.Colon) { + this.tokens.get(); + let typeExpr; + if (inline) { + typeExpr = this.parsePrimitiveTypeExpression(); + } else { + typeExpr = this.parseTypeExpression(); + this.expectToken(SyntaxKind.LineFoldEnd); + } + annotations.push(new TypeAnnotation(t0, t1, typeExpr)); + continue; + } + let expr; + if (inline) { + expr = this.parsePrimitiveExpression(); + } else { + expr = this.parseExpression(); + this.expectToken(SyntaxKind.LineFoldEnd); + } + annotations.push(new ExprAnnotation(t0, expr)); + } + return annotations; + } + + public parseConstantExpression(annotations?: Annotation[]): ConstantExpression { + if (annotations === undefined) { + annotations = this.parseAnnotations(); + } const token = this.getToken() if (token.kind !== SyntaxKind.StringLiteral && token.kind !== SyntaxKind.Integer) { this.raiseParseError(token, [ SyntaxKind.StringLiteral, SyntaxKind.Integer ]) } - return new ConstantExpression(token); + return new ConstantExpression(annotations, token); } - public parseReferenceExpression(): ReferenceExpression { + public parseReferenceExpression(annotations?: Annotation[]): ReferenceExpression { + if (annotations === undefined) { + annotations = this.parseAnnotations(); + } const modulePath: Array<[IdentifierAlt, Dot]> = []; for (;;) { const t0 = this.peekToken(1); @@ -347,10 +391,13 @@ export class Parser { if (name.kind !== SyntaxKind.Identifier && name.kind !== SyntaxKind.IdentifierAlt) { this.raiseParseError(name, [ SyntaxKind.Identifier, SyntaxKind.IdentifierAlt ]); } - return new ReferenceExpression(modulePath, name); + return new ReferenceExpression(annotations, modulePath, name); } - private parseExpressionWithParens(): Expression { + private parseExpressionWithParens(annotations?: Annotation[]): Expression { + if (annotations === undefined) { + annotations = this.parseAnnotations(); + } const elements = []; const lparen = this.expectToken(SyntaxKind.LParen) let rparen; @@ -374,46 +421,52 @@ export class Parser { } } if (elements.length === 1) { - return new NestedExpression(lparen, elements[0], rparen); + return new NestedExpression(annotations, lparen, elements[0], rparen); } - return new TupleExpression(lparen, elements, rparen); + return new TupleExpression(annotations, lparen, elements, rparen); + } + + public parseMatchExpression(annotations?: Annotation[]): MatchExpression { + if (annotations === undefined) { + annotations = this.parseAnnotations(); + } + const t0 = this.expectToken(SyntaxKind.MatchKeyword); + let expression = null + const t1 = this.peekToken(); + if (t1.kind !== SyntaxKind.BlockStart) { + expression = this.parseExpression(); + } + this.expectToken(SyntaxKind.BlockStart); + const arms = []; + for (;;) { + const t2 = this.peekToken(); + if (t2.kind === SyntaxKind.BlockEnd) { + this.getToken(); + break; + } + const pattern = this.parsePattern(); + const rarrowAlt = this.expectToken(SyntaxKind.RArrowAlt); + const expression = this.parseExpression(); + arms.push(new MatchArm(pattern, rarrowAlt, expression)); + this.expectToken(SyntaxKind.LineFoldEnd); + } + return new MatchExpression(annotations, t0, expression, arms); } private parsePrimitiveExpression(): Expression { + const annotations = this.parseAnnotations(); const t0 = this.peekToken(); switch (t0.kind) { case SyntaxKind.LParen: - return this.parseExpressionWithParens(); + return this.parseExpressionWithParens(annotations); case SyntaxKind.Identifier: case SyntaxKind.IdentifierAlt: - return this.parseReferenceExpression(); + return this.parseReferenceExpression(annotations); case SyntaxKind.Integer: case SyntaxKind.StringLiteral: - return this.parseConstantExpression(); + return this.parseConstantExpression(annotations); case SyntaxKind.MatchKeyword: - { - this.getToken(); - let expression = null - const t1 = this.peekToken(); - if (t1.kind !== SyntaxKind.BlockStart) { - expression = this.parseExpression(); - } - this.expectToken(SyntaxKind.BlockStart); - const arms = []; - for (;;) { - const t2 = this.peekToken(); - if (t2.kind === SyntaxKind.BlockEnd) { - this.getToken(); - break; - } - const pattern = this.parsePattern(); - const rarrowAlt = this.expectToken(SyntaxKind.RArrowAlt); - const expression = this.parseExpression(); - arms.push(new MatchArm(pattern, rarrowAlt, expression)); - this.expectToken(SyntaxKind.LineFoldEnd); - } - return new MatchExpression(t0, expression, arms); - } + return this.parseMatchExpression(annotations); case SyntaxKind.LBrace: { this.getToken(); @@ -452,7 +505,7 @@ export class Parser { break; } } - return new StructExpression(t0, fields, rbrace); + return new StructExpression(annotations, t0, fields, rbrace); } default: this.raiseParseError(t0, [ @@ -466,20 +519,25 @@ export class Parser { private tryParseMemberExpression(): Expression { const expression = this.parsePrimitiveExpression(); - const path: Array<[Dot, Identifier]> = []; + const path: Array<[Dot, Identifier | Integer]> = []; for (;;) { const t1 = this.peekToken(); if (t1.kind !== SyntaxKind.Dot) { break; } this.getToken(); - const name = this.expectToken(SyntaxKind.Identifier); - path.push([t1, name]); + const t2 = this.getToken(); + if (t2.kind !== SyntaxKind.Identifier && t2.kind !== SyntaxKind.Integer) { + this.raiseParseError(t2, [ SyntaxKind.Identifier, SyntaxKind.Integer ]); + } + path.push([t1, t2]); } if (path.length === 0) { return expression; } - return new MemberExpression(expression, path); + const annotations = expression.annotations; + expression.annotations = []; + return new MemberExpression(annotations, expression, path); } private tryParseCallExpression(): Expression { @@ -501,13 +559,16 @@ export class Parser { if (args.length === 0) { return func } - return new CallExpression(func, args); + const annotations = func.annotations; + func.annotations = []; + return new CallExpression(annotations, func, args); } private parseUnaryExpression(): Expression { let result = this.tryParseCallExpression() - const prefixes = []; + const prefixes: Array<[Annotations, ExprOperator]> = []; for (;;) { + const annotations = this.parseAnnotations(); const t0 = this.peekToken(); if (!isExprOperator(t0)) { break; @@ -515,12 +576,12 @@ export class Parser { if (!this.prefixExprOperators.has(t0.text)) { break; } - prefixes.push(t0); + prefixes.push([annotations, t0]); this.getToken() } for (let i = prefixes.length-1; i >= 0; i--) { - const operator = prefixes[i]; - result = new PrefixExpression(operator, result); + const [annotations, operator] = prefixes[i]; + result = new PrefixExpression(annotations, operator, result); } return result; } @@ -550,7 +611,9 @@ export class Parser { } rhs = this.parseBinaryOperatorAfterExpr(rhs, info0.precedence); } - lhs = new InfixExpression(lhs, t0, rhs); + const annotations = lhs.annotations; + lhs.annotations = []; + lhs = new InfixExpression(annotations, lhs, t0, rhs); } return lhs; } diff --git a/compiler/src/passes/TypeclassDictPass.ts b/compiler/src/passes/TypeclassDictPass.ts index b704456be..d10c355ad 100644 --- a/compiler/src/passes/TypeclassDictPass.ts +++ b/compiler/src/passes/TypeclassDictPass.ts @@ -64,6 +64,7 @@ export class TypeclassDictPassing implements Pass { new ExprBody( new Equals(), new StructExpression( + [], new LBrace(), node.elements.map(element => { assert(element.kind === SyntaxKind.LetDeclaration); @@ -71,7 +72,7 @@ export class TypeclassDictPassing implements Pass { return new StructExpressionField( new Identifier(null, element.pattern.name.text), new Equals(), - new FunctionExpression(new Backslash(), element.params, element.body!) + new FunctionExpression([], new Backslash(), element.params, element.body!) ); }), new RBrace(), diff --git a/compiler/src/scanner.ts b/compiler/src/scanner.ts index 0d2e172bc..50ce14dbe 100644 --- a/compiler/src/scanner.ts +++ b/compiler/src/scanner.ts @@ -46,6 +46,7 @@ import { InstanceKeyword, Backslash, ForallKeyword, + At, } from "./cst" import { Diagnostics } from "./diagnostics" import { Stream, BufferedStream, assert } from "./util"; @@ -221,6 +222,7 @@ export class Scanner extends BufferedStream { return new EndOfFile(startPos); } + case '@': return new At(startPos); case '\\': return new Backslash(startPos); case '(': return new LParen(startPos); case ')': return new RParen(startPos); diff --git a/compiler/src/types.ts b/compiler/src/types.ts index c4bc2da27..a764a9765 100644 --- a/compiler/src/types.ts +++ b/compiler/src/types.ts @@ -1,6 +1,6 @@ import { InspectOptions } from "util"; import { ClassDeclaration, EnumDeclaration, StructDeclaration, Syntax } from "./cst"; -import { InspectFn, toStringTag } from "./util"; +import { InspectFn, assert, assertNever, toStringTag } from "./util"; export enum TypeKind { Arrow, @@ -568,6 +568,58 @@ export type TVar = TUniVar | TRigidVar + +export function typesEqual(a: Type, b: Type): boolean { + if (a.kind !== b.kind) { + return false; + } + switch (a.kind) { + case TypeKind.Con: + assert(b.kind === TypeKind.Con); + return a.id === b.id; + case TypeKind.UniVar: + assert(b.kind === TypeKind.UniVar); + return a.id === b.id; + case TypeKind.RigidVar: + assert(b.kind === TypeKind.RigidVar); + return a.id === b.id; + case TypeKind.Nil: + case TypeKind.Absent: + return true; + case TypeKind.Nominal: + assert(b.kind === TypeKind.Nominal); + return a.decl === b.decl; + case TypeKind.App: + assert(b.kind === TypeKind.App); + return typesEqual(a.left, b.left) && typesEqual(a.right, b.right); + case TypeKind.Field: + assert(b.kind === TypeKind.Field); + return a.name === b.name && typesEqual(a.type, b.type) && typesEqual(a.restType, b.restType); + case TypeKind.Arrow: + assert(b.kind === TypeKind.Arrow); + return typesEqual(a.paramType, b.paramType) && typesEqual(a.returnType, b.returnType); + case TypeKind.Tuple: + assert(b.kind === TypeKind.Tuple); + if (a.elementTypes.length !== b.elementTypes.length) { + return false; + } + for (let i = 0; i < a.elementTypes.length; i++) { + if (!typesEqual(a.elementTypes[i], b.elementTypes[i])) { + return false; + } + } + return true; + case TypeKind.Present: + assert(b.kind === TypeKind.Present); + return typesEqual(a.type, b.type); + case TypeKind.TupleIndex: + assert(b.kind === TypeKind.TupleIndex); + return a.index === b.index && typesEqual(a.tupleType, b.tupleType); + default: + assertNever(a); + } +} + export class TVSet { private mapping = new Map();