Refactor by splitting into multiple files
This commit is contained in:
parent
767572b197
commit
f58011e50e
17 changed files with 1369 additions and 1281 deletions
|
@ -1,18 +1,14 @@
|
|||
{
|
||||
"name": "@samvv/bolt",
|
||||
"name": "@boltlang/bolt",
|
||||
"version": "0.0.1",
|
||||
"description": "A new programming language for the web",
|
||||
"main": "lib/index.js",
|
||||
"bin": {
|
||||
"bolt": "lib/bin/bolt.js",
|
||||
"bolt-self": "lib/bin/bolt-self.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node ./lib/bin/bolt-self.js check"
|
||||
"bolt": "lib/bin/bolt.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/BoltJS"
|
||||
"url": "https://github.com/samvv/BoltJS"
|
||||
},
|
||||
"keywords": [
|
||||
"programming-language",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import "source-map-support/register"
|
||||
import "reflect-metadata"
|
||||
|
||||
import fs from "fs"
|
||||
import util from "util"
|
||||
|
|
File diff suppressed because it is too large
Load diff
168
compiler/src/constraints.ts
Normal file
168
compiler/src/constraints.ts
Normal file
|
@ -0,0 +1,168 @@
|
|||
|
||||
import { InspectOptions } from "util";
|
||||
import { Syntax } from "./cst"
|
||||
import { TVar, TVSub, Type } from "./types";
|
||||
import { first, InspectFn, last, toStringTag } from "./util";
|
||||
|
||||
export const enum ConstraintKind {
|
||||
Equal,
|
||||
Many,
|
||||
Empty,
|
||||
Class,
|
||||
}
|
||||
|
||||
abstract class ConstraintBase {
|
||||
|
||||
public constructor(
|
||||
public node: Syntax | null = null
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
public prevInstantiation: Constraint | null = null;
|
||||
|
||||
public *getNodes(): Iterable<Syntax> {
|
||||
let curr: Constraint | null = this as any;
|
||||
while (curr !== null) {
|
||||
if (curr.node !== null) {
|
||||
yield curr.node;
|
||||
}
|
||||
curr = curr.prevInstantiation;
|
||||
}
|
||||
}
|
||||
|
||||
public get lastNode(): Syntax | null {
|
||||
return last(this.getNodes()[Symbol.iterator]()) ?? null;
|
||||
}
|
||||
|
||||
public get firstNode(): Syntax | null {
|
||||
return first(this.getNodes()[Symbol.iterator]()) ?? null;
|
||||
}
|
||||
|
||||
public abstract freeTypeVars(): Iterable<TVar>;
|
||||
|
||||
public abstract substitute(sub: TVSub): Constraint;
|
||||
|
||||
}
|
||||
|
||||
export class CEqual extends ConstraintBase {
|
||||
|
||||
public readonly kind = ConstraintKind.Equal;
|
||||
|
||||
public constructor(
|
||||
public left: Type,
|
||||
public right: Type,
|
||||
public node: Syntax | null,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public substitute(sub: TVSub): CEqual {
|
||||
return new CEqual(
|
||||
this.left.substitute(sub),
|
||||
this.right.substitute(sub),
|
||||
this.node,
|
||||
);
|
||||
}
|
||||
|
||||
public *freeTypeVars(): Iterable<TVar> {
|
||||
yield* this.left.getTypeVars();
|
||||
yield* this.right.getTypeVars();
|
||||
}
|
||||
|
||||
public [toStringTag](_currentDepth: number, options: InspectOptions, inspect: InspectFn): string {
|
||||
return inspect(this.left, options) + ' ~ ' + inspect(this.right, options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CMany extends ConstraintBase {
|
||||
|
||||
public readonly kind = ConstraintKind.Many;
|
||||
|
||||
public constructor(
|
||||
public elements: Constraint[]
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public substitute(sub: TVSub): CMany {
|
||||
const newElements = [];
|
||||
for (const element of this.elements) {
|
||||
newElements.push(element.substitute(sub));
|
||||
}
|
||||
return new CMany(newElements);
|
||||
}
|
||||
|
||||
public *freeTypeVars(): Iterable<TVar> {
|
||||
for (const element of this.elements) {
|
||||
yield* element.freeTypeVars();
|
||||
}
|
||||
}
|
||||
|
||||
public [toStringTag](currentDepth: number, { depth = 2, ...options }: InspectOptions, inspect: InspectFn): string {
|
||||
if (this.elements.length === 0) {
|
||||
return '[]';
|
||||
}
|
||||
let out = '[\n';
|
||||
const newOptions = { ...options, depth: depth === null ? null : depth - 1 };
|
||||
out += this.elements.map(constraint => ' ' + inspect(constraint, newOptions)).join('\n');
|
||||
out += '\n]';
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CClass extends ConstraintBase {
|
||||
|
||||
public readonly kind = ConstraintKind.Class;
|
||||
|
||||
public constructor(
|
||||
public className: string,
|
||||
public type: Type,
|
||||
public node: Syntax | null = null,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public substitute(sub: TVSub): CClass {
|
||||
return new CClass(this.className, this.type.substitute(sub));
|
||||
}
|
||||
|
||||
public freeTypeVars(): Iterable<TVar> {
|
||||
return this.type.getTypeVars();
|
||||
}
|
||||
|
||||
public [toStringTag](_depth: number, options: InspectOptions, inspect: InspectFn) {
|
||||
return this.className + ' ' + inspect(this.type, options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CEmpty extends ConstraintBase {
|
||||
|
||||
public readonly kind = ConstraintKind.Empty;
|
||||
|
||||
public substitute(_sub: TVSub): CEmpty {
|
||||
return this;
|
||||
}
|
||||
|
||||
public *freeTypeVars(): Iterable<TVar> {
|
||||
|
||||
}
|
||||
|
||||
public [toStringTag]() {
|
||||
return 'ε';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type Constraint
|
||||
= CEqual
|
||||
| CMany
|
||||
| CEmpty
|
||||
| CClass
|
||||
|
||||
export class ConstraintSet extends Array<Constraint> {
|
||||
|
||||
}
|
|
@ -4,7 +4,8 @@ import path from "path"
|
|||
|
||||
import { assert, deserializable, implementationLimitation, IndentWriter, JSONObject, JSONValue, nonenumerable, unreachable } from "./util";
|
||||
import { isNodeWithScope, Scope } from "./scope"
|
||||
import { InferContext, Kind, KindEnv, Scheme, Type, TypeEnv } from "./checker"
|
||||
import type { InferContext, Kind, KindEnv, Scheme, TypeEnv } from "./checker"
|
||||
import type { Type } from "./types";
|
||||
import { Emitter } from "./emitter";
|
||||
|
||||
export type TextSpan = [number, number];
|
||||
|
@ -121,6 +122,7 @@ export const enum SyntaxKind {
|
|||
IfKeyword,
|
||||
ElifKeyword,
|
||||
ElseKeyword,
|
||||
ForallKeyword,
|
||||
LineFoldEnd,
|
||||
BlockEnd,
|
||||
BlockStart,
|
||||
|
@ -133,6 +135,8 @@ export const enum SyntaxKind {
|
|||
AppTypeExpression,
|
||||
NestedTypeExpression,
|
||||
TupleTypeExpression,
|
||||
ForallTypeExpression,
|
||||
TypeExpressionWithConstraints,
|
||||
|
||||
// Patterns
|
||||
NamedPattern,
|
||||
|
@ -1201,6 +1205,21 @@ export class VBar extends TokenBase {
|
|||
|
||||
}
|
||||
|
||||
@deserializable()
|
||||
export class ForallKeyword extends TokenBase {
|
||||
|
||||
public readonly kind = SyntaxKind.ForallKeyword;
|
||||
|
||||
public get text(): string {
|
||||
return 'forall';
|
||||
}
|
||||
|
||||
public clone(): ForallKeyword {
|
||||
return new ForallKeyword(this.startPos);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type Token
|
||||
= RArrow
|
||||
| RArrowAlt
|
||||
|
@ -1243,10 +1262,78 @@ export type Token
|
|||
| ElifKeyword
|
||||
| EnumKeyword
|
||||
| ForeignKeyword
|
||||
| ForallKeyword
|
||||
|
||||
export type TokenKind
|
||||
= Token['kind']
|
||||
|
||||
@deserializable()
|
||||
export class ForallTypeExpression extends SyntaxBase {
|
||||
|
||||
public readonly kind = SyntaxKind.ForallTypeExpression;
|
||||
|
||||
public constructor(
|
||||
public forallKeyword: ForallKeyword,
|
||||
public varTypeExps: VarTypeExpression[],
|
||||
public dot: Dot,
|
||||
public typeExpr: TypeExpression,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public clone(): ForallTypeExpression {
|
||||
return new ForallTypeExpression(
|
||||
this.forallKeyword.clone(),
|
||||
this.varTypeExps.map(e => e.clone()),
|
||||
this.dot.clone(),
|
||||
this.typeExpr.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
public getFirstToken(): Token {
|
||||
return this.forallKeyword;
|
||||
}
|
||||
|
||||
public getLastToken(): Token {
|
||||
return this.typeExpr.getLastToken();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@deserializable()
|
||||
export class TypeExpressionWithConstraints extends SyntaxBase {
|
||||
|
||||
public readonly kind = SyntaxKind.TypeExpressionWithConstraints;
|
||||
|
||||
public constructor(
|
||||
public constraints: ClassConstraint[],
|
||||
public rarrowAlt: RArrowAlt,
|
||||
public typeExpr: TypeExpression,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public clone(): TypeExpressionWithConstraints {
|
||||
return new TypeExpressionWithConstraints(
|
||||
this.constraints.map(c => c.clone()),
|
||||
this.rarrowAlt.clone(),
|
||||
this.typeExpr.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
public getFirstToken(): Token {
|
||||
if (this.constraints.length > 0) {
|
||||
return this.constraints[0].getFirstToken();
|
||||
}
|
||||
return this.rarrowAlt;
|
||||
}
|
||||
|
||||
public getLastToken(): Token {
|
||||
return this.typeExpr.getLastToken();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@deserializable()
|
||||
export class ArrowTypeExpression extends SyntaxBase {
|
||||
|
||||
|
@ -1437,6 +1524,8 @@ export type TypeExpression
|
|||
| AppTypeExpression
|
||||
| NestedTypeExpression
|
||||
| TupleTypeExpression
|
||||
| ForallTypeExpression
|
||||
| TypeExpressionWithConstraints
|
||||
|
||||
@deserializable()
|
||||
export class NamedPattern extends SyntaxBase {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
|
||||
import { TypeKind, type Type, Kind, KindType } from "./checker";
|
||||
import { Kind, KindType } from "./checker";
|
||||
import { type Type, TypeKind } from "./types"
|
||||
import { ClassConstraint, ClassDeclaration, IdentifierAlt, InstanceDeclaration, Syntax, SyntaxKind, TextFile, TextPosition, TextRange, Token } from "./cst";
|
||||
import { assertNever, countDigits, deserializable, IndentWriter } from "./util";
|
||||
|
||||
|
@ -42,6 +43,7 @@ const enum DiagnosticKind {
|
|||
TypeMismatch,
|
||||
TypeclassNotFound,
|
||||
TypeclassDecaredTwice,
|
||||
TypeclassNotImplemented,
|
||||
BindingNotFound,
|
||||
ModuleNotFound,
|
||||
FieldNotFound,
|
||||
|
@ -114,7 +116,8 @@ export class TypeclassNotFoundDiagnostic extends DiagnosticBase {
|
|||
public level = Level.Error;
|
||||
|
||||
public constructor(
|
||||
public name: IdentifierAlt,
|
||||
public name: string,
|
||||
public node: Syntax | null = null,
|
||||
public origin: InstanceDeclaration | ClassConstraint | null = null,
|
||||
) {
|
||||
super();
|
||||
|
@ -122,6 +125,23 @@ export class TypeclassNotFoundDiagnostic extends DiagnosticBase {
|
|||
|
||||
}
|
||||
|
||||
@deserializable()
|
||||
export class TypeclassNotImplementedDiagnostic extends DiagnosticBase {
|
||||
|
||||
public readonly kind = DiagnosticKind.TypeclassNotImplemented;
|
||||
|
||||
public level = Level.Error;
|
||||
|
||||
public constructor(
|
||||
public name: string,
|
||||
public type: Type,
|
||||
public node: Syntax | null = null,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@deserializable()
|
||||
export class BindingNotFoundDiagnostic extends DiagnosticBase {
|
||||
|
||||
|
@ -212,6 +232,7 @@ export type Diagnostic
|
|||
= UnexpectedCharDiagnostic
|
||||
| TypeclassNotFoundDiagnostic
|
||||
| TypeclassDeclaredTwiceDiagnostic
|
||||
| TypeclassNotImplementedDiagnostic
|
||||
| BindingNotFoundDiagnostic
|
||||
| TypeMismatchDiagnostic
|
||||
| UnexpectedTokenDiagnostic
|
||||
|
@ -304,15 +325,17 @@ export class ConsoleDiagnostics implements Diagnostics {
|
|||
break;
|
||||
|
||||
case DiagnosticKind.TypeclassNotFound:
|
||||
this.writer.write(`the type class ${ANSI_FG_MAGENTA + diagnostic.name.text + ANSI_RESET} was not found.\n\n`);
|
||||
this.writer.write(printNode(diagnostic.name) + '\n');
|
||||
if (diagnostic.origin !== null) {
|
||||
this.writer.indent();
|
||||
this.writer.write(ANSI_FG_YELLOW + ANSI_BOLD + 'info: ' + ANSI_RESET);
|
||||
this.writer.write(`${ANSI_FG_MAGENTA + diagnostic.name.text + ANSI_RESET} is required by ${ANSI_FG_MAGENTA + diagnostic.origin.name.text + ANSI_RESET}\n\n`);
|
||||
this.writer.write(printNode(diagnostic.origin.name) + '\n');
|
||||
this.writer.dedent();
|
||||
this.writer.write(`the type class ${ANSI_FG_MAGENTA + diagnostic.name + ANSI_RESET} was not found.\n\n`);
|
||||
if (diagnostic.node !== null) {
|
||||
this.writer.write(printNode(diagnostic.node) + '\n');
|
||||
}
|
||||
// if (diagnostic.origin !== null) {
|
||||
// this.writer.indent();
|
||||
// this.writer.write(ANSI_FG_YELLOW + ANSI_BOLD + 'info: ' + ANSI_RESET);
|
||||
// this.writer.write(`${ANSI_FG_MAGENTA + diagnostic.name + ANSI_RESET} is required by ${ANSI_FG_MAGENTA + diagnostic.origin.name.text + ANSI_RESET}\n\n`);
|
||||
// this.writer.write(printNode(diagnostic.origin.name) + '\n');
|
||||
// this.writer.dedent();
|
||||
// }
|
||||
break;
|
||||
|
||||
case DiagnosticKind.BindingNotFound:
|
||||
|
|
|
@ -44,7 +44,6 @@ import {
|
|||
IfStatement,
|
||||
MemberExpression,
|
||||
IdentifierAlt,
|
||||
WrappedOperator,
|
||||
ArrowTypeExpression,
|
||||
EnumDeclarationStructElement,
|
||||
EnumDeclaration,
|
||||
|
@ -67,6 +66,8 @@ import {
|
|||
InstanceDeclaration,
|
||||
ClassConstraintClause,
|
||||
AssignStatement,
|
||||
ForallTypeExpression,
|
||||
TypeExpressionWithConstraints,
|
||||
} from "./cst"
|
||||
import { Stream } from "./util";
|
||||
|
||||
|
@ -253,7 +254,57 @@ export class Parser {
|
|||
return new AppTypeExpression(operator, args);
|
||||
}
|
||||
|
||||
public parseTypeExpressionWithConstraints(): TypeExpression {
|
||||
if (!this.lookaheadHasClassConstraints()) {
|
||||
return this.parseArrowTypeExpression();
|
||||
}
|
||||
const constraints = [];
|
||||
let rarrowAlt;
|
||||
for (;;) {
|
||||
const constraint = this.parseClassConstraint();
|
||||
constraints.push(constraint);
|
||||
const t1 = this.getToken();
|
||||
if (t1.kind === SyntaxKind.RArrowAlt) {
|
||||
rarrowAlt = t1;
|
||||
break;
|
||||
} else if (t1.kind !== SyntaxKind.Comma) {
|
||||
this.raiseParseError(t1, [ SyntaxKind.RArrowAlt, SyntaxKind.Comma ]);
|
||||
}
|
||||
}
|
||||
const type = this.parseArrowTypeExpression();
|
||||
return new TypeExpressionWithConstraints(constraints, rarrowAlt, type);
|
||||
}
|
||||
|
||||
public parseTypeExpression(): TypeExpression {
|
||||
const t0 = this.peekToken();
|
||||
switch (t0.kind) {
|
||||
case SyntaxKind.ForallKeyword:
|
||||
{
|
||||
this.getToken();
|
||||
let dot;
|
||||
const typeVarExps = [];
|
||||
for (;;) {
|
||||
const t1 = this.peekToken();
|
||||
if (t1.kind === SyntaxKind.Dot) {
|
||||
dot = t1;
|
||||
this.getToken();
|
||||
break;
|
||||
}
|
||||
typeVarExps.push(this.parseVarTypeExpression());
|
||||
}
|
||||
return new ForallTypeExpression(
|
||||
t0,
|
||||
typeVarExps,
|
||||
dot,
|
||||
this.parseTypeExpression(),
|
||||
);
|
||||
}
|
||||
default:
|
||||
return this.parseTypeExpressionWithConstraints();
|
||||
}
|
||||
}
|
||||
|
||||
public parseArrowTypeExpression(): TypeExpression {
|
||||
let returnType = this.parseAppTypeExpressionOrBelow();
|
||||
const paramTypes = [];
|
||||
for (;;) {
|
||||
|
@ -822,6 +873,20 @@ export class Parser {
|
|||
}
|
||||
}
|
||||
|
||||
private lookaheadHasClassConstraints(): boolean {
|
||||
for (let i = 1;; i++) {
|
||||
const token = this.peekToken(i);
|
||||
switch (token.kind) {
|
||||
case SyntaxKind.RArrowAlt:
|
||||
return true;
|
||||
case SyntaxKind.BlockStart:
|
||||
case SyntaxKind.LineFoldEnd:
|
||||
case SyntaxKind.Equals:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public parseLetDeclaration(): LetDeclaration {
|
||||
let t0 = this.getToken();
|
||||
let pubKeyword = null;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
import { CBuiltinType, CBuiltinTypeKind, CCallExpr, CConstExpr, CDecl, CDir, CExpr, CExprStmt, CFuncDecl, CIncDir, CNode, CProgram, CRefExpr, CStmt } from "../c";
|
||||
import { Expression, Syntax, SyntaxKind } from "../cst";
|
||||
import { Pass } from "../types";
|
||||
import type { Pass } from "../program";
|
||||
import { assert } from "../util";
|
||||
|
||||
interface Context {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Syntax } from "../cst";
|
||||
import { JSNode, JSProgram } from "../js";
|
||||
import { Pass } from "../types";
|
||||
import type { Pass } from "../program";
|
||||
|
||||
export class BoltToJS implements Pass<Syntax, JSNode> {
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
canHaveInstanceDeclaration,
|
||||
vistEachChild
|
||||
} from "../cst";
|
||||
import { Pass } from "../types";
|
||||
import { Pass } from "../program";
|
||||
import { assert } from "../util";
|
||||
|
||||
function encode(typeExpr: TypeExpression): string {
|
||||
|
@ -58,7 +58,7 @@ export class TypeclassDictPassing implements Pass<SourceFile, SourceFile> {
|
|||
new LetKeyword(),
|
||||
null,
|
||||
null,
|
||||
new NamedPattern(new Identifier(this.mangleInstance(node))),
|
||||
new NamedPattern(new Identifier(null, this.mangleInstance(node))),
|
||||
[],
|
||||
null, // TODO
|
||||
new ExprBody(
|
||||
|
@ -69,7 +69,7 @@ export class TypeclassDictPassing implements Pass<SourceFile, SourceFile> {
|
|||
assert(element.kind === SyntaxKind.LetDeclaration);
|
||||
assert(element.pattern.kind === SyntaxKind.NamedPattern);
|
||||
return new StructExpressionField(
|
||||
new Identifier(element.pattern.name.text),
|
||||
new Identifier(null, element.pattern.name.text),
|
||||
new Equals(),
|
||||
new FunctionExpression(new Backslash(), element.params, element.body!)
|
||||
);
|
||||
|
|
|
@ -5,7 +5,14 @@ import { SourceFile, TextFile } from "./cst";
|
|||
import { ConsoleDiagnostics, Diagnostics } from "./diagnostics";
|
||||
import { Checker } from "./checker";
|
||||
import { Analyser } from "./analysis";
|
||||
import { Newable, Pass } from "./types";
|
||||
|
||||
export interface Pass<In, Out> {
|
||||
apply(input: In): Out;
|
||||
}
|
||||
|
||||
export interface Newable<T> {
|
||||
new (...args: any[]): T;
|
||||
}
|
||||
|
||||
type AnyPass = Pass<any, any>;
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ import {
|
|||
ClassKeyword,
|
||||
InstanceKeyword,
|
||||
Backslash,
|
||||
ForallKeyword,
|
||||
} from "./cst"
|
||||
import { Diagnostics } from "./diagnostics"
|
||||
import { Stream, BufferedStream, assert } from "./util";
|
||||
|
@ -383,6 +384,7 @@ export class Scanner extends BufferedStream<Token> {
|
|||
case 'match': return new MatchKeyword(startPos);
|
||||
case 'foreign': return new ForeignKeyword(startPos);
|
||||
case 'mod': return new ModKeyword(startPos);
|
||||
case 'forall': return new ForallKeyword(startPos);
|
||||
default:
|
||||
if (isUpper(text[0])) {
|
||||
return new IdentifierAlt(startPos, text);
|
||||
|
|
312
compiler/src/solver.ts
Normal file
312
compiler/src/solver.ts
Normal file
|
@ -0,0 +1,312 @@
|
|||
import { Constraint, ConstraintKind } from "./constraints";
|
||||
import { Diagnostics, FieldNotFoundDiagnostic, TypeclassNotFoundDiagnostic, TypeclassNotImplementedDiagnostic, TypeMismatchDiagnostic } from "./diagnostics";
|
||||
import { TAbsent, TField, TVar, TVSub, Type, TypeBase, TypeKind } from "./types";
|
||||
import { assert } from "./util";
|
||||
|
||||
export class ConstraintSolver {
|
||||
|
||||
private path: string[] = [];
|
||||
private constraint: Constraint | null = null;
|
||||
private maxTypeErrorCount = 5;
|
||||
|
||||
public solution = new TVSub;
|
||||
|
||||
public constructor(
|
||||
public diagnostics: Diagnostics,
|
||||
private nextTypeVarId: number,
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
private find(type: Type): Type {
|
||||
while (type.kind === TypeKind.Var && this.solution.has(type)) {
|
||||
type = this.solution.get(type)!;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private unifyField(left: Type, right: Type, enableDiagnostics: boolean): boolean {
|
||||
|
||||
const swap = () => { [right, left] = [left, right]; }
|
||||
|
||||
if (left.kind === TypeKind.Absent && right.kind === TypeKind.Absent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (right.kind === TypeKind.Absent) {
|
||||
swap();
|
||||
}
|
||||
|
||||
if (left.kind === TypeKind.Absent) {
|
||||
assert(right.kind === TypeKind.Present);
|
||||
const fieldName = this.path[this.path.length-1];
|
||||
if (enableDiagnostics) {
|
||||
this.diagnostics.add(
|
||||
new FieldNotFoundDiagnostic(fieldName, left.node, right.type.node, this.constraint!.firstNode)
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(left.kind === TypeKind.Present && right.kind === TypeKind.Present);
|
||||
return this.unify(left.type, right.type, enableDiagnostics);
|
||||
}
|
||||
|
||||
|
||||
private unify(left: Type, right: Type, enableDiagnostics: boolean): boolean {
|
||||
|
||||
left = this.find(left);
|
||||
right = this.find(right);
|
||||
|
||||
// console.log(`unify ${describeType(left)} @ ${left.node && left.node.constructor && left.node.constructor.name} ~ ${describeType(right)} @ ${right.node && right.node.constructor && right.node.constructor.name}`);
|
||||
|
||||
const swap = () => { [right, left] = [left, right]; }
|
||||
|
||||
if (left.kind !== TypeKind.Var && right.kind === TypeKind.Var) {
|
||||
swap();
|
||||
}
|
||||
|
||||
if (left.kind === TypeKind.Var) {
|
||||
|
||||
// Perform an occurs check, verifying whether left occurs
|
||||
// somewhere inside the structure of right. If so, unification
|
||||
// makes no sense.
|
||||
if (right.hasTypeVar(left)) {
|
||||
// TODO print a diagnostic
|
||||
return false;
|
||||
}
|
||||
|
||||
// We are ready to join the types, so the first thing we do is
|
||||
// propagating the type classes that 'left' requires to 'right'.
|
||||
// If 'right' is another type variable, we're lucky. We just copy
|
||||
// the missing type classes from 'left' to 'right'. Otherwise,
|
||||
//const propagateClasses = (classes: Iterable<ClassDeclaration>, type: Type) => {
|
||||
// if (type.kind === TypeKind.Var) {
|
||||
// for (const constraint of classes) {
|
||||
// type.context.add(constraint);
|
||||
// }
|
||||
// } else if (type.kind === TypeKind.Con) {
|
||||
// for (const constraint of classes) {
|
||||
// propagateClassTCon(constraint, type);
|
||||
// }
|
||||
// } else {
|
||||
// //assert(false);
|
||||
// //this.diagnostics.add(new );
|
||||
// }
|
||||
//}
|
||||
|
||||
//const propagateClassTCon = (clazz: ClassDeclaration, type: TCon) => {
|
||||
// const s = this.findInstanceContext(type, clazz);
|
||||
// let i = 0;
|
||||
// for (const classes of s) {
|
||||
// propagateClasses(classes, type.argTypes[i++]);
|
||||
// }
|
||||
//}
|
||||
|
||||
//propagateClasses(left.context, right);
|
||||
|
||||
// We are all clear; set the actual type of left to right.
|
||||
this.solution.set(left, right);
|
||||
|
||||
// These types will be join, and we'd like to track that
|
||||
// into a special chain.
|
||||
TypeBase.join(left, right);
|
||||
|
||||
// if (left.node !== null) {
|
||||
// right.node = left.node;
|
||||
// }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (left.kind === TypeKind.Arrow && right.kind === TypeKind.Arrow) {
|
||||
let success = true;
|
||||
if (!this.unify(left.paramType, right.paramType, enableDiagnostics)) {
|
||||
success = false;
|
||||
}
|
||||
if (!this.unify(left.returnType, right.returnType, enableDiagnostics)) {
|
||||
success = false;
|
||||
}
|
||||
if (success) {
|
||||
TypeBase.join(left, right);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
if (left.kind === TypeKind.Tuple && right.kind === TypeKind.Tuple) {
|
||||
if (left.elementTypes.length === right.elementTypes.length) {
|
||||
let success = false;
|
||||
const count = left.elementTypes.length;
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (!this.unify(left.elementTypes[i], right.elementTypes[i], enableDiagnostics)) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
TypeBase.join(left, right);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
if (left.kind === TypeKind.Con && right.kind === TypeKind.Con) {
|
||||
if (left.id === right.id) {
|
||||
assert(left.argTypes.length === right.argTypes.length);
|
||||
const count = left.argTypes.length;
|
||||
let success = true;
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (!this.unify(left.argTypes[i], right.argTypes[i], enableDiagnostics)) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
TypeBase.join(left, right);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
if (left.kind === TypeKind.Nil && right.kind === TypeKind.Nil) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (left.kind === TypeKind.Field && right.kind === TypeKind.Field) {
|
||||
if (left.name === right.name) {
|
||||
let success = true;
|
||||
this.path.push(left.name);
|
||||
if (!this.unifyField(left.type, right.type, enableDiagnostics)) {
|
||||
success = false;
|
||||
}
|
||||
this.path.pop();
|
||||
if (!this.unify(left.restType, right.restType, enableDiagnostics)) {
|
||||
success = false;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
let success = true;
|
||||
const newRestType = new TVar(this.nextTypeVarId++);
|
||||
if (!this.unify(left.restType, new TField(right.name, right.type, newRestType), enableDiagnostics)) {
|
||||
success = false;
|
||||
}
|
||||
if (!this.unify(right.restType, new TField(left.name, left.type, newRestType), enableDiagnostics)) {
|
||||
success = false;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
if (left.kind === TypeKind.Nil && right.kind === TypeKind.Field) {
|
||||
swap();
|
||||
}
|
||||
|
||||
if (left.kind === TypeKind.Field && right.kind === TypeKind.Nil) {
|
||||
let success = true;
|
||||
this.path.push(left.name);
|
||||
if (!this.unifyField(left.type, new TAbsent(right.node), enableDiagnostics)) {
|
||||
success = false;
|
||||
}
|
||||
this.path.pop();
|
||||
if (!this.unify(left.restType, right, enableDiagnostics)) {
|
||||
success = false;
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
if (left.kind === TypeKind.Nominal && right.kind === TypeKind.Nominal) {
|
||||
if (left.decl === right.decl) {
|
||||
return true;
|
||||
}
|
||||
// fall through to error reporting
|
||||
}
|
||||
|
||||
if (left.kind === TypeKind.App && right.kind === TypeKind.App) {
|
||||
return this.unify(left.left, right.left, enableDiagnostics)
|
||||
&& this.unify(left.right, right.right, enableDiagnostics);
|
||||
}
|
||||
|
||||
if (enableDiagnostics) {
|
||||
this.diagnostics.add(
|
||||
new TypeMismatchDiagnostic(
|
||||
left.substitute(this.solution),
|
||||
right.substitute(this.solution),
|
||||
[...this.constraint!.getNodes()],
|
||||
this.path,
|
||||
)
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public solve(constraint: Constraint): void {
|
||||
|
||||
let queue = [ constraint ];
|
||||
let next = [];
|
||||
let isNext = false;
|
||||
|
||||
let errorCount = 0;
|
||||
|
||||
for (;;) {
|
||||
|
||||
if (queue.length === 0) {
|
||||
if (next.length === 0) {
|
||||
break;
|
||||
}
|
||||
isNext = true;
|
||||
queue = next;
|
||||
next = [];
|
||||
}
|
||||
|
||||
const constraint = queue.shift()!;
|
||||
|
||||
sw: switch (constraint.kind) {
|
||||
|
||||
case ConstraintKind.Many:
|
||||
{
|
||||
for (const element of constraint.elements) {
|
||||
queue.push(element);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// case ConstraintKind.Class:
|
||||
// {
|
||||
// if (constraint.type.kind === TypeKind.Var) {
|
||||
// if (isNext) {
|
||||
// // TODO
|
||||
// } else {
|
||||
// next.push(constraint);
|
||||
// }
|
||||
// } else {
|
||||
// const classDecl = this.lookupClass(constraint.className);
|
||||
// if (classDecl === null) {
|
||||
// this.diagnostics.add(new TypeclassNotFoundDiagnostic(constraint.className, constraint.node));
|
||||
// break;
|
||||
// }
|
||||
// for (const instance of classDecl.getInstances()) {
|
||||
// if (this.unify(instance.inferredType, constraint.type, false)) {
|
||||
// break sw;
|
||||
// }
|
||||
// }
|
||||
// this.diagnostics.add(new TypeclassNotImplementedDiagnostic(constraint.className, constraint.type, constraint.node));
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
|
||||
case ConstraintKind.Equal:
|
||||
{
|
||||
this.constraint = constraint;
|
||||
if (!this.unify(constraint.left, constraint.right, true)) {
|
||||
errorCount++;
|
||||
if (errorCount === this.maxTypeErrorCount) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,226 +0,0 @@
|
|||
|
||||
## Record types can be unified without causing an error
|
||||
|
||||
```
|
||||
struct Person.
|
||||
email: String
|
||||
age: Int
|
||||
|
||||
let bert
|
||||
= Person {
|
||||
email = "bar@boo.com",
|
||||
age = 32
|
||||
}
|
||||
let bob
|
||||
= Person {
|
||||
email = "boo",
|
||||
age = 43
|
||||
}
|
||||
|
||||
bert == bob
|
||||
```
|
||||
|
||||
## Return types are polymorphic
|
||||
|
||||
```
|
||||
let id x = x
|
||||
|
||||
id 1
|
||||
id "foo"
|
||||
id True
|
||||
```
|
||||
|
||||
## Nested definitions work
|
||||
|
||||
```
|
||||
let foo x.
|
||||
let bar y z = y + z - x
|
||||
bar
|
||||
|
||||
foo True
|
||||
```
|
||||
|
||||
## Everything that can be type-checked will be type-checked
|
||||
|
||||
```
|
||||
let foo n.
|
||||
let f : String = 1
|
||||
return n
|
||||
```
|
||||
|
||||
## Recursive definitions do not cause infinite loops in the type-checker
|
||||
|
||||
```
|
||||
let fac n = fac_2 n
|
||||
|
||||
let fac_2 n = fac_3 n + fac n
|
||||
|
||||
let fac_3 n = fac_2 (n-1)
|
||||
|
||||
not (fac 1)
|
||||
```
|
||||
|
||||
## Example with mutual recursion works
|
||||
|
||||
```
|
||||
let is_even x.
|
||||
if x == 0.
|
||||
return True
|
||||
else.
|
||||
return is_odd (x-1)
|
||||
|
||||
let is_odd x.
|
||||
if x == 1.
|
||||
return False
|
||||
else.
|
||||
return is_even (x-1)
|
||||
|
||||
not (is_even True)
|
||||
```
|
||||
|
||||
## Polymorphic records can be partially typed
|
||||
|
||||
```
|
||||
struct Timestamped a b.
|
||||
first: a
|
||||
second: b
|
||||
timestamp: Int
|
||||
|
||||
type Foo = Timestamped Int
|
||||
|
||||
type Bar = Foo Int
|
||||
|
||||
let t : Bar = Timestamped { first = "bar", second = 1, timestamp = 12345 }
|
||||
```
|
||||
|
||||
## Extensible records work
|
||||
|
||||
```
|
||||
struct Timestamped a.
|
||||
data: a
|
||||
timestamp: Int
|
||||
|
||||
let t = Timestamped { data = "foo", timestamp = 12345 }
|
||||
|
||||
t.data == 1
|
||||
t.data == "foo"
|
||||
|
||||
let u = Timestamped { data = True, timestamp = 12345 }
|
||||
|
||||
u.data == "foo"
|
||||
u.data == False
|
||||
```
|
||||
|
||||
## A recursive function is automatically instantiated
|
||||
|
||||
```
|
||||
let fac n.
|
||||
if n == 0.
|
||||
return 1
|
||||
else.
|
||||
return n * fac (n-"foo")
|
||||
```
|
||||
|
||||
## Enum-declarations are correctly typed
|
||||
|
||||
```
|
||||
enum Maybe a.
|
||||
Just a
|
||||
Nothing
|
||||
|
||||
let right_1 : Maybe Int = Just 1
|
||||
let right_2 : Maybe String = Just "foo"
|
||||
let wrong : Maybe Int = Just "foo"
|
||||
```
|
||||
|
||||
## Kind inference works
|
||||
|
||||
```
|
||||
enum Maybe a.
|
||||
Just a
|
||||
Nothing
|
||||
|
||||
let foo_1 : Maybe
|
||||
let foo_2 : Maybe Int
|
||||
let foo_3 : Maybe Int Int
|
||||
let foo_4 : Maybe Int Int Int
|
||||
```
|
||||
|
||||
## Can indirectly apply a polymorphic datatype to some type
|
||||
|
||||
```
|
||||
enum Maybe a.
|
||||
Just a
|
||||
Nothing
|
||||
|
||||
enum App a b.
|
||||
MkApp (a b)
|
||||
|
||||
enum Foo.
|
||||
MkFoo (App Maybe Int)
|
||||
|
||||
let f : Foo = MkFoo (MkApp (Just 1))
|
||||
```
|
||||
|
||||
## Record-declarations inside enum-declarations work
|
||||
|
||||
```
|
||||
enum Shape.
|
||||
Circle.
|
||||
radius: Int
|
||||
Rect.
|
||||
width: Int
|
||||
height: Int
|
||||
|
||||
let z = Circle { radius = 12 }
|
||||
let a = Rect { width = 12, height = 12 }
|
||||
|
||||
a == z
|
||||
```
|
||||
|
||||
## Tuple types are correctly inferred and unified
|
||||
|
||||
```
|
||||
let foo_1 : (Int, Int, Int) = (1, 2, 3)
|
||||
let foo_2 : (Int, Int, Int) = (1, 2, "foo")
|
||||
```
|
||||
|
||||
## Module references work
|
||||
|
||||
```
|
||||
mod CD.
|
||||
mod A.
|
||||
struct Foo
|
||||
mod B.
|
||||
let alpha: A.Foo
|
||||
```
|
||||
|
||||
<<<<<<< HEAD
|
||||
## Rest-expressions on extensible records work
|
||||
|
||||
```
|
||||
struct Point.
|
||||
x: Int
|
||||
y: Int
|
||||
z: Int
|
||||
|
||||
let foo { x, y, .. } : Point -> Int = x + y
|
||||
|
||||
foo { x = 1, y = 2 }
|
||||
```
|
||||
=======
|
||||
|
||||
## A polymorphic function is properly generalized when assigned to a new variable
|
||||
|
||||
```
|
||||
let id x = x
|
||||
let id2 = id
|
||||
let id3 = id
|
||||
|
||||
id3 1
|
||||
id3 "bla"
|
||||
|
||||
id2 1
|
||||
id2 "bla"
|
||||
````
|
||||
>>>>>>> 11bae0fed0d58eafebd863b4e3bf117176176cb1
|
|
@ -1,8 +1,566 @@
|
|||
import { InspectOptions } from "util";
|
||||
import { ClassDeclaration, EnumDeclaration, StructDeclaration, Syntax } from "./cst";
|
||||
import { deserializable, ignore, InspectFn, toStringTag } from "./util";
|
||||
|
||||
export interface Pass<In, Out> {
|
||||
apply(input: In): Out;
|
||||
export enum TypeKind {
|
||||
Arrow,
|
||||
Var,
|
||||
Con,
|
||||
Tuple,
|
||||
App,
|
||||
Nominal,
|
||||
Field,
|
||||
Nil,
|
||||
Absent,
|
||||
Present,
|
||||
}
|
||||
|
||||
export interface Newable<T> {
|
||||
new (...args: any[]): T;
|
||||
export abstract class TypeBase {
|
||||
|
||||
@ignore
|
||||
public abstract readonly kind: TypeKind;
|
||||
|
||||
@ignore
|
||||
public next: Type = this as any;
|
||||
|
||||
public abstract node: Syntax | null;
|
||||
|
||||
public static join(a: Type, b: Type): void {
|
||||
const keep = a.next;
|
||||
a.next = b;
|
||||
b.next = keep;
|
||||
}
|
||||
|
||||
public abstract getTypeVars(): Iterable<TVar>;
|
||||
|
||||
public abstract shallowClone(): Type;
|
||||
|
||||
public abstract substitute(sub: TVSub): Type;
|
||||
|
||||
public hasTypeVar(tv: TVar): boolean {
|
||||
for (const other of this.getTypeVars()) {
|
||||
if (tv.id === other.id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract [toStringTag](depth: number, options: InspectOptions, inspect: InspectFn): string;
|
||||
|
||||
}
|
||||
|
||||
export function isType(value: any): value is Type {
|
||||
return value !== undefined
|
||||
&& value !== null
|
||||
&& value instanceof TypeBase;
|
||||
}
|
||||
|
||||
@deserializable()
|
||||
export class TVar extends TypeBase {
|
||||
|
||||
public readonly kind = TypeKind.Var;
|
||||
|
||||
@ignore
|
||||
public context = new Set<ClassDeclaration>();
|
||||
|
||||
public constructor(
|
||||
public id: number,
|
||||
public node: Syntax | null = null,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public *getTypeVars(): Iterable<TVar> {
|
||||
yield this;
|
||||
}
|
||||
|
||||
public shallowClone(): TVar {
|
||||
return new TVar(this.id, this.node);
|
||||
}
|
||||
|
||||
public substitute(sub: TVSub): Type {
|
||||
const other = sub.get(this);
|
||||
return other === undefined
|
||||
? this : other.substitute(sub);
|
||||
}
|
||||
|
||||
public [toStringTag]() {
|
||||
return 'a' + this.id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class TNil extends TypeBase {
|
||||
|
||||
public readonly kind = TypeKind.Nil;
|
||||
|
||||
public constructor(
|
||||
public node: Syntax | null = null
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public substitute(_sub: TVSub): Type {
|
||||
return this;
|
||||
}
|
||||
|
||||
public shallowClone(): Type {
|
||||
return new TNil(this.node);
|
||||
}
|
||||
|
||||
public *getTypeVars(): Iterable<TVar> {
|
||||
|
||||
}
|
||||
|
||||
public [toStringTag]() {
|
||||
return '∂Abs';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@deserializable()
|
||||
export class TAbsent extends TypeBase {
|
||||
|
||||
public readonly kind = TypeKind.Absent;
|
||||
|
||||
public constructor(
|
||||
public node: Syntax | null = null,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public substitute(_sub: TVSub): Type {
|
||||
return this;
|
||||
}
|
||||
|
||||
public shallowClone(): Type {
|
||||
return new TAbsent(this.node);
|
||||
}
|
||||
|
||||
public *getTypeVars(): Iterable<TVar> {
|
||||
|
||||
}
|
||||
|
||||
public [toStringTag]() {
|
||||
return 'Abs';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@deserializable()
|
||||
export class TPresent extends TypeBase {
|
||||
|
||||
public readonly kind = TypeKind.Present;
|
||||
|
||||
public constructor(
|
||||
public type: Type,
|
||||
public node: Syntax | null = null,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public substitute(sub: TVSub): Type {
|
||||
return new TPresent(this.type.substitute(sub), this.node);
|
||||
}
|
||||
|
||||
public getTypeVars(): Iterable<TVar> {
|
||||
return this.type.getTypeVars();
|
||||
}
|
||||
|
||||
public shallowClone(): Type {
|
||||
return new TPresent(this.type, this.node);
|
||||
}
|
||||
|
||||
public [toStringTag](_depth: number, options: InspectOptions, inspect: InspectFn) {
|
||||
return 'Pre ' + inspect(this.type, options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@deserializable()
|
||||
export class TArrow extends TypeBase {
|
||||
|
||||
public readonly kind = TypeKind.Arrow;
|
||||
|
||||
public constructor(
|
||||
public paramType: Type,
|
||||
public returnType: Type,
|
||||
public node: Syntax | null = null,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public static build(paramTypes: Type[], returnType: Type, node: Syntax | null = null): Type {
|
||||
let result = returnType;
|
||||
for (let i = paramTypes.length-1; i >= 0; i--) {
|
||||
result = new TArrow(paramTypes[i], result, node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public *getTypeVars(): Iterable<TVar> {
|
||||
yield* this.paramType.getTypeVars();
|
||||
yield* this.returnType.getTypeVars();
|
||||
}
|
||||
|
||||
public shallowClone(): TArrow {
|
||||
return new TArrow(
|
||||
this.paramType,
|
||||
this.returnType,
|
||||
this.node,
|
||||
)
|
||||
}
|
||||
|
||||
public substitute(sub: TVSub): Type {
|
||||
let changed = false;
|
||||
const newParamType = this.paramType.substitute(sub);
|
||||
if (newParamType !== this.paramType) {
|
||||
changed = true;
|
||||
}
|
||||
const newReturnType = this.returnType.substitute(sub);
|
||||
if (newReturnType !== this.returnType) {
|
||||
changed = true;
|
||||
}
|
||||
return changed ? new TArrow(newParamType, newReturnType, this.node) : this;
|
||||
}
|
||||
|
||||
public [toStringTag](_depth: number, options: InspectOptions, inspect: InspectFn) {
|
||||
return inspect(this.paramType, options) + ' -> ' + inspect(this.returnType, options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@deserializable()
|
||||
export class TCon extends TypeBase {
|
||||
|
||||
public readonly kind = TypeKind.Con;
|
||||
|
||||
public constructor(
|
||||
public id: number,
|
||||
public argTypes: Type[],
|
||||
public displayName: string,
|
||||
public node: Syntax | null = null,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public *getTypeVars(): Iterable<TVar> {
|
||||
for (const argType of this.argTypes) {
|
||||
yield* argType.getTypeVars();
|
||||
}
|
||||
}
|
||||
|
||||
public shallowClone(): TCon {
|
||||
return new TCon(
|
||||
this.id,
|
||||
this.argTypes,
|
||||
this.displayName,
|
||||
this.node,
|
||||
);
|
||||
}
|
||||
|
||||
public substitute(sub: TVSub): Type {
|
||||
let changed = false;
|
||||
const newArgTypes = [];
|
||||
for (const argType of this.argTypes) {
|
||||
const newArgType = argType.substitute(sub);
|
||||
if (newArgType !== argType) {
|
||||
changed = true;
|
||||
}
|
||||
newArgTypes.push(newArgType);
|
||||
}
|
||||
return changed ? new TCon(this.id, newArgTypes, this.displayName, this.node) : this;
|
||||
}
|
||||
|
||||
public [toStringTag](_depth: number, options: InspectOptions, inspect: InspectFn) {
|
||||
return this.displayName + ' ' + this.argTypes.map(t => inspect(t, options)).join(' ');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@deserializable()
|
||||
export class TTuple extends TypeBase {
|
||||
|
||||
public readonly kind = TypeKind.Tuple;
|
||||
|
||||
public constructor(
|
||||
public elementTypes: Type[],
|
||||
public node: Syntax | null = null,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public *getTypeVars(): Iterable<TVar> {
|
||||
for (const elementType of this.elementTypes) {
|
||||
yield* elementType.getTypeVars();
|
||||
}
|
||||
}
|
||||
|
||||
public shallowClone(): TTuple {
|
||||
return new TTuple(
|
||||
this.elementTypes,
|
||||
this.node,
|
||||
);
|
||||
}
|
||||
|
||||
public substitute(sub: TVSub): Type {
|
||||
let changed = false;
|
||||
const newElementTypes = [];
|
||||
for (const elementType of this.elementTypes) {
|
||||
const newElementType = elementType.substitute(sub);
|
||||
if (newElementType !== elementType) {
|
||||
changed = true;
|
||||
}
|
||||
newElementTypes.push(newElementType);
|
||||
}
|
||||
return changed ? new TTuple(newElementTypes, this.node) : this;
|
||||
}
|
||||
|
||||
public [toStringTag](_depth: number, options: InspectOptions, inspect: InspectFn) {
|
||||
return this.elementTypes.map(t => inspect(t, options)).join(' × ');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@deserializable()
|
||||
export class TField extends TypeBase {
|
||||
|
||||
public readonly kind = TypeKind.Field;
|
||||
|
||||
public constructor(
|
||||
public name: string,
|
||||
public type: Type,
|
||||
public restType: Type,
|
||||
public node: Syntax | null = null,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getTypeVars(): Iterable<TVar> {
|
||||
return this.type.getTypeVars();
|
||||
}
|
||||
|
||||
public shallowClone(): TField {
|
||||
return new TField(
|
||||
this.name,
|
||||
this.type,
|
||||
this.restType,
|
||||
this.node,
|
||||
);
|
||||
}
|
||||
|
||||
public static sort(type: Type): Type {
|
||||
const fields = new Map<string, TField>();
|
||||
while (type.kind === TypeKind.Field) {
|
||||
fields.set(type.name, type);
|
||||
type = type.restType;
|
||||
}
|
||||
const keys = [...fields.keys()].sort().reverse();
|
||||
let out: Type = type;
|
||||
for (const key of keys) {
|
||||
const field = fields.get(key)!;
|
||||
out = new TField(key, field.type, out, field.node);
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
public substitute(sub: TVSub): Type {
|
||||
const newType = this.type.substitute(sub);
|
||||
const newRestType = this.restType.substitute(sub);
|
||||
return newType !== this.type || newRestType !== this.restType
|
||||
? new TField(this.name, newType, newRestType, this.node) : this;
|
||||
}
|
||||
|
||||
public [toStringTag](_depth: number, options: InspectOptions, inspect: InspectFn) {
|
||||
let out = '{ ' + this.name + ': ' + inspect(this.type, options);
|
||||
let type = this.restType;
|
||||
while (type.kind === TypeKind.Field) {
|
||||
out += '; ' + type.name + ': ' + inspect(type.type, options);
|
||||
type = type.restType;
|
||||
}
|
||||
if (type.kind !== TypeKind.Nil) {
|
||||
out += '; ' + inspect(type, options);
|
||||
}
|
||||
return out + ' }'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@deserializable()
|
||||
export class TApp extends TypeBase {
|
||||
|
||||
public readonly kind = TypeKind.App;
|
||||
|
||||
public constructor(
|
||||
public left: Type,
|
||||
public right: Type,
|
||||
public node: Syntax | null = null
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public static build(resultType: Type, types: Type[], node: Syntax | null = null): Type {
|
||||
for (let i = 0; i < types.length; i++) {
|
||||
resultType = new TApp(types[i], resultType, node);
|
||||
}
|
||||
return resultType;
|
||||
}
|
||||
|
||||
public *getTypeVars(): Iterable<TVar> {
|
||||
yield* this.left.getTypeVars();
|
||||
yield* this.right.getTypeVars();
|
||||
}
|
||||
|
||||
public shallowClone() {
|
||||
return new TApp(
|
||||
this.left,
|
||||
this.right,
|
||||
this.node
|
||||
);
|
||||
}
|
||||
|
||||
public substitute(sub: TVSub): Type {
|
||||
let changed = false;
|
||||
const newOperatorType = this.left.substitute(sub);
|
||||
if (newOperatorType !== this.left) {
|
||||
changed = true;
|
||||
}
|
||||
const newArgType = this.right.substitute(sub);
|
||||
if (newArgType !== this.right) {
|
||||
changed = true;
|
||||
}
|
||||
return changed ? new TApp(newOperatorType, newArgType, this.node) : this;
|
||||
}
|
||||
|
||||
public [toStringTag](_depth: number, options: InspectOptions, inspect: InspectFn) {
|
||||
return inspect(this.left, options) + ' ' + inspect(this.right, options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@deserializable()
|
||||
export class TNominal extends TypeBase {
|
||||
|
||||
public readonly kind = TypeKind.Nominal;
|
||||
|
||||
public constructor(
|
||||
public decl: StructDeclaration | EnumDeclaration,
|
||||
public node: Syntax | null = null,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public *getTypeVars(): Iterable<TVar> {
|
||||
|
||||
}
|
||||
|
||||
public shallowClone(): Type {
|
||||
return new TNominal(
|
||||
this.decl,
|
||||
this.node,
|
||||
);
|
||||
}
|
||||
|
||||
public substitute(_sub: TVSub): Type {
|
||||
return this;
|
||||
}
|
||||
|
||||
public [toStringTag]() {
|
||||
return this.decl.name.text;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type Type
|
||||
= TCon
|
||||
| TArrow
|
||||
| TVar
|
||||
| TTuple
|
||||
| TApp
|
||||
| TNominal
|
||||
| TField
|
||||
| TNil
|
||||
| TPresent
|
||||
| TAbsent
|
||||
|
||||
|
||||
export class TVSet {
|
||||
|
||||
private mapping = new Map<number, TVar>();
|
||||
|
||||
public constructor(iterable?: Iterable<TVar>) {
|
||||
if (iterable !== undefined) {
|
||||
for (const tv of iterable) {
|
||||
this.add(tv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public add(tv: TVar): void {
|
||||
this.mapping.set(tv.id, tv);
|
||||
}
|
||||
|
||||
public has(tv: TVar): boolean {
|
||||
return this.mapping.has(tv.id);
|
||||
}
|
||||
|
||||
public intersectsType(type: Type): boolean {
|
||||
for (const tv of type.getTypeVars()) {
|
||||
if (this.has(tv)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public delete(tv: TVar): void {
|
||||
this.mapping.delete(tv.id);
|
||||
}
|
||||
|
||||
public get size(): number {
|
||||
return this.mapping.size;
|
||||
}
|
||||
|
||||
public [Symbol.iterator](): Iterator<TVar> {
|
||||
return this.mapping.values();
|
||||
}
|
||||
|
||||
public [toStringTag](_depth: number, options: InspectOptions, inspect: InspectFn) {
|
||||
let out = '{ ';
|
||||
let first = true;
|
||||
for (const tv of this) {
|
||||
if (first) first = false;
|
||||
else out += ', ';
|
||||
out += inspect(tv, options);
|
||||
}
|
||||
return out + ' }';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class TVSub {
|
||||
|
||||
private mapping = new Map<number, Type>();
|
||||
|
||||
public set(tv: TVar, type: Type): void {
|
||||
this.mapping.set(tv.id, type);
|
||||
}
|
||||
|
||||
public get(tv: TVar): Type | undefined {
|
||||
return this.mapping.get(tv.id);
|
||||
}
|
||||
|
||||
public has(tv: TVar): boolean {
|
||||
return this.mapping.has(tv.id);
|
||||
}
|
||||
|
||||
public delete(tv: TVar): void {
|
||||
this.mapping.delete(tv.id);
|
||||
}
|
||||
|
||||
public values(): Iterable<Type> {
|
||||
return this.mapping.values();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
import "reflect-metadata"
|
||||
import path from "path"
|
||||
import stream from "stream"
|
||||
import { InspectOptions } from "util";
|
||||
|
|
10
package.json
Normal file
10
package.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "bolt-workspace",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"compiler"
|
||||
],
|
||||
"dependencies": {
|
||||
"typescript": "^5.0.4"
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue