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:
parent
d194ff9b2e
commit
7a0cb6753a
8 changed files with 452 additions and 133 deletions
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>();
|
||||
|
|
Loading…
Reference in a new issue