[WIP] Add support for typeclasses
This commit is contained in:
parent
036ccedea3
commit
3e14538d15
10 changed files with 676 additions and 208 deletions
|
@ -1,6 +1,7 @@
|
|||
import { DirectedHashGraph, strongconnect } from "yagl";
|
||||
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
|
||||
= LetDeclaration
|
||||
|
@ -36,6 +37,8 @@ export class Analyser {
|
|||
break;
|
||||
}
|
||||
|
||||
case SyntaxKind.InstanceDeclaration:
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.SourceFile:
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
{
|
||||
|
|
|
@ -23,20 +23,36 @@ program
|
|||
program
|
||||
.command('build', 'Build a set of Bolt sources')
|
||||
.argument('<file>', 'Path to the Bolt program to compile')
|
||||
.option('-t, --target <target-id>', 'What to compile to', 'c')
|
||||
.action((file, opts) => {
|
||||
|
||||
const cwd = opts.workDir;
|
||||
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 ]);
|
||||
if (program.diagnostics.hasError) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
program.check();
|
||||
if (program.diagnostics.hasError) {
|
||||
process.exit(1);
|
||||
}
|
||||
program.emit({ type: TargetType.JS });
|
||||
|
||||
program.emit({ type: targetType });
|
||||
if (program.diagnostics.hasError) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
8
src/c.ts
8
src/c.ts
|
@ -43,7 +43,14 @@ export const enum CBuiltinTypeKind {
|
|||
}
|
||||
|
||||
abstract class CNodeBase {
|
||||
|
||||
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 {
|
||||
|
@ -204,6 +211,7 @@ export class CProgram extends CNodeBase {
|
|||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type CNode
|
||||
|
|
147
src/checker.ts
147
src/checker.ts
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
ClassDeclaration,
|
||||
EnumDeclaration,
|
||||
Expression,
|
||||
ExprOperator,
|
||||
|
@ -11,20 +12,21 @@ import {
|
|||
ReferenceTypeExpression,
|
||||
SourceFile,
|
||||
StructDeclaration,
|
||||
Symkind,
|
||||
Syntax,
|
||||
SyntaxKind,
|
||||
TypeExpression,
|
||||
} from "./cst";
|
||||
import { Symkind } from "./scope"
|
||||
import {
|
||||
describeType,
|
||||
BindingNotFoudDiagnostic,
|
||||
BindingNotFoundDiagnostic,
|
||||
Diagnostics,
|
||||
FieldDoesNotExistDiagnostic,
|
||||
FieldMissingDiagnostic,
|
||||
UnificationFailedDiagnostic,
|
||||
KindMismatchDiagnostic,
|
||||
ModuleNotFoundDiagnostic,
|
||||
TypeclassNotFoundDiagnostic,
|
||||
} from "./diagnostics";
|
||||
import { assert, isEmpty, MultiMap } from "./util";
|
||||
import { Analyser } from "./analysis";
|
||||
|
@ -696,7 +698,7 @@ type NodeWithReference
|
|||
| ReferenceExpression
|
||||
| ReferenceTypeExpression
|
||||
|
||||
export class TypeEnv {
|
||||
class TypeEnv {
|
||||
|
||||
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] {
|
||||
let modulePath: IdentifierAlt[];
|
||||
let name: Identifier | IdentifierAlt | ExprOperator;
|
||||
|
@ -832,7 +836,7 @@ export class Checker {
|
|||
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);
|
||||
if (modulePath.length > 0) {
|
||||
let maxIndex = 0;
|
||||
|
@ -844,12 +848,14 @@ export class Checker {
|
|||
const nextDown = currDown.resolveModule(moduleName.text);
|
||||
if (nextDown === null) {
|
||||
if (currUp.kind === SyntaxKind.SourceFile) {
|
||||
this.diagnostics.add(
|
||||
new ModuleNotFoundDiagnostic(
|
||||
modulePath.slice(maxIndex).map(id => id.text),
|
||||
modulePath[maxIndex],
|
||||
)
|
||||
);
|
||||
if (emitDiagnostic) {
|
||||
this.diagnostics.add(
|
||||
new ModuleNotFoundDiagnostic(
|
||||
modulePath.slice(maxIndex).map(id => id.text),
|
||||
modulePath[maxIndex],
|
||||
)
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
currUp = currUp.getEnclosingModule();
|
||||
|
@ -862,13 +868,15 @@ export class Checker {
|
|||
if (found !== null) {
|
||||
return found;
|
||||
}
|
||||
this.diagnostics.add(
|
||||
new BindingNotFoudDiagnostic(
|
||||
modulePath.map(id => id.text),
|
||||
name.text,
|
||||
name,
|
||||
)
|
||||
);
|
||||
if (emitDiagnostic) {
|
||||
this.diagnostics.add(
|
||||
new BindingNotFoundDiagnostic(
|
||||
modulePath.map(id => id.text),
|
||||
name.text,
|
||||
name,
|
||||
)
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
|
@ -880,13 +888,15 @@ export class Checker {
|
|||
}
|
||||
curr = curr.parent;
|
||||
} while(curr !== null);
|
||||
this.diagnostics.add(
|
||||
new BindingNotFoudDiagnostic(
|
||||
[],
|
||||
name.text,
|
||||
name,
|
||||
)
|
||||
);
|
||||
if (emitDiagnostic) {
|
||||
this.diagnostics.add(
|
||||
new BindingNotFoundDiagnostic(
|
||||
[],
|
||||
name.text,
|
||||
name,
|
||||
)
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -922,7 +932,7 @@ export class Checker {
|
|||
return found;
|
||||
}
|
||||
this.diagnostics.add(
|
||||
new BindingNotFoudDiagnostic(
|
||||
new BindingNotFoundDiagnostic(
|
||||
modulePath.map(id => id.text),
|
||||
name.text,
|
||||
name,
|
||||
|
@ -940,7 +950,7 @@ export class Checker {
|
|||
curr = curr.parent;
|
||||
} while(curr !== null);
|
||||
this.diagnostics.add(
|
||||
new BindingNotFoudDiagnostic(
|
||||
new BindingNotFoundDiagnostic(
|
||||
[],
|
||||
name.text,
|
||||
name,
|
||||
|
@ -1013,12 +1023,13 @@ export class Checker {
|
|||
}
|
||||
case SyntaxKind.VarTypeExpression:
|
||||
{
|
||||
const matchedKind = this.lookupKind(env, node.name);
|
||||
const matchedKind = this.lookupKind(env, node.name, false);
|
||||
if (matchedKind === null) {
|
||||
// this.diagnostics.add(new BindingNotFoudDiagnostic([], node.name.text, node.name));
|
||||
// Create a filler kind variable that still will be able to catch other errors.
|
||||
kind = this.createKindVar();
|
||||
kind.flags |= KindFlags.UnificationFailed;
|
||||
env.set(node.name.text, kind);
|
||||
// kind.flags |= KindFlags.UnificationFailed;
|
||||
} else {
|
||||
kind = matchedKind;
|
||||
}
|
||||
|
@ -1144,6 +1155,24 @@ export class Checker {
|
|||
}
|
||||
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:
|
||||
{
|
||||
for (const element of node.elements) {
|
||||
|
@ -1278,6 +1307,26 @@ export class Checker {
|
|||
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:
|
||||
{
|
||||
this.inferExpression(node.expression);
|
||||
|
@ -1657,6 +1706,20 @@ export class Checker {
|
|||
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:
|
||||
{
|
||||
let type;
|
||||
|
@ -1766,6 +1829,15 @@ export class Checker {
|
|||
break;
|
||||
}
|
||||
|
||||
case SyntaxKind.InstanceDeclaration:
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
{
|
||||
for (const element of node.elements) {
|
||||
this.initialize(element, parentEnv);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SyntaxKind.LetDeclaration:
|
||||
{
|
||||
const env = node.typeEnv = new TypeEnv(parentEnv);
|
||||
|
@ -1973,17 +2045,15 @@ export class Checker {
|
|||
const returnType = this.createTypeVar();
|
||||
context.returnType = returnType;
|
||||
|
||||
const paramTypes = [];
|
||||
for (const param of node.params) {
|
||||
const paramType = this.inferBindings(param.pattern, [], []);
|
||||
paramTypes.push(paramType);
|
||||
}
|
||||
const paramTypes = node.params.map(
|
||||
param => this.inferBindings(param.pattern, [], [])
|
||||
);
|
||||
|
||||
let type = TArrow.build(paramTypes, returnType, node);
|
||||
if (node.typeAssert !== null) {
|
||||
this.addConstraint(
|
||||
new CEqual(
|
||||
this.inferTypeExpression(node.typeAssert.typeExpression),
|
||||
this.inferTypeExpression(node.typeAssert.typeExpression, true),
|
||||
type,
|
||||
node
|
||||
)
|
||||
|
@ -1991,6 +2061,14 @@ export class Checker {
|
|||
}
|
||||
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();
|
||||
|
||||
// FIXME get rid of all this useless stack manipulation
|
||||
|
@ -2016,6 +2094,7 @@ export class Checker {
|
|||
}
|
||||
|
||||
const visitElements = (elements: Syntax[]) => {
|
||||
|
||||
for (const element of elements) {
|
||||
if (element.kind === SyntaxKind.LetDeclaration
|
||||
&& isFunctionDeclarationLike(element)) {
|
||||
|
|
348
src/cst.ts
348
src/cst.ts
|
@ -1,5 +1,6 @@
|
|||
import { assert, JSONObject, JSONValue, MultiMap } from "./util";
|
||||
import type { InferContext, Kind, Scheme, Type, TypeEnv } from "./checker"
|
||||
import { assert, JSONObject, JSONValue } from "./util";
|
||||
import { isNodeWithScope, Scope } from "./scope"
|
||||
import type { InferContext, Kind, KindEnv, Scheme, Type, TypeEnv } from "./checker"
|
||||
|
||||
export type TextSpan = [number, number];
|
||||
|
||||
|
@ -97,6 +98,8 @@ export const enum SyntaxKind {
|
|||
MutKeyword,
|
||||
ModKeyword,
|
||||
ImportKeyword,
|
||||
ClassKeyword,
|
||||
InstanceKeyword,
|
||||
StructKeyword,
|
||||
EnumKeyword,
|
||||
TypeKeyword,
|
||||
|
@ -164,6 +167,9 @@ export const enum SyntaxKind {
|
|||
EnumDeclaration,
|
||||
ImportDeclaration,
|
||||
TypeDeclaration,
|
||||
ClassDeclaration,
|
||||
InstanceDeclaration,
|
||||
ModuleDeclaration,
|
||||
|
||||
// Let declaration body members
|
||||
ExprBody,
|
||||
|
@ -182,14 +188,17 @@ export const enum SyntaxKind {
|
|||
Initializer,
|
||||
TypeAssert,
|
||||
Param,
|
||||
ModuleDeclaration,
|
||||
SourceFile,
|
||||
|
||||
ClassConstraint,
|
||||
ClassConstraintClause,
|
||||
}
|
||||
|
||||
export type Syntax
|
||||
= SourceFile
|
||||
| ModuleDeclaration
|
||||
| ClassDeclaration
|
||||
| InstanceDeclaration
|
||||
| ClassConstraint
|
||||
| Token
|
||||
| Param
|
||||
| Body
|
||||
|
@ -208,168 +217,6 @@ function isIgnoredProperty(key: string): boolean {
|
|||
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 {
|
||||
|
||||
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 {
|
||||
|
||||
public readonly kind = SyntaxKind.TypeKeyword;
|
||||
|
@ -1042,6 +910,8 @@ export type Token
|
|||
| MutKeyword
|
||||
| ModKeyword
|
||||
| ImportKeyword
|
||||
| ClassKeyword
|
||||
| InstanceKeyword
|
||||
| TypeKeyword
|
||||
| StructKeyword
|
||||
| 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 {
|
||||
|
||||
public readonly kind = SyntaxKind.ModuleDeclaration;
|
||||
|
||||
public typeEnv?: TypeEnv;
|
||||
public kindEnv?: KindEnv;
|
||||
|
||||
public constructor(
|
||||
public pubKeyword: PubKeyword | null,
|
||||
|
@ -2281,6 +2294,8 @@ export class ModuleDeclaration extends SyntaxBase {
|
|||
export type SourceFileElement
|
||||
= Statement
|
||||
| Declaration
|
||||
| ClassDeclaration
|
||||
| InstanceDeclaration
|
||||
| ModuleDeclaration
|
||||
|
||||
export class SourceFile extends SyntaxBase {
|
||||
|
@ -2289,6 +2304,7 @@ export class SourceFile extends SyntaxBase {
|
|||
|
||||
public scope?: Scope;
|
||||
public typeEnv?: TypeEnv;
|
||||
public kindEnv?: KindEnv;
|
||||
|
||||
public constructor(
|
||||
private file: TextFile,
|
||||
|
|
|
@ -51,7 +51,7 @@ export class UnexpectedCharDiagnostic {
|
|||
const endPos = this.position.clone();
|
||||
endPos.advance(this.actual);
|
||||
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');
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -375,7 +395,8 @@ export class ModuleNotFoundDiagnostic {
|
|||
|
||||
export type Diagnostic
|
||||
= UnexpectedCharDiagnostic
|
||||
| BindingNotFoudDiagnostic
|
||||
| TypeclassNotFoundDiagnostic
|
||||
| BindingNotFoundDiagnostic
|
||||
| UnificationFailedDiagnostic
|
||||
| UnexpectedTokenDiagnostic
|
||||
| FieldMissingDiagnostic
|
||||
|
|
141
src/parser.ts
141
src/parser.ts
|
@ -61,6 +61,11 @@ import {
|
|||
TupleTypeExpression,
|
||||
ModuleDeclaration,
|
||||
isExprOperator,
|
||||
ClassConstraint,
|
||||
ClassDeclaration,
|
||||
ClassKeyword,
|
||||
InstanceDeclaration,
|
||||
ClassConstraintClause,
|
||||
} from "./cst"
|
||||
import { Stream } from "./util";
|
||||
|
||||
|
@ -133,7 +138,7 @@ export class Parser {
|
|||
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) {
|
||||
this.raiseParseError(token, [ expectedKind ]);
|
||||
}
|
||||
|
@ -280,6 +285,8 @@ export class Parser {
|
|||
if (t0.kind !== SyntaxKind.IdentifierAlt || t1.kind !== SyntaxKind.Dot) {
|
||||
break;
|
||||
}
|
||||
this.getToken();
|
||||
this.getToken();
|
||||
modulePath.push([t0, t1]);
|
||||
}
|
||||
const name = this.getToken();
|
||||
|
@ -981,6 +988,134 @@ export class Parser {
|
|||
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 {
|
||||
const t0 = this.peekTokenAfterModifiers();
|
||||
switch (t0.kind) {
|
||||
|
@ -992,6 +1127,10 @@ export class Parser {
|
|||
return this.parseImportDeclaration();
|
||||
case SyntaxKind.StructKeyword:
|
||||
return this.parseStructDeclaration();
|
||||
case SyntaxKind.InstanceKeyword:
|
||||
return this.parseInstanceDeclaration();
|
||||
case SyntaxKind.ClassKeyword:
|
||||
return this.parseClassDeclaration();
|
||||
case SyntaxKind.EnumKeyword:
|
||||
return this.parseEnumDeclaration();
|
||||
case SyntaxKind.TypeKeyword:
|
||||
|
|
|
@ -85,9 +85,10 @@ export class Program {
|
|||
passes.add(BoltToJS);
|
||||
break;
|
||||
}
|
||||
for (const [filePath, sourceFile] of this.sourceFilesByPath) {
|
||||
for (const [sourceFilePath, sourceFile] of this.sourceFilesByPath) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,8 @@ import {
|
|||
VBar,
|
||||
ForeignKeyword,
|
||||
ModKeyword,
|
||||
ClassKeyword,
|
||||
InstanceKeyword,
|
||||
} from "./cst"
|
||||
import { Diagnostics, UnexpectedCharDiagnostic } from "./diagnostics"
|
||||
import { Stream, BufferedStream, assert } from "./util";
|
||||
|
@ -355,6 +357,8 @@ export class Scanner extends BufferedStream<Token> {
|
|||
{
|
||||
const text = c0 + this.takeWhile(isIdentPart);
|
||||
switch (text) {
|
||||
case 'trait': return new ClassKeyword(startPos);
|
||||
case 'impl': return new InstanceKeyword(startPos);
|
||||
case 'import': return new ImportKeyword(startPos);
|
||||
case 'pub': return new PubKeyword(startPos);
|
||||
case 'mut': return new MutKeyword(startPos);
|
||||
|
|
181
src/scope.ts
Normal file
181
src/scope.ts
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in a new issue