[WIP] Add support for typeclasses

This commit is contained in:
Sam Vervaeck 2023-02-03 17:51:27 +01:00
parent 036ccedea3
commit 3e14538d15
10 changed files with 676 additions and 208 deletions

View file

@ -1,6 +1,7 @@
import { DirectedHashGraph, strongconnect } from "yagl"; import { DirectedHashGraph, strongconnect } from "yagl";
import { assert } from "./util"; import { assert } from "./util";
import { Syntax, LetDeclaration, Scope, SourceFile, SyntaxKind } from "./cst"; import { Syntax, LetDeclaration, SourceFile, SyntaxKind } from "./cst";
import type { Scope } from "./scope"
type NodeWithBlock type NodeWithBlock
= LetDeclaration = LetDeclaration
@ -36,6 +37,8 @@ export class Analyser {
break; break;
} }
case SyntaxKind.InstanceDeclaration:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.SourceFile: case SyntaxKind.SourceFile:
case SyntaxKind.ModuleDeclaration: case SyntaxKind.ModuleDeclaration:
{ {

View file

@ -23,20 +23,36 @@ program
program program
.command('build', 'Build a set of Bolt sources') .command('build', 'Build a set of Bolt sources')
.argument('<file>', 'Path to the Bolt program to compile') .argument('<file>', 'Path to the Bolt program to compile')
.option('-t, --target <target-id>', 'What to compile to', 'c')
.action((file, opts) => { .action((file, opts) => {
const cwd = opts.workDir; const cwd = opts.workDir;
const filename = path.resolve(cwd, file); const filename = path.resolve(cwd, file);
let targetType: TargetType;
switch (opts.target) {
case 'js':
targetType = TargetType.JS;
break;
case 'c':
targetType = TargetType.C;
break;
default:
console.error(`Invalid target '${opts.target}' provided.`);
process.exit(1);
}
const program = new Program([ filename ]); const program = new Program([ filename ]);
if (program.diagnostics.hasError) { if (program.diagnostics.hasError) {
process.exit(1); process.exit(1);
} }
program.check(); program.check();
if (program.diagnostics.hasError) { if (program.diagnostics.hasError) {
process.exit(1); process.exit(1);
} }
program.emit({ type: TargetType.JS });
program.emit({ type: targetType });
if (program.diagnostics.hasError) { if (program.diagnostics.hasError) {
process.exit(1); process.exit(1);
} }

View file

@ -43,7 +43,14 @@ export const enum CBuiltinTypeKind {
} }
abstract class CNodeBase { abstract class CNodeBase {
public abstract readonly kind: CNodeKind; public abstract readonly kind: CNodeKind;
public emit(file: stream.Writable): void {
const emitter = new CEmitter(file);
emitter.emit(this as any);
}
} }
export class CBuiltinType extends CNodeBase { export class CBuiltinType extends CNodeBase {
@ -204,6 +211,7 @@ export class CProgram extends CNodeBase {
) { ) {
super(); super();
} }
} }
export type CNode export type CNode

View file

@ -1,4 +1,5 @@
import { import {
ClassDeclaration,
EnumDeclaration, EnumDeclaration,
Expression, Expression,
ExprOperator, ExprOperator,
@ -11,20 +12,21 @@ import {
ReferenceTypeExpression, ReferenceTypeExpression,
SourceFile, SourceFile,
StructDeclaration, StructDeclaration,
Symkind,
Syntax, Syntax,
SyntaxKind, SyntaxKind,
TypeExpression, TypeExpression,
} from "./cst"; } from "./cst";
import { Symkind } from "./scope"
import { import {
describeType, describeType,
BindingNotFoudDiagnostic, BindingNotFoundDiagnostic,
Diagnostics, Diagnostics,
FieldDoesNotExistDiagnostic, FieldDoesNotExistDiagnostic,
FieldMissingDiagnostic, FieldMissingDiagnostic,
UnificationFailedDiagnostic, UnificationFailedDiagnostic,
KindMismatchDiagnostic, KindMismatchDiagnostic,
ModuleNotFoundDiagnostic, ModuleNotFoundDiagnostic,
TypeclassNotFoundDiagnostic,
} from "./diagnostics"; } from "./diagnostics";
import { assert, isEmpty, MultiMap } from "./util"; import { assert, isEmpty, MultiMap } from "./util";
import { Analyser } from "./analysis"; import { Analyser } from "./analysis";
@ -696,7 +698,7 @@ type NodeWithReference
| ReferenceExpression | ReferenceExpression
| ReferenceTypeExpression | ReferenceTypeExpression
export class TypeEnv { class TypeEnv {
private mapping = new MultiMap<string, [Symkind, Scheme]>(); private mapping = new MultiMap<string, [Symkind, Scheme]>();
@ -750,6 +752,8 @@ class KindEnv {
} }
export type { KindEnv, TypeEnv };
function splitReferences(node: NodeWithReference): [IdentifierAlt[], Identifier | IdentifierAlt | ExprOperator] { function splitReferences(node: NodeWithReference): [IdentifierAlt[], Identifier | IdentifierAlt | ExprOperator] {
let modulePath: IdentifierAlt[]; let modulePath: IdentifierAlt[];
let name: Identifier | IdentifierAlt | ExprOperator; let name: Identifier | IdentifierAlt | ExprOperator;
@ -832,7 +836,7 @@ export class Checker {
this.contexts.pop(); this.contexts.pop();
} }
private lookupKind(env: KindEnv, node: NodeWithReference): Kind | null { private lookupKind(env: KindEnv, node: NodeWithReference, emitDiagnostic = true): Kind | null {
const [modulePath, name] = splitReferences(node); const [modulePath, name] = splitReferences(node);
if (modulePath.length > 0) { if (modulePath.length > 0) {
let maxIndex = 0; let maxIndex = 0;
@ -844,12 +848,14 @@ export class Checker {
const nextDown = currDown.resolveModule(moduleName.text); const nextDown = currDown.resolveModule(moduleName.text);
if (nextDown === null) { if (nextDown === null) {
if (currUp.kind === SyntaxKind.SourceFile) { if (currUp.kind === SyntaxKind.SourceFile) {
this.diagnostics.add( if (emitDiagnostic) {
new ModuleNotFoundDiagnostic( this.diagnostics.add(
modulePath.slice(maxIndex).map(id => id.text), new ModuleNotFoundDiagnostic(
modulePath[maxIndex], modulePath.slice(maxIndex).map(id => id.text),
) modulePath[maxIndex],
); )
);
}
return null; return null;
} }
currUp = currUp.getEnclosingModule(); currUp = currUp.getEnclosingModule();
@ -862,13 +868,15 @@ export class Checker {
if (found !== null) { if (found !== null) {
return found; return found;
} }
this.diagnostics.add( if (emitDiagnostic) {
new BindingNotFoudDiagnostic( this.diagnostics.add(
modulePath.map(id => id.text), new BindingNotFoundDiagnostic(
name.text, modulePath.map(id => id.text),
name, name.text,
) name,
); )
);
}
return null; return null;
} }
} else { } else {
@ -880,13 +888,15 @@ export class Checker {
} }
curr = curr.parent; curr = curr.parent;
} while(curr !== null); } while(curr !== null);
this.diagnostics.add( if (emitDiagnostic) {
new BindingNotFoudDiagnostic( this.diagnostics.add(
[], new BindingNotFoundDiagnostic(
name.text, [],
name, name.text,
) name,
); )
);
}
return null; return null;
} }
} }
@ -922,7 +932,7 @@ export class Checker {
return found; return found;
} }
this.diagnostics.add( this.diagnostics.add(
new BindingNotFoudDiagnostic( new BindingNotFoundDiagnostic(
modulePath.map(id => id.text), modulePath.map(id => id.text),
name.text, name.text,
name, name,
@ -940,7 +950,7 @@ export class Checker {
curr = curr.parent; curr = curr.parent;
} while(curr !== null); } while(curr !== null);
this.diagnostics.add( this.diagnostics.add(
new BindingNotFoudDiagnostic( new BindingNotFoundDiagnostic(
[], [],
name.text, name.text,
name, name,
@ -1013,12 +1023,13 @@ export class Checker {
} }
case SyntaxKind.VarTypeExpression: case SyntaxKind.VarTypeExpression:
{ {
const matchedKind = this.lookupKind(env, node.name); const matchedKind = this.lookupKind(env, node.name, false);
if (matchedKind === null) { if (matchedKind === null) {
// this.diagnostics.add(new BindingNotFoudDiagnostic([], node.name.text, node.name)); // this.diagnostics.add(new BindingNotFoudDiagnostic([], node.name.text, node.name));
// Create a filler kind variable that still will be able to catch other errors. // Create a filler kind variable that still will be able to catch other errors.
kind = this.createKindVar(); kind = this.createKindVar();
kind.flags |= KindFlags.UnificationFailed; env.set(node.name.text, kind);
// kind.flags |= KindFlags.UnificationFailed;
} else { } else {
kind = matchedKind; kind = matchedKind;
} }
@ -1144,6 +1155,24 @@ export class Checker {
} }
break; break;
} }
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InstanceDeclaration:
{
if (node.constraints !== null) {
for (const constraint of node.constraints.constraints) {
for (const typeExpr of constraint.types) {
this.unifyKind(this.inferKindFromTypeExpression(typeExpr, env), new KStar(), typeExpr);
}
}
}
for (const typeExpr of node.constraint.types) {
this.unifyKind(this.inferKindFromTypeExpression(typeExpr, env), new KStar(), typeExpr);
}
for (const element of node.elements) {
this.inferKind(element, env);
}
break;
}
case SyntaxKind.SourceFile: case SyntaxKind.SourceFile:
{ {
for (const element of node.elements) { for (const element of node.elements) {
@ -1278,6 +1307,26 @@ export class Checker {
break; break;
} }
case SyntaxKind.ClassDeclaration:
{
for (const element of node.elements) {
this.infer(element);
}
break;
}
case SyntaxKind.InstanceDeclaration:
{
const cls = node.getScope().lookup(node.constraint.name.text, Symkind.Typeclass) as ClassDeclaration | null;
if (cls === null) {
this.diagnostics.add(new TypeclassNotFoundDiagnostic(node.constraint.name.text, node.constraint.name));
}
for (const element of node.elements) {
this.infer(element);
}
break;
}
case SyntaxKind.ExpressionStatement: case SyntaxKind.ExpressionStatement:
{ {
this.inferExpression(node.expression); this.inferExpression(node.expression);
@ -1657,6 +1706,20 @@ export class Checker {
return type; return type;
} }
case SyntaxKind.NamedTuplePattern:
{
const scheme = this.lookup(pattern.name, Symkind.Type);
if (scheme === null) {
return this.createTypeVar();
}
let tupleType = pattern.elements.map(p => this.inferBindings(p, typeVars, constraints));
// FIXME not tested
return TApp.build(
new TNominal(scheme.type.node as StructDeclaration | EnumDeclaration, pattern),
tupleType
);
}
case SyntaxKind.LiteralPattern: case SyntaxKind.LiteralPattern:
{ {
let type; let type;
@ -1766,6 +1829,15 @@ export class Checker {
break; break;
} }
case SyntaxKind.InstanceDeclaration:
case SyntaxKind.ClassDeclaration:
{
for (const element of node.elements) {
this.initialize(element, parentEnv);
}
break;
}
case SyntaxKind.LetDeclaration: case SyntaxKind.LetDeclaration:
{ {
const env = node.typeEnv = new TypeEnv(parentEnv); const env = node.typeEnv = new TypeEnv(parentEnv);
@ -1973,17 +2045,15 @@ export class Checker {
const returnType = this.createTypeVar(); const returnType = this.createTypeVar();
context.returnType = returnType; context.returnType = returnType;
const paramTypes = []; const paramTypes = node.params.map(
for (const param of node.params) { param => this.inferBindings(param.pattern, [], [])
const paramType = this.inferBindings(param.pattern, [], []); );
paramTypes.push(paramType);
}
let type = TArrow.build(paramTypes, returnType, node); let type = TArrow.build(paramTypes, returnType, node);
if (node.typeAssert !== null) { if (node.typeAssert !== null) {
this.addConstraint( this.addConstraint(
new CEqual( new CEqual(
this.inferTypeExpression(node.typeAssert.typeExpression), this.inferTypeExpression(node.typeAssert.typeExpression, true),
type, type,
node node
) )
@ -1991,6 +2061,14 @@ export class Checker {
} }
node.inferredType = type; node.inferredType = type;
// if (node.parent!.kind === SyntaxKind.InstanceDeclaration) {
// const inst = node.parent!;
// const cls = inst.getScope().lookup(node.parent!.constraint.name.text, Symkind.Typeclass) as ClassDeclaration;
// const other = cls.lookup(node)! ;
// console.log(describeType(type));
// this.addConstraint(new CEqual(type, other.inferredType!, node));
// }
this.contexts.pop(); this.contexts.pop();
// FIXME get rid of all this useless stack manipulation // FIXME get rid of all this useless stack manipulation
@ -2016,6 +2094,7 @@ export class Checker {
} }
const visitElements = (elements: Syntax[]) => { const visitElements = (elements: Syntax[]) => {
for (const element of elements) { for (const element of elements) {
if (element.kind === SyntaxKind.LetDeclaration if (element.kind === SyntaxKind.LetDeclaration
&& isFunctionDeclarationLike(element)) { && isFunctionDeclarationLike(element)) {

View file

@ -1,5 +1,6 @@
import { assert, JSONObject, JSONValue, MultiMap } from "./util"; import { assert, JSONObject, JSONValue } from "./util";
import type { InferContext, Kind, Scheme, Type, TypeEnv } from "./checker" import { isNodeWithScope, Scope } from "./scope"
import type { InferContext, Kind, KindEnv, Scheme, Type, TypeEnv } from "./checker"
export type TextSpan = [number, number]; export type TextSpan = [number, number];
@ -97,6 +98,8 @@ export const enum SyntaxKind {
MutKeyword, MutKeyword,
ModKeyword, ModKeyword,
ImportKeyword, ImportKeyword,
ClassKeyword,
InstanceKeyword,
StructKeyword, StructKeyword,
EnumKeyword, EnumKeyword,
TypeKeyword, TypeKeyword,
@ -164,6 +167,9 @@ export const enum SyntaxKind {
EnumDeclaration, EnumDeclaration,
ImportDeclaration, ImportDeclaration,
TypeDeclaration, TypeDeclaration,
ClassDeclaration,
InstanceDeclaration,
ModuleDeclaration,
// Let declaration body members // Let declaration body members
ExprBody, ExprBody,
@ -182,14 +188,17 @@ export const enum SyntaxKind {
Initializer, Initializer,
TypeAssert, TypeAssert,
Param, Param,
ModuleDeclaration,
SourceFile, SourceFile,
ClassConstraint,
ClassConstraintClause,
} }
export type Syntax export type Syntax
= SourceFile = SourceFile
| ModuleDeclaration | ModuleDeclaration
| ClassDeclaration
| InstanceDeclaration
| ClassConstraint
| Token | Token
| Param | Param
| Body | Body
@ -208,168 +217,6 @@ function isIgnoredProperty(key: string): boolean {
return key === 'kind' || key === 'parent'; return key === 'kind' || key === 'parent';
} }
type NodeWithScope
= SourceFile
| LetDeclaration
function isNodeWithScope(node: Syntax): node is NodeWithScope {
return node.kind === SyntaxKind.SourceFile
|| node.kind === SyntaxKind.LetDeclaration;
}
export const enum Symkind {
Var = 1,
Type = 2,
Module = 4,
Any = Var | Type | Module
}
export class Scope {
private mapping = new MultiMap<string, [Symkind, Syntax]>();
public constructor(
public node: NodeWithScope,
) {
this.scan(node);
}
public get depth(): number {
let out = 0;
let curr = this.getParent();
while (curr !== null) {
out++;
curr = curr.getParent();
}
return out;
}
private getParent(): Scope | null {
let curr = this.node.parent;
while (curr !== null) {
if (isNodeWithScope(curr)) {
return curr.getScope();
}
curr = curr.parent;
}
return null;
}
private add(name: string, node: Syntax, kind: Symkind): void {
this.mapping.add(name, [kind, node]);
}
private scan(node: Syntax): void {
switch (node.kind) {
case SyntaxKind.SourceFile:
{
for (const element of node.elements) {
this.scan(element);
}
break;
}
case SyntaxKind.ModuleDeclaration:
{
this.add(node.name.text, node, Symkind.Module);
for (const element of node.elements) {
this.scan(element);
}
break;
}
case SyntaxKind.ExpressionStatement:
case SyntaxKind.ReturnStatement:
case SyntaxKind.IfStatement:
break;
case SyntaxKind.TypeDeclaration:
{
this.add(node.name.text, node, Symkind.Type);
break;
}
case SyntaxKind.EnumDeclaration:
{
this.add(node.name.text, node, Symkind.Type);
if (node.members !== null) {
for (const member of node.members) {
this.add(member.name.text, member, Symkind.Var);
}
}
}
case SyntaxKind.StructDeclaration:
{
this.add(node.name.text, node, Symkind.Type);
this.add(node.name.text, node, Symkind.Var);
break;
}
case SyntaxKind.LetDeclaration:
{
for (const param of node.params) {
this.scanPattern(param.pattern, param);
}
if (node === this.node) {
if (node.body !== null && node.body.kind === SyntaxKind.BlockBody) {
for (const element of node.body.elements) {
this.scan(element);
}
}
} else {
if (node.pattern.kind === SyntaxKind.WrappedOperator) {
this.add(node.pattern.operator.text, node, Symkind.Var);
} else {
this.scanPattern(node.pattern, node);
}
}
break;
}
default:
throw new Error(`Unexpected ${node.constructor.name}`);
}
}
private scanPattern(node: Pattern, decl: Syntax): void {
switch (node.kind) {
case SyntaxKind.BindPattern:
{
this.add(node.name.text, decl, Symkind.Var);
break;
}
case SyntaxKind.StructPattern:
{
for (const member of node.members) {
switch (member.kind) {
case SyntaxKind.StructPatternField:
{
this.scanPattern(member.pattern, decl);
break;
}
case SyntaxKind.PunnedStructPatternField:
{
this.add(node.name.text, decl, Symkind.Var);
break;
}
}
}
break;
}
default:
throw new Error(`Unexpected ${node}`);
}
}
public lookup(name: string, expectedKind: Symkind = Symkind.Any): Syntax | null {
let curr: Scope | null = this;
do {
for (const [kind, decl] of curr.mapping.get(name)) {
if (kind & expectedKind) {
return decl;
}
}
curr = curr.getParent();
} while (curr !== null);
return null;
}
}
abstract class SyntaxBase { abstract class SyntaxBase {
public parent: Syntax | null = null; public parent: Syntax | null = null;
@ -957,6 +804,27 @@ export class ImportKeyword extends TokenBase {
} }
export class ClassKeyword extends TokenBase {
public readonly kind = SyntaxKind.ClassKeyword;
public get text(): string {
return 'trait';
}
}
export class InstanceKeyword extends TokenBase {
public readonly kind = SyntaxKind.InstanceKeyword;
public get text(): string {
return 'impl';
}
}
export class TypeKeyword extends TokenBase { export class TypeKeyword extends TokenBase {
public readonly kind = SyntaxKind.TypeKeyword; public readonly kind = SyntaxKind.TypeKeyword;
@ -1042,6 +910,8 @@ export type Token
| MutKeyword | MutKeyword
| ModKeyword | ModKeyword
| ImportKeyword | ImportKeyword
| ClassKeyword
| InstanceKeyword
| TypeKeyword | TypeKeyword
| StructKeyword | StructKeyword
| ReturnKeyword | ReturnKeyword
@ -2246,11 +2116,154 @@ export class Initializer extends SyntaxBase {
} }
export class ClassConstraint extends SyntaxBase {
public readonly kind = SyntaxKind.ClassConstraint;
public constructor(
public name: IdentifierAlt,
public types: TypeExpression[],
) {
super();
}
public getFirstToken(): Token {
return this.name;
}
public getLastToken(): Token {
return this.types[this.types.length-1].getLastToken();
}
}
export class ClassConstraintClause extends SyntaxBase {
public readonly kind = SyntaxKind.ClassConstraintClause;
public constructor(
public constraints: ClassConstraint[],
public rarrowAlt: RArrowAlt,
) {
super();
}
public getFirstToken(): Token {
if (this.constraints.length > 0) {
return this.constraints[0].getFirstToken();
}
return this.rarrowAlt;
}
public getLastToken(): Token {
return this.rarrowAlt;
}
}
export type ClassDeclarationElement
= LetDeclaration
| TypeDeclaration
export class ClassDeclaration extends SyntaxBase {
public readonly kind = SyntaxKind.ClassDeclaration;
public constructor(
public pubKeyword: PubKeyword | null,
public classKeyword: ClassKeyword,
public constraints: ClassConstraintClause | null,
public constraint: ClassConstraint,
public elements: ClassDeclarationElement[],
) {
super();
}
public lookup(element: InstanceDeclarationElement): ClassDeclarationElement | null {
switch (element.kind) {
case SyntaxKind.LetDeclaration:
assert(element.pattern.kind === SyntaxKind.BindPattern);
for (const other of this.elements) {
if (other.kind === SyntaxKind.LetDeclaration
&& other.pattern.kind === SyntaxKind.BindPattern
&& other.pattern.name.text === element.pattern.name.text) {
return other;
}
}
break;
case SyntaxKind.TypeDeclaration:
for (const other of this.elements) {
if (other.kind === SyntaxKind.TypeDeclaration
&& other.name.text === element.name.text) {
return other;
}
}
break;
}
return null;
}
public getFirstToken(): Token {
if (this.pubKeyword !== null) {
return this.pubKeyword;
}
return this.classKeyword;
}
public getLastToken(): Token {
if (this.elements.length > 0) {
return this.elements[this.elements.length-1].getLastToken();
}
return this.constraint.getLastToken();
}
}
export type InstanceDeclarationElement
= LetDeclaration
| TypeDeclaration
export class InstanceDeclaration extends SyntaxBase {
public readonly kind = SyntaxKind.InstanceDeclaration;
public constructor(
public pubKeyword: PubKeyword | null,
public classKeyword: InstanceKeyword,
public constraints: ClassConstraintClause | null,
public constraint: ClassConstraint,
public elements: InstanceDeclarationElement[],
) {
super();
}
public getFirstToken(): Token {
if (this.pubKeyword !== null) {
return this.pubKeyword;
}
return this.classKeyword;
}
public getLastToken(): Token {
if (this.elements.length > 0) {
return this.elements[this.elements.length-1].getLastToken();
}
return this.constraint.getLastToken();
}
}
export class ModuleDeclaration extends SyntaxBase { export class ModuleDeclaration extends SyntaxBase {
public readonly kind = SyntaxKind.ModuleDeclaration; public readonly kind = SyntaxKind.ModuleDeclaration;
public typeEnv?: TypeEnv; public typeEnv?: TypeEnv;
public kindEnv?: KindEnv;
public constructor( public constructor(
public pubKeyword: PubKeyword | null, public pubKeyword: PubKeyword | null,
@ -2281,6 +2294,8 @@ export class ModuleDeclaration extends SyntaxBase {
export type SourceFileElement export type SourceFileElement
= Statement = Statement
| Declaration | Declaration
| ClassDeclaration
| InstanceDeclaration
| ModuleDeclaration | ModuleDeclaration
export class SourceFile extends SyntaxBase { export class SourceFile extends SyntaxBase {
@ -2289,6 +2304,7 @@ export class SourceFile extends SyntaxBase {
public scope?: Scope; public scope?: Scope;
public typeEnv?: TypeEnv; public typeEnv?: TypeEnv;
public kindEnv?: KindEnv;
public constructor( public constructor(
private file: TextFile, private file: TextFile,

View file

@ -51,7 +51,7 @@ export class UnexpectedCharDiagnostic {
const endPos = this.position.clone(); const endPos = this.position.clone();
endPos.advance(this.actual); endPos.advance(this.actual);
out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET); out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
out.write(`unexpeced character sequence '${this.actual}'.\n\n`); out.write(`unexpected character sequence '${this.actual}'.\n\n`);
out.write(printExcerpt(this.file, new TextRange(this.position, endPos)) + '\n'); out.write(printExcerpt(this.file, new TextRange(this.position, endPos)) + '\n');
} }
@ -146,7 +146,27 @@ export class UnexpectedTokenDiagnostic {
} }
export class BindingNotFoudDiagnostic { export class TypeclassNotFoundDiagnostic {
public readonly level = Level.Error;
public constructor(
public name: string,
public node: Syntax,
) {
}
public format(out: IndentWriter): void {
out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
out.write(`could not implement type class because class '${this.name}' was not found.\n\n`);
out.write(printNode(this.node) + '\n');
}
}
export class BindingNotFoundDiagnostic {
public readonly level = Level.Error; public readonly level = Level.Error;
@ -375,7 +395,8 @@ export class ModuleNotFoundDiagnostic {
export type Diagnostic export type Diagnostic
= UnexpectedCharDiagnostic = UnexpectedCharDiagnostic
| BindingNotFoudDiagnostic | TypeclassNotFoundDiagnostic
| BindingNotFoundDiagnostic
| UnificationFailedDiagnostic | UnificationFailedDiagnostic
| UnexpectedTokenDiagnostic | UnexpectedTokenDiagnostic
| FieldMissingDiagnostic | FieldMissingDiagnostic

View file

@ -61,6 +61,11 @@ import {
TupleTypeExpression, TupleTypeExpression,
ModuleDeclaration, ModuleDeclaration,
isExprOperator, isExprOperator,
ClassConstraint,
ClassDeclaration,
ClassKeyword,
InstanceDeclaration,
ClassConstraintClause,
} from "./cst" } from "./cst"
import { Stream } from "./util"; import { Stream } from "./util";
@ -133,7 +138,7 @@ export class Parser {
return this.tokens.peek(offset); return this.tokens.peek(offset);
} }
private assertToken<K extends Token['kind']>(token: Token, expectedKind: K): void { private assertToken<K extends Token['kind']>(token: Token, expectedKind: K): asserts token is Token & { kind: K } {
if (token.kind !== expectedKind) { if (token.kind !== expectedKind) {
this.raiseParseError(token, [ expectedKind ]); this.raiseParseError(token, [ expectedKind ]);
} }
@ -280,6 +285,8 @@ export class Parser {
if (t0.kind !== SyntaxKind.IdentifierAlt || t1.kind !== SyntaxKind.Dot) { if (t0.kind !== SyntaxKind.IdentifierAlt || t1.kind !== SyntaxKind.Dot) {
break; break;
} }
this.getToken();
this.getToken();
modulePath.push([t0, t1]); modulePath.push([t0, t1]);
} }
const name = this.getToken(); const name = this.getToken();
@ -981,6 +988,134 @@ export class Parser {
return new ModuleDeclaration(pubKeyword, t0, name, blockStart, elements); return new ModuleDeclaration(pubKeyword, t0, name, blockStart, elements);
} }
private currentLineFoldHasToken(expectedKind: SyntaxKind): boolean {
for (let i = 1;; i++) {
const t0 = this.peekToken(i);
switch (t0.kind) {
case SyntaxKind.BlockStart:
case SyntaxKind.LineFoldEnd:
case SyntaxKind.EndOfFile:
return false;
case expectedKind:
return true;
}
}
}
private parseClassConstraint(): ClassConstraint {
const name = this.expectToken(SyntaxKind.IdentifierAlt);
const types = [];
for (;;) {
const t1 = this.peekToken();
if (t1.kind === SyntaxKind.Comma
|| t1.kind === SyntaxKind.RArrowAlt
|| t1.kind === SyntaxKind.BlockStart
|| t1.kind === SyntaxKind.LineFoldEnd) {
break;
}
types.push(this.parsePrimitiveTypeExpression());
}
return new ClassConstraint(name, types);
}
public parseInstanceDeclaration(): InstanceDeclaration {
let pubKeyword = null;
let t0 = this.getToken();
if (t0.kind === SyntaxKind.PubKeyword) {
pubKeyword = t0;
t0 = this.getToken();
}
this.assertToken(t0, SyntaxKind.InstanceKeyword);
let clause = null;
if (this.currentLineFoldHasToken(SyntaxKind.RArrowAlt)) {
let rarrowAlt;
const constraints = [];
for (;;) {
constraints.push(this.parseClassConstraint());
const t2 = this.getToken();
if (t2.kind === SyntaxKind.RArrowAlt) {
rarrowAlt = t2;
break;
} else if (t2.kind !== SyntaxKind.Comma) {
this.raiseParseError(t2, [ SyntaxKind.RArrowAlt, SyntaxKind.Comma ])
}
}
clause = new ClassConstraintClause(constraints, rarrowAlt);
}
const constraint = this.parseClassConstraint();
this.expectToken(SyntaxKind.BlockStart);
const elements = [];
loop: for (;;) {
const t3 = this.peekToken();
let element;
switch (t3.kind) {
case SyntaxKind.BlockEnd:
this.getToken();
break loop;
case SyntaxKind.LetKeyword:
element = this.parseLetDeclaration();
break;
case SyntaxKind.TypeKeyword:
element = this.parseTypeDeclaration();
break;
default:
this.raiseParseError(t3, [ SyntaxKind.LetKeyword, SyntaxKind.TypeKeyword, SyntaxKind.BlockEnd ]);
}
elements.push(element);
}
this.expectToken(SyntaxKind.LineFoldEnd);
return new InstanceDeclaration(pubKeyword, t0, clause, constraint, elements);
}
public parseClassDeclaration(): ClassDeclaration {
let pubKeyword = null;
let t0 = this.getToken();
if (t0.kind === SyntaxKind.PubKeyword) {
pubKeyword = t0;
t0 = this.getToken();
}
this.assertToken(t0, SyntaxKind.ClassKeyword);
let clause = null;
if (this.currentLineFoldHasToken(SyntaxKind.RArrowAlt)) {
let rarrowAlt;
const constraints = [];
for (;;) {
constraints.push(this.parseClassConstraint());
const t2 = this.getToken();
if (t2.kind === SyntaxKind.RArrowAlt) {
rarrowAlt = t2;
break;
} else if (t2.kind !== SyntaxKind.Comma) {
this.raiseParseError(t2, [ SyntaxKind.RArrowAlt, SyntaxKind.Comma ])
}
}
clause = new ClassConstraintClause(constraints, rarrowAlt);
}
const constraint = this.parseClassConstraint();
this.expectToken(SyntaxKind.BlockStart);
const elements = [];
loop: for (;;) {
const t3 = this.peekToken();
let element;
switch (t3.kind) {
case SyntaxKind.BlockEnd:
this.getToken();
break loop;
case SyntaxKind.LetKeyword:
element = this.parseLetDeclaration();
break;
case SyntaxKind.TypeKeyword:
element = this.parseTypeDeclaration();
break;
default:
this.raiseParseError(t3, [ SyntaxKind.LetKeyword, SyntaxKind.TypeKeyword, SyntaxKind.BlockEnd ]);
}
elements.push(element);
}
this.expectToken(SyntaxKind.LineFoldEnd);
return new ClassDeclaration(pubKeyword, t0 as ClassKeyword, clause, constraint, elements);
}
public parseSourceFileElement(): SourceFileElement { public parseSourceFileElement(): SourceFileElement {
const t0 = this.peekTokenAfterModifiers(); const t0 = this.peekTokenAfterModifiers();
switch (t0.kind) { switch (t0.kind) {
@ -992,6 +1127,10 @@ export class Parser {
return this.parseImportDeclaration(); return this.parseImportDeclaration();
case SyntaxKind.StructKeyword: case SyntaxKind.StructKeyword:
return this.parseStructDeclaration(); return this.parseStructDeclaration();
case SyntaxKind.InstanceKeyword:
return this.parseInstanceDeclaration();
case SyntaxKind.ClassKeyword:
return this.parseClassDeclaration();
case SyntaxKind.EnumKeyword: case SyntaxKind.EnumKeyword:
return this.parseEnumDeclaration(); return this.parseEnumDeclaration();
case SyntaxKind.TypeKeyword: case SyntaxKind.TypeKeyword:

View file

@ -85,9 +85,10 @@ export class Program {
passes.add(BoltToJS); passes.add(BoltToJS);
break; break;
} }
for (const [filePath, sourceFile] of this.sourceFilesByPath) { for (const [sourceFilePath, sourceFile] of this.sourceFilesByPath) {
const code = passes.apply(sourceFile) as any; const code = passes.apply(sourceFile) as any;
const file = fs.createWriteStream(stripExtension(filePath) + suffix, 'utf-8'); const targetFilePath = stripExtension(sourceFilePath) + suffix;
const file = fs.createWriteStream(targetFilePath, 'utf-8');
code.emit(file); code.emit(file);
} }
} }

View file

@ -42,6 +42,8 @@ import {
VBar, VBar,
ForeignKeyword, ForeignKeyword,
ModKeyword, ModKeyword,
ClassKeyword,
InstanceKeyword,
} from "./cst" } from "./cst"
import { Diagnostics, UnexpectedCharDiagnostic } from "./diagnostics" import { Diagnostics, UnexpectedCharDiagnostic } from "./diagnostics"
import { Stream, BufferedStream, assert } from "./util"; import { Stream, BufferedStream, assert } from "./util";
@ -355,6 +357,8 @@ export class Scanner extends BufferedStream<Token> {
{ {
const text = c0 + this.takeWhile(isIdentPart); const text = c0 + this.takeWhile(isIdentPart);
switch (text) { switch (text) {
case 'trait': return new ClassKeyword(startPos);
case 'impl': return new InstanceKeyword(startPos);
case 'import': return new ImportKeyword(startPos); case 'import': return new ImportKeyword(startPos);
case 'pub': return new PubKeyword(startPos); case 'pub': return new PubKeyword(startPos);
case 'mut': return new MutKeyword(startPos); case 'mut': return new MutKeyword(startPos);

181
src/scope.ts Normal file
View file

@ -0,0 +1,181 @@
import { LetDeclaration, Pattern, SourceFile, Syntax, SyntaxKind } from "./cst";
import { MultiMap } from "./util";
export type NodeWithScope
= SourceFile
| LetDeclaration
export function isNodeWithScope(node: Syntax): node is NodeWithScope {
return node.kind === SyntaxKind.SourceFile
|| node.kind === SyntaxKind.LetDeclaration;
}
export const enum Symkind {
Var = 1,
Type = 2,
Module = 4,
Typeclass = 8,
Any = Var | Type | Module
}
export class Scope {
private mapping = new MultiMap<string, [Symkind, Syntax]>();
public constructor(
public node: NodeWithScope,
) {
this.scan(node);
}
public get depth(): number {
let out = 0;
let curr = this.getParent();
while (curr !== null) {
out++;
curr = curr.getParent();
}
return out;
}
private getParent(): Scope | null {
let curr = this.node.parent;
while (curr !== null) {
if (isNodeWithScope(curr)) {
return curr.getScope();
}
curr = curr.parent;
}
return null;
}
private add(name: string, node: Syntax, kind: Symkind): void {
this.mapping.add(name, [kind, node]);
}
private scan(node: Syntax): void {
switch (node.kind) {
case SyntaxKind.ClassDeclaration:
{
this.add(node.constraint.name.text, node, Symkind.Typeclass);
}
case SyntaxKind.InstanceDeclaration:
case SyntaxKind.SourceFile:
{
for (const element of node.elements) {
this.scan(element);
}
break;
}
case SyntaxKind.ModuleDeclaration:
{
this.add(node.name.text, node, Symkind.Module);
for (const element of node.elements) {
this.scan(element);
}
break;
}
case SyntaxKind.ExpressionStatement:
case SyntaxKind.ReturnStatement:
case SyntaxKind.IfStatement:
break;
case SyntaxKind.TypeDeclaration:
{
this.add(node.name.text, node, Symkind.Type);
break;
}
case SyntaxKind.EnumDeclaration:
{
this.add(node.name.text, node, Symkind.Type);
if (node.members !== null) {
for (const member of node.members) {
this.add(member.name.text, member, Symkind.Var);
}
}
}
case SyntaxKind.StructDeclaration:
{
this.add(node.name.text, node, Symkind.Type);
this.add(node.name.text, node, Symkind.Var);
break;
}
case SyntaxKind.LetDeclaration:
{
for (const param of node.params) {
this.scanPattern(param.pattern, param);
}
if (node === this.node) {
if (node.body !== null && node.body.kind === SyntaxKind.BlockBody) {
for (const element of node.body.elements) {
this.scan(element);
}
}
} else {
if (node.pattern.kind === SyntaxKind.WrappedOperator) {
this.add(node.pattern.operator.text, node, Symkind.Var);
} else {
this.scanPattern(node.pattern, node);
}
}
break;
}
default:
throw new Error(`Unexpected ${node.constructor.name}`);
}
}
private scanPattern(node: Pattern, decl: Syntax): void {
switch (node.kind) {
case SyntaxKind.LiteralPattern:
break;
case SyntaxKind.BindPattern:
{
this.add(node.name.text, decl, Symkind.Var);
break;
}
case SyntaxKind.NamedTuplePattern:
{
for (const element of node.elements) {
this.scanPattern(element, decl);
}
break;
}
case SyntaxKind.StructPattern:
{
for (const member of node.members) {
switch (member.kind) {
case SyntaxKind.StructPatternField:
{
this.scanPattern(member.pattern, decl);
break;
}
case SyntaxKind.PunnedStructPatternField:
{
this.add(node.name.text, decl, Symkind.Var);
break;
}
}
}
break;
}
default:
console.log(node);
throw new Error(`Unexpected ${node}`);
}
}
public lookup(name: string, expectedKind: Symkind = Symkind.Any): Syntax | null {
let curr: Scope | null = this;
do {
for (const [kind, decl] of curr.mapping.get(name)) {
if (kind & expectedKind) {
return decl;
}
}
curr = curr.getParent();
} while (curr !== null);
return null;
}
}