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 { 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('<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-emit', 'Do not output compiled files')
.option('-t, --target <target-id>', '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>', '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();

View file

@ -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<ClassDeclaration[]> {
// for (const instance of clazz.getInstances()) {
// assert(instance.types.length === 1);

View file

@ -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<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 = [];
let changed = false;

View file

@ -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;

View file

@ -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;
}

View file

@ -64,6 +64,7 @@ export class TypeclassDictPassing implements Pass<SourceFile, SourceFile> {
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<SourceFile, SourceFile> {
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(),

View file

@ -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<Token> {
return new EndOfFile(startPos);
}
case '@': return new At(startPos);
case '\\': return new Backslash(startPos);
case '(': return new LParen(startPos);
case ')': return new RParen(startPos);

View file

@ -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<number, TVar>();