Refactor by splitting into multiple files

This commit is contained in:
Sam Vervaeck 2023-06-21 16:56:04 +02:00
parent 767572b197
commit f58011e50e
Signed by: samvv
SSH key fingerprint: SHA256:dIg0ywU1OP+ZYifrYxy8c5esO72cIKB+4/9wkZj1VaY
17 changed files with 1369 additions and 1281 deletions

View file

@ -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",

View file

@ -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
View 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> {
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,4 @@
import "reflect-metadata"
import path from "path"
import stream from "stream"
import { InspectOptions } from "util";

10
package.json Normal file
View file

@ -0,0 +1,10 @@
{
"name": "bolt-workspace",
"private": true,
"workspaces": [
"compiler"
],
"dependencies": {
"typescript": "^5.0.4"
}
}