Add a 'verify' command, ...

- Add a command to check the integrity of the compiler
 - Add a diagnostic when a tuple index is out of bounds
 - Make 'build' the default command
 - ...
This commit is contained in:
Sam Vervaeck 2023-06-30 19:30:42 +02:00
parent d194ff9b2e
commit 7a0cb6753a
Signed by: samvv
SSH key fingerprint: SHA256:dIg0ywU1OP+ZYifrYxy8c5esO72cIKB+4/9wkZj1VaY
8 changed files with 452 additions and 133 deletions

View file

@ -15,6 +15,10 @@ import BoltToJS from "../passes/BoltToJS"
import { stripExtension } from "../util" import { stripExtension } from "../util"
import { sync as which } from "which" import { sync as which } from "which"
import { spawnSync } from "child_process" 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) { function debug(value: any) {
console.error(util.inspect(value, { colors: true, depth: Infinity })); console.error(util.inspect(value, { colors: true, depth: Infinity }));
@ -73,15 +77,17 @@ program
.version('0.0.1') .version('0.0.1')
.option('-C, --work-dir', 'Act as if run from this directory', '.'); .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('<file>', 'Path to the Bolt program to compile') .argument('<file>', '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-typecheck', 'Skip type-checking')
.option('--no-emit', 'Do not output compiled files') .option('--no-emit', 'Do not output compiled files')
.option('-t, --target <target-id>', 'What to compile to', 'c') .option('-t, --target <target-id>', 'What to compile to', 'c')
.action((file, opts) => { .action((fileName, opts) => {
const cwd = opts.workDir; const cwd = opts.workDir;
const filename = path.resolve(cwd, file); const filePath = path.resolve(cwd, fileName);
const shouldTypecheck = opts.typecheck; const shouldTypecheck = opts.typecheck;
const shouldEmit = opts.emit; const shouldEmit = opts.emit;
@ -101,7 +107,7 @@ program.command('build', 'Build a set of Bolt sources')
process.exit(1); process.exit(1);
} }
const program = new Program([ filename ]); const program = new Program([ filePath ]);
if (program.diagnostics.hasError) { if (program.diagnostics.hasError) {
process.exit(1); 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>', '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(); program.parse();

View file

@ -26,11 +26,12 @@ import {
TypeclassDeclaredTwiceDiagnostic, TypeclassDeclaredTwiceDiagnostic,
FieldNotFoundDiagnostic, FieldNotFoundDiagnostic,
TypeMismatchDiagnostic, TypeMismatchDiagnostic,
TupleIndexOutOfRangeDiagnostic,
} from "./diagnostics"; } from "./diagnostics";
import { assert, assertNever, isEmpty, MultiMap, toStringTag, InspectFn, implementationLimitation } from "./util"; import { assert, assertNever, isEmpty, MultiMap, toStringTag, InspectFn, implementationLimitation } from "./util";
import { Analyser } from "./analysis"; import { Analyser } from "./analysis";
import { InspectOptions } from "util"; 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"; import { CClass, CEmpty, CEqual, CMany, Constraint, ConstraintKind, ConstraintSet } from "./constraints";
// export class Qual { // export class Qual {
@ -691,7 +692,10 @@ export class Checker {
{ {
const tupleType = this.simplifyType(type.tupleType); const tupleType = this.simplifyType(type.tupleType);
if (tupleType.kind === TypeKind.Tuple) { 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]; const newType = tupleType.elementTypes[type.index];
type.set(newType); type.set(newType);
return newType; return newType;
@ -1295,10 +1299,19 @@ export class Checker {
public inferExpression(node: Expression): Type { 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) { switch (node.kind) {
case SyntaxKind.NestedExpression: case SyntaxKind.NestedExpression:
return this.inferExpression(node.expression); type = this.inferExpression(node.expression);
break;
case SyntaxKind.MatchExpression: case SyntaxKind.MatchExpression:
{ {
@ -1308,7 +1321,7 @@ export class Checker {
} else { } else {
exprType = this.createTypeVar(); exprType = this.createTypeVar();
} }
let resultType: Type = this.createTypeVar(); type = this.createTypeVar();
for (const arm of node.arms) { for (const arm of node.arms) {
const context = this.getContext(); const context = this.getContext();
const newEnv = new TypeEnv(context.env); const newEnv = new TypeEnv(context.env);
@ -1330,7 +1343,7 @@ export class Checker {
); );
this.addConstraint( this.addConstraint(
new CEqual( new CEqual(
resultType, type,
this.inferExpression(arm.expression), this.inferExpression(arm.expression),
arm.expression arm.expression
) )
@ -1338,13 +1351,14 @@ export class Checker {
this.popContext(newContext); this.popContext(newContext);
} }
if (node.expression === null) { if (node.expression === null) {
resultType = new TArrow(exprType, resultType); type = new TArrow(exprType, type);
} }
return resultType; break;
} }
case SyntaxKind.TupleExpression: 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: case SyntaxKind.ReferenceExpression:
{ {
@ -1360,36 +1374,48 @@ export class Checker {
} }
const scheme = this.lookup(node, Symkind.Var); const scheme = this.lookup(node, Symkind.Var);
if (scheme === null) { if (scheme === null) {
//this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name)); //this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
return this.createTypeVar(); type = this.createTypeVar();
break;
} }
const type = this.instantiate(scheme, node); type = this.instantiate(scheme, node);
type.node = node; type.node = node;
return type; break;
} }
case SyntaxKind.MemberExpression: case SyntaxKind.MemberExpression:
{ {
let type = this.inferExpression(node.expression); type = this.inferExpression(node.expression);
for (const [_dot, name] of node.path) { for (const [_dot, name] of node.path) {
const newFieldType = this.createTypeVar(name); switch (name.kind) {
const newRestType = this.createTypeVar(); case SyntaxKind.Identifier:
this.addConstraint( {
new CEqual( const newFieldType = this.createTypeVar(name);
type, const newRestType = this.createTypeVar();
new TField(name.text, new TPresent(newFieldType), newRestType, name), this.addConstraint(
node, new CEqual(
) type,
); new TField(name.text, new TPresent(newFieldType), newRestType, name),
type = newFieldType; node,
)
);
type = newFieldType;
break;
}
case SyntaxKind.Integer:
type = new TTupleIndex(type, Number(name.value));
break;
default:
assertNever(name);
}
} }
return type; break;
} }
case SyntaxKind.CallExpression: case SyntaxKind.CallExpression:
{ {
const opType = this.inferExpression(node.func); const opType = this.inferExpression(node.func);
const retType = this.createTypeVar(node); type = this.createTypeVar(node);
const paramTypes = []; const paramTypes = [];
for (const arg of node.args) { for (const arg of node.args) {
paramTypes.push(this.inferExpression(arg)); paramTypes.push(this.inferExpression(arg));
@ -1397,32 +1423,31 @@ export class Checker {
this.addConstraint( this.addConstraint(
new CEqual( new CEqual(
opType, opType,
TArrow.build(paramTypes, retType), TArrow.build(paramTypes, type),
node node
) )
); );
return retType; break;
} }
case SyntaxKind.ConstantExpression: case SyntaxKind.ConstantExpression:
{ {
let ty;
switch (node.token.kind) { switch (node.token.kind) {
case SyntaxKind.StringLiteral: case SyntaxKind.StringLiteral:
ty = this.getStringType(); type = this.getStringType();
break; break;
case SyntaxKind.Integer: case SyntaxKind.Integer:
ty = this.getIntType(); type = this.getIntType();
break; break;
} }
ty = ty.shallowClone(); type = type.shallowClone();
ty.node = node; type.node = node;
return ty; break;
} }
case SyntaxKind.StructExpression: case SyntaxKind.StructExpression:
{ {
let type: Type = new TNil(node); type = new TNil(node);
for (const member of node.members) { for (const member of node.members) {
switch (member.kind) { switch (member.kind) {
case SyntaxKind.StructExpressionField: case SyntaxKind.StructExpressionField:
@ -1447,7 +1472,9 @@ export class Checker {
throw new Error(`Unexpected ${member}`); 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: case SyntaxKind.InfixExpression:
@ -1458,17 +1485,17 @@ export class Checker {
return this.createTypeVar(); return this.createTypeVar();
} }
const opType = this.instantiate(scheme, node.operator); const opType = this.instantiate(scheme, node.operator);
const retType = this.createTypeVar();
const leftType = this.inferExpression(node.left); const leftType = this.inferExpression(node.left);
const rightType = this.inferExpression(node.right); const rightType = this.inferExpression(node.right);
type = this.createTypeVar();
this.addConstraint( this.addConstraint(
new CEqual( new CEqual(
new TArrow(leftType, new TArrow(rightType, retType)), new TArrow(leftType, new TArrow(rightType, type)),
opType, opType,
node, node,
), ),
); );
return retType; break;
} }
default: 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; let type;
if (!node.inferredKind) { if (checkKind && !node.inferredKind) {
type = this.createTypeVar(); type = this.createTypeVar();
@ -1496,16 +1527,17 @@ export class Checker {
if (scheme === null) { if (scheme === null) {
// this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name)); // this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
type = this.createTypeVar(); type = this.createTypeVar();
} else { 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();
}
type.node = node;
} }
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; break;
} }
@ -2281,6 +2313,11 @@ export class Checker {
return this.classDecls.get(name) ?? null; 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<ClassDeclaration[]> { // private *findInstanceContext(type: TCon, clazz: ClassDeclaration): Iterable<ClassDeclaration[]> {
// for (const instance of clazz.getInstances()) { // for (const instance of clazz.getInstances()) {
// assert(instance.types.length === 1); // assert(instance.types.length === 1);

View file

@ -97,6 +97,7 @@ export const enum SyntaxKind {
VBar, VBar,
Dot, Dot,
DotDot, DotDot,
At,
Comma, Comma,
Colon, Colon,
Equals, Equals,
@ -125,6 +126,10 @@ export const enum SyntaxKind {
BlockStart, BlockStart,
EndOfFile, EndOfFile,
// Annotations
TypeAnnotation,
ExpressionAnnotation,
// Type expressions // Type expressions
ReferenceTypeExpression, ReferenceTypeExpression,
ArrowTypeExpression, ArrowTypeExpression,
@ -225,6 +230,7 @@ export type Syntax
| StructDeclarationField | StructDeclarationField
| EnumDeclarationElement | EnumDeclarationElement
| TypeAssert | TypeAssert
| Annotation
| Declaration | Declaration
| Statement | Statement
| Expression | 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 { export class Comma extends TokenBase {
public readonly kind = SyntaxKind.Comma; public readonly kind = SyntaxKind.Comma;
@ -1190,6 +1210,7 @@ export type Token
| CustomOperator | CustomOperator
| Integer | Integer
| StringLiteral | StringLiteral
| At
| Comma | Comma
| Dot | Dot
| DotDot | DotDot
@ -1777,11 +1798,47 @@ export type Pattern
| DisjunctivePattern | DisjunctivePattern
| LiteralPattern | 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 { export class TupleExpression extends SyntaxBase {
public readonly kind = SyntaxKind.TupleExpression; public readonly kind = SyntaxKind.TupleExpression;
public constructor( public constructor(
public annotations: Annotations,
public lparen: LParen, public lparen: LParen,
public elements: Expression[], public elements: Expression[],
public rparen: RParen, public rparen: RParen,
@ -1791,6 +1848,7 @@ export class TupleExpression extends SyntaxBase {
public clone(): TupleExpression { public clone(): TupleExpression {
return new TupleExpression( return new TupleExpression(
this.annotations.map(a => a.clone()),
this.lparen.clone(), this.lparen.clone(),
this.elements.map(element => element.clone()), this.elements.map(element => element.clone()),
this.rparen.clone() this.rparen.clone()
@ -1812,6 +1870,7 @@ export class NestedExpression extends SyntaxBase {
public readonly kind = SyntaxKind.NestedExpression; public readonly kind = SyntaxKind.NestedExpression;
public constructor( public constructor(
public annotations: Annotations,
public lparen: LParen, public lparen: LParen,
public expression: Expression, public expression: Expression,
public rparen: RParen, public rparen: RParen,
@ -1821,6 +1880,7 @@ export class NestedExpression extends SyntaxBase {
public clone(): NestedExpression { public clone(): NestedExpression {
return new NestedExpression( return new NestedExpression(
this.annotations.map(a => a.clone()),
this.lparen.clone(), this.lparen.clone(),
this.expression.clone(), this.expression.clone(),
this.rparen.clone(), this.rparen.clone(),
@ -1842,13 +1902,17 @@ export class ConstantExpression extends SyntaxBase {
public readonly kind = SyntaxKind.ConstantExpression; public readonly kind = SyntaxKind.ConstantExpression;
public constructor( public constructor(
public annotations: Annotations,
public token: Integer | StringLiteral, public token: Integer | StringLiteral,
) { ) {
super(); super();
} }
public clone(): ConstantExpression { public clone(): ConstantExpression {
return new ConstantExpression( this.token.clone() ); return new ConstantExpression(
this.annotations.map(a => a.clone()),
this.token.clone()
);
} }
public getFirstToken(): Token { public getFirstToken(): Token {
@ -1866,6 +1930,7 @@ export class CallExpression extends SyntaxBase {
public readonly kind = SyntaxKind.CallExpression; public readonly kind = SyntaxKind.CallExpression;
public constructor( public constructor(
public annotations: Annotations,
public func: Expression, public func: Expression,
public args: Expression[], public args: Expression[],
) { ) {
@ -1874,6 +1939,7 @@ export class CallExpression extends SyntaxBase {
public clone(): CallExpression { public clone(): CallExpression {
return new CallExpression( return new CallExpression(
this.annotations.map(a => a.clone()),
this.func.clone(), this.func.clone(),
this.args.map(arg => arg.clone()), this.args.map(arg => arg.clone()),
); );
@ -1955,6 +2021,7 @@ export class StructExpression extends SyntaxBase {
public readonly kind = SyntaxKind.StructExpression; public readonly kind = SyntaxKind.StructExpression;
public constructor( public constructor(
public annotations: Annotations,
public lbrace: LBrace, public lbrace: LBrace,
public members: StructExpressionElement[], public members: StructExpressionElement[],
public rbrace: RBrace, public rbrace: RBrace,
@ -1964,6 +2031,7 @@ export class StructExpression extends SyntaxBase {
public clone(): StructExpression { public clone(): StructExpression {
return new StructExpression( return new StructExpression(
this.annotations.map(a => a.clone()),
this.lbrace.clone(), this.lbrace.clone(),
this.members.map(member => member.clone()), this.members.map(member => member.clone()),
this.rbrace.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 { export class MatchArm extends SyntaxBase {
public readonly kind = SyntaxKind.MatchArm; 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 { export class MatchExpression extends SyntaxBase {
public readonly kind = SyntaxKind.MatchExpression; public readonly kind = SyntaxKind.MatchExpression;
public constructor( public constructor(
public annotations: Annotations,
public matchKeyword: MatchKeyword, public matchKeyword: MatchKeyword,
public expression: Expression | null, public expression: Expression | null,
public arms: MatchArm[], public arms: MatchArm[],
@ -2055,6 +2126,7 @@ export class MatchExpression extends SyntaxBase {
public clone(): MatchExpression { public clone(): MatchExpression {
return new MatchExpression( return new MatchExpression(
this.annotations.map(a => a.clone()),
this.matchKeyword.clone(), this.matchKeyword.clone(),
this.expression !== null ? this.expression.clone() : null, this.expression !== null ? this.expression.clone() : null,
this.arms.map(arm => arm.clone()), this.arms.map(arm => arm.clone()),
@ -2062,6 +2134,9 @@ export class MatchExpression extends SyntaxBase {
} }
public getFirstToken(): Token { public getFirstToken(): Token {
if (this.annotations.length > 0) {
return this.annotations[0].getFirstToken();
}
return this.matchKeyword; return this.matchKeyword;
} }
@ -2082,6 +2157,7 @@ export class ReferenceExpression extends SyntaxBase {
public readonly kind = SyntaxKind.ReferenceExpression; public readonly kind = SyntaxKind.ReferenceExpression;
public constructor( public constructor(
public annotations: Annotations,
public modulePath: Array<[IdentifierAlt, Dot]>, public modulePath: Array<[IdentifierAlt, Dot]>,
public name: Identifier | IdentifierAlt, public name: Identifier | IdentifierAlt,
) { ) {
@ -2090,6 +2166,7 @@ export class ReferenceExpression extends SyntaxBase {
public clone(): ReferenceExpression { public clone(): ReferenceExpression {
return new ReferenceExpression( return new ReferenceExpression(
this.annotations.map(a => a.clone()),
this.modulePath.map(([name, dot]) => [name.clone(), dot.clone()]), this.modulePath.map(([name, dot]) => [name.clone(), dot.clone()]),
this.name.clone(), this.name.clone(),
); );
@ -2113,14 +2190,16 @@ export class MemberExpression extends SyntaxBase {
public readonly kind = SyntaxKind.MemberExpression; public readonly kind = SyntaxKind.MemberExpression;
public constructor( public constructor(
public annotations: Annotations,
public expression: Expression, public expression: Expression,
public path: [Dot, Identifier][], public path: [Dot, Identifier | Integer][],
) { ) {
super(); super();
} }
public clone(): MemberExpression { public clone(): MemberExpression {
return new MemberExpression( return new MemberExpression(
this.annotations.map(a => a.clone()),
this.expression.clone(), this.expression.clone(),
this.path.map(([dot, name]) => [dot.clone(), name.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 readonly kind = SyntaxKind.PrefixExpression;
public constructor( public constructor(
public annotations: Annotations,
public operator: ExprOperator, public operator: ExprOperator,
public expression: Expression, public expression: Expression,
) { ) {
@ -2149,6 +2229,7 @@ export class PrefixExpression extends SyntaxBase {
public clone(): PrefixExpression { public clone(): PrefixExpression {
return new PrefixExpression( return new PrefixExpression(
this.annotations.map(a => a.clone()),
this.operator.clone(), this.operator.clone(),
this.expression.clone(), this.expression.clone(),
); );
@ -2169,6 +2250,7 @@ export class PostfixExpression extends SyntaxBase {
public readonly kind = SyntaxKind.PostfixExpression; public readonly kind = SyntaxKind.PostfixExpression;
public constructor( public constructor(
public annotations: Annotations,
public expression: Expression, public expression: Expression,
public operator: ExprOperator, public operator: ExprOperator,
) { ) {
@ -2177,6 +2259,7 @@ export class PostfixExpression extends SyntaxBase {
public clone(): PostfixExpression { public clone(): PostfixExpression {
return new PostfixExpression( return new PostfixExpression(
this.annotations.map(a => a.clone()),
this.expression.clone(), this.expression.clone(),
this.operator.clone(), this.operator.clone(),
); );
@ -2197,6 +2280,7 @@ export class InfixExpression extends SyntaxBase {
public readonly kind = SyntaxKind.InfixExpression; public readonly kind = SyntaxKind.InfixExpression;
public constructor( public constructor(
public annotations: Annotations,
public left: Expression, public left: Expression,
public operator: ExprOperator, public operator: ExprOperator,
public right: Expression, public right: Expression,
@ -2206,6 +2290,7 @@ export class InfixExpression extends SyntaxBase {
public clone(): InfixExpression { public clone(): InfixExpression {
return new InfixExpression( return new InfixExpression(
this.annotations.map(a => a.clone()),
this.left.clone(), this.left.clone(),
this.operator.clone(), this.operator.clone(),
this.right.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 export type Expression
= MemberExpression = MemberExpression
| CallExpression | CallExpression
@ -3233,7 +3338,7 @@ export function isToken(value: any): value is Token {
&& value instanceof TokenBase; && value instanceof TokenBase;
} }
export function vistEachChild<T extends Syntax>(node: T, proc: (node: Syntax) => Syntax | undefined): Syntax { export function visitEachChild<T extends Syntax>(node: T, proc: (node: Syntax) => Syntax | void): Syntax {
const newArgs = []; const newArgs = [];
let changed = false; let changed = false;

View file

@ -1,6 +1,6 @@
import { Kind, KindType } from "./checker"; 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 { ClassConstraint, ClassDeclaration, IdentifierAlt, InstanceDeclaration, Syntax, SyntaxKind, TextFile, TextPosition, TextRange, Token } from "./cst";
import { assertNever, countDigits, IndentWriter } from "./util"; import { assertNever, countDigits, IndentWriter } from "./util";
@ -41,6 +41,7 @@ const enum DiagnosticKind {
UnexpectedToken, UnexpectedToken,
KindMismatch, KindMismatch,
TypeMismatch, TypeMismatch,
TupleIndexOutOfRange,
TypeclassNotFound, TypeclassNotFound,
TypeclassDecaredTwice, TypeclassDecaredTwice,
TypeclassNotImplemented, 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 { export class FieldNotFoundDiagnostic extends DiagnosticBase {
public readonly kind = DiagnosticKind.FieldNotFound; public readonly kind = DiagnosticKind.FieldNotFound;
@ -225,6 +241,7 @@ export type Diagnostic
| TypeclassNotImplementedDiagnostic | TypeclassNotImplementedDiagnostic
| BindingNotFoundDiagnostic | BindingNotFoundDiagnostic
| TypeMismatchDiagnostic | TypeMismatchDiagnostic
| TupleIndexOutOfRangeDiagnostic
| UnexpectedTokenDiagnostic | UnexpectedTokenDiagnostic
| FieldNotFoundDiagnostic | FieldNotFoundDiagnostic
| KindMismatchDiagnostic | KindMismatchDiagnostic
@ -510,6 +527,8 @@ export function describeType(type: Type): string {
} }
return out + ')'; return out + ')';
} }
case TypeKind.TupleIndex:
return describeType(type.tupleType) + '.' + type.index;
case TypeKind.Nominal: case TypeKind.Nominal:
{ {
return type.decl.name.text; return type.decl.name.text;

View file

@ -68,6 +68,11 @@ import {
AssignStatement, AssignStatement,
ForallTypeExpression, ForallTypeExpression,
TypeExpressionWithConstraints, TypeExpressionWithConstraints,
Annotation,
TypeAnnotation,
Annotations,
ExprOperator,
Integer,
} from "./cst" } from "./cst"
import { Stream } from "./util"; import { Stream } from "./util";
@ -322,16 +327,55 @@ export class Parser {
return new ArrowTypeExpression(paramTypes, returnType); 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() const token = this.getToken()
if (token.kind !== SyntaxKind.StringLiteral if (token.kind !== SyntaxKind.StringLiteral
&& token.kind !== SyntaxKind.Integer) { && token.kind !== SyntaxKind.Integer) {
this.raiseParseError(token, [ SyntaxKind.StringLiteral, 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]> = []; const modulePath: Array<[IdentifierAlt, Dot]> = [];
for (;;) { for (;;) {
const t0 = this.peekToken(1); const t0 = this.peekToken(1);
@ -347,10 +391,13 @@ export class Parser {
if (name.kind !== SyntaxKind.Identifier && name.kind !== SyntaxKind.IdentifierAlt) { if (name.kind !== SyntaxKind.Identifier && name.kind !== SyntaxKind.IdentifierAlt) {
this.raiseParseError(name, [ SyntaxKind.Identifier, 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 elements = [];
const lparen = this.expectToken(SyntaxKind.LParen) const lparen = this.expectToken(SyntaxKind.LParen)
let rparen; let rparen;
@ -374,46 +421,52 @@ export class Parser {
} }
} }
if (elements.length === 1) { 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 { private parsePrimitiveExpression(): Expression {
const annotations = this.parseAnnotations();
const t0 = this.peekToken(); const t0 = this.peekToken();
switch (t0.kind) { switch (t0.kind) {
case SyntaxKind.LParen: case SyntaxKind.LParen:
return this.parseExpressionWithParens(); return this.parseExpressionWithParens(annotations);
case SyntaxKind.Identifier: case SyntaxKind.Identifier:
case SyntaxKind.IdentifierAlt: case SyntaxKind.IdentifierAlt:
return this.parseReferenceExpression(); return this.parseReferenceExpression(annotations);
case SyntaxKind.Integer: case SyntaxKind.Integer:
case SyntaxKind.StringLiteral: case SyntaxKind.StringLiteral:
return this.parseConstantExpression(); return this.parseConstantExpression(annotations);
case SyntaxKind.MatchKeyword: case SyntaxKind.MatchKeyword:
{ return this.parseMatchExpression(annotations);
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);
}
case SyntaxKind.LBrace: case SyntaxKind.LBrace:
{ {
this.getToken(); this.getToken();
@ -452,7 +505,7 @@ export class Parser {
break; break;
} }
} }
return new StructExpression(t0, fields, rbrace); return new StructExpression(annotations, t0, fields, rbrace);
} }
default: default:
this.raiseParseError(t0, [ this.raiseParseError(t0, [
@ -466,20 +519,25 @@ export class Parser {
private tryParseMemberExpression(): Expression { private tryParseMemberExpression(): Expression {
const expression = this.parsePrimitiveExpression(); const expression = this.parsePrimitiveExpression();
const path: Array<[Dot, Identifier]> = []; const path: Array<[Dot, Identifier | Integer]> = [];
for (;;) { for (;;) {
const t1 = this.peekToken(); const t1 = this.peekToken();
if (t1.kind !== SyntaxKind.Dot) { if (t1.kind !== SyntaxKind.Dot) {
break; break;
} }
this.getToken(); this.getToken();
const name = this.expectToken(SyntaxKind.Identifier); const t2 = this.getToken();
path.push([t1, name]); if (t2.kind !== SyntaxKind.Identifier && t2.kind !== SyntaxKind.Integer) {
this.raiseParseError(t2, [ SyntaxKind.Identifier, SyntaxKind.Integer ]);
}
path.push([t1, t2]);
} }
if (path.length === 0) { if (path.length === 0) {
return expression; return expression;
} }
return new MemberExpression(expression, path); const annotations = expression.annotations;
expression.annotations = [];
return new MemberExpression(annotations, expression, path);
} }
private tryParseCallExpression(): Expression { private tryParseCallExpression(): Expression {
@ -501,13 +559,16 @@ export class Parser {
if (args.length === 0) { if (args.length === 0) {
return func return func
} }
return new CallExpression(func, args); const annotations = func.annotations;
func.annotations = [];
return new CallExpression(annotations, func, args);
} }
private parseUnaryExpression(): Expression { private parseUnaryExpression(): Expression {
let result = this.tryParseCallExpression() let result = this.tryParseCallExpression()
const prefixes = []; const prefixes: Array<[Annotations, ExprOperator]> = [];
for (;;) { for (;;) {
const annotations = this.parseAnnotations();
const t0 = this.peekToken(); const t0 = this.peekToken();
if (!isExprOperator(t0)) { if (!isExprOperator(t0)) {
break; break;
@ -515,12 +576,12 @@ export class Parser {
if (!this.prefixExprOperators.has(t0.text)) { if (!this.prefixExprOperators.has(t0.text)) {
break; break;
} }
prefixes.push(t0); prefixes.push([annotations, t0]);
this.getToken() this.getToken()
} }
for (let i = prefixes.length-1; i >= 0; i--) { for (let i = prefixes.length-1; i >= 0; i--) {
const operator = prefixes[i]; const [annotations, operator] = prefixes[i];
result = new PrefixExpression(operator, result); result = new PrefixExpression(annotations, operator, result);
} }
return result; return result;
} }
@ -550,7 +611,9 @@ export class Parser {
} }
rhs = this.parseBinaryOperatorAfterExpr(rhs, info0.precedence); 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; return lhs;
} }

View file

@ -64,6 +64,7 @@ export class TypeclassDictPassing implements Pass<SourceFile, SourceFile> {
new ExprBody( new ExprBody(
new Equals(), new Equals(),
new StructExpression( new StructExpression(
[],
new LBrace(), new LBrace(),
node.elements.map(element => { node.elements.map(element => {
assert(element.kind === SyntaxKind.LetDeclaration); assert(element.kind === SyntaxKind.LetDeclaration);
@ -71,7 +72,7 @@ export class TypeclassDictPassing implements Pass<SourceFile, SourceFile> {
return new StructExpressionField( return new StructExpressionField(
new Identifier(null, element.pattern.name.text), new Identifier(null, element.pattern.name.text),
new Equals(), new Equals(),
new FunctionExpression(new Backslash(), element.params, element.body!) new FunctionExpression([], new Backslash(), element.params, element.body!)
); );
}), }),
new RBrace(), new RBrace(),

View file

@ -46,6 +46,7 @@ import {
InstanceKeyword, InstanceKeyword,
Backslash, Backslash,
ForallKeyword, ForallKeyword,
At,
} from "./cst" } from "./cst"
import { Diagnostics } from "./diagnostics" import { Diagnostics } from "./diagnostics"
import { Stream, BufferedStream, assert } from "./util"; import { Stream, BufferedStream, assert } from "./util";
@ -221,6 +222,7 @@ export class Scanner extends BufferedStream<Token> {
return new EndOfFile(startPos); return new EndOfFile(startPos);
} }
case '@': return new At(startPos);
case '\\': return new Backslash(startPos); case '\\': return new Backslash(startPos);
case '(': return new LParen(startPos); case '(': return new LParen(startPos);
case ')': return new RParen(startPos); case ')': return new RParen(startPos);

View file

@ -1,6 +1,6 @@
import { InspectOptions } from "util"; import { InspectOptions } from "util";
import { ClassDeclaration, EnumDeclaration, StructDeclaration, Syntax } from "./cst"; import { ClassDeclaration, EnumDeclaration, StructDeclaration, Syntax } from "./cst";
import { InspectFn, toStringTag } from "./util"; import { InspectFn, assert, assertNever, toStringTag } from "./util";
export enum TypeKind { export enum TypeKind {
Arrow, Arrow,
@ -568,6 +568,58 @@ export type TVar
= TUniVar = TUniVar
| TRigidVar | 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 { export class TVSet {
private mapping = new Map<number, TVar>(); private mapping = new Map<number, TVar>();