diff --git a/src/analysis.ts b/src/analysis.ts index b0c366b13..644cb0c2d 100644 --- a/src/analysis.ts +++ b/src/analysis.ts @@ -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: { diff --git a/src/bin/bolt.ts b/src/bin/bolt.ts index 811fe927e..7f720adf7 100644 --- a/src/bin/bolt.ts +++ b/src/bin/bolt.ts @@ -23,20 +23,36 @@ program program .command('build', 'Build a set of Bolt sources') .argument('', 'Path to the Bolt program to compile') + .option('-t, --target ', '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); } diff --git a/src/c.ts b/src/c.ts index 8546f505d..07a44cf6d 100644 --- a/src/c.ts +++ b/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 diff --git a/src/checker.ts b/src/checker.ts index c77d550ab..4440639b3 100644 --- a/src/checker.ts +++ b/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(); @@ -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)) { diff --git a/src/cst.ts b/src/cst.ts index 2c34fad8c..cc7324a9a 100644 --- a/src/cst.ts +++ b/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(); - - 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, diff --git a/src/diagnostics.ts b/src/diagnostics.ts index bc0943c50..1251c75bd 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -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 diff --git a/src/parser.ts b/src/parser.ts index 31778e536..0bf9676d0 100644 --- a/src/parser.ts +++ b/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(token: Token, expectedKind: K): void { + private assertToken(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: diff --git a/src/program.ts b/src/program.ts index e47bbdc9f..0e27316ae 100644 --- a/src/program.ts +++ b/src/program.ts @@ -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); } } diff --git a/src/scanner.ts b/src/scanner.ts index f4babeed4..11ba4a373 100644 --- a/src/scanner.ts +++ b/src/scanner.ts @@ -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 { { 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); diff --git a/src/scope.ts b/src/scope.ts new file mode 100644 index 000000000..44f519feb --- /dev/null +++ b/src/scope.ts @@ -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(); + + 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; + } + +} +