Add extensible records and add foundations for typeclasses

This commit is contained in:
Sam Vervaeck 2023-03-11 14:24:02 +01:00
parent f995d887e7
commit df5f857905
10 changed files with 1062 additions and 473 deletions

View file

@ -2,11 +2,16 @@
import "source-map-support/register" import "source-map-support/register"
import fs from "fs"
import util from "util" import util from "util"
import path from "path" import path from "path"
import { Command } from "commander" import { Command } from "commander"
import { Program, TargetType } from "../program" import { PassManager, Program, TargetType } from "../program"
import { TypeclassDictPassing } from "../passes/TypeclassDictPass"
import BoltToC from "../passes/BoltToC"
import BoltToJS from "../passes/BoltToJS"
import { stripExtension } from "../util"
function debug(value: any) { function debug(value: any) {
console.error(util.inspect(value, { colors: true, depth: Infinity })); console.error(util.inspect(value, { colors: true, depth: Infinity }));
@ -23,14 +28,21 @@ 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('--no-typecheck', 'Skip type-checking')
.option('--no-emit', 'Do not output compiled files')
.option('-t, --target <target-id>', 'What to compile to', 'c') .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);
const shouldTypecheck = opts.typecheck;
const shouldEmit = opts.emit;
let targetType: TargetType; let targetType: TargetType;
switch (opts.target) { switch (opts.target) {
case 'bolt':
targetType = TargetType.Bolt;
break;
case 'js': case 'js':
targetType = TargetType.JS; targetType = TargetType.JS;
break; break;
@ -47,14 +59,44 @@ program
process.exit(1); process.exit(1);
} }
program.check(); if (shouldTypecheck) {
if (program.diagnostics.hasError) { program.check();
process.exit(1); if (program.diagnostics.hasError) {
process.exit(1);
}
} }
program.emit({ type: targetType }); if (shouldEmit) {
if (program.diagnostics.hasError) {
process.exit(1); const passes = new PassManager();
passes.add(TypeclassDictPassing);
let suffix;
switch (targetType) {
case TargetType.Bolt:
suffix = '.gen.bolt';
break;
case TargetType.C:
suffix = '.c';
passes.add(BoltToC);
break;
case TargetType.JS:
suffix = '.js'
passes.add(BoltToJS);
break;
}
for (const sourceFile of program.getSourceFiles()) {
const code = passes.apply(sourceFile);
const targetFilePath = stripExtension(sourceFile.getFile().getFullPath()) + suffix;
const file = fs.createWriteStream(targetFilePath, 'utf-8');
code.emit(file);
}
if (program.diagnostics.hasError) {
process.exit(1);
}
} }
}); });

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,11 @@
import { assert, JSONObject, JSONValue } from "./util";
import type stream from "stream";
import path from "path"
import { assert, IndentWriter, JSONObject, JSONValue } from "./util";
import { isNodeWithScope, Scope } from "./scope" import { isNodeWithScope, Scope } from "./scope"
import type { InferContext, Kind, KindEnv, Scheme, Type, TypeEnv } from "./checker" import { InferContext, Kind, KindEnv, Scheme, Type, TypeEnv } from "./checker"
import { array, middleware } from "yargs"; import { Emitter } from "./emitter";
import { warn } from "console";
export type TextSpan = [number, number]; export type TextSpan = [number, number];
@ -70,6 +73,10 @@ export class TextFile {
} }
public getFullPath(): string {
return path.resolve(this.origPath);
}
} }
export const enum SyntaxKind { export const enum SyntaxKind {
@ -126,7 +133,7 @@ export const enum SyntaxKind {
TupleTypeExpression, TupleTypeExpression,
// Patterns // Patterns
BindPattern, NamedPattern,
TuplePattern, TuplePattern,
StructPattern, StructPattern,
NestedPattern, NestedPattern,
@ -308,19 +315,64 @@ abstract class SyntaxBase {
} }
public *getTokens(): Iterable<Token> {
for (const [_, value] of this.getFields()) {
yield* filter(value);
}
function* filter(value: any): Iterable<Token> {
if (isToken(value)) {
yield value;
} else if (Array.isArray(value)) {
for (const element of value) {
yield* filter(element);
}
} else if (isSyntax(value)) {
yield* value.getTokens();
}
}
}
public *getFields(): Iterable<[string, any]> {
for (const key of Object.getOwnPropertyNames(this)) {
if (!isIgnoredProperty(key)) {
yield [key, (this as any)[key]];
}
}
}
public *getChildNodes(): Iterable<Syntax> {
function* visit(value: any): Iterable<Syntax> {
if (value === null) {
return;
}
if (Array.isArray(value)) {
for (const element of value) {
yield* visit(element);
}
} else if (isSyntax(value)) {
yield value;
}
}
for (const [_key, value] of this.getFields()) {
yield* visit(value);
}
}
public emit(file: stream.Writable): void {
const emitter = new Emitter(new IndentWriter(file));
emitter.emit(this as any);
}
public toJSON(): JSONObject { public toJSON(): JSONObject {
const obj: JSONObject = {}; const obj: JSONObject = {};
obj['type'] = this.constructor.name; obj['type'] = this.constructor.name;
for (const [key, value] of this.getFields()) {
for (const key of Object.getOwnPropertyNames(this)) {
if (isIgnoredProperty(key)) { if (isIgnoredProperty(key)) {
continue; continue;
} }
obj[key] = encode((this as any)[key]); obj[key] = encode(value);
} }
return obj; return obj;
function encode(value: any): JSONValue { function encode(value: any): JSONValue {
@ -328,7 +380,7 @@ abstract class SyntaxBase {
return null; return null;
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {
return value.map(encode); return value.map(encode);
} else if (value instanceof SyntaxBase) { } else if (isSyntax(value)) {
return value.toJSON(); return value.toJSON();
} else { } else {
return value; return value;
@ -1329,9 +1381,9 @@ export type TypeExpression
| NestedTypeExpression | NestedTypeExpression
| TupleTypeExpression | TupleTypeExpression
export class BindPattern extends SyntaxBase { export class NamedPattern extends SyntaxBase {
public readonly kind = SyntaxKind.BindPattern; public readonly kind = SyntaxKind.NamedPattern;
public constructor( public constructor(
public name: Identifier, public name: Identifier,
@ -1339,8 +1391,8 @@ export class BindPattern extends SyntaxBase {
super(); super();
} }
public clone(): BindPattern { public clone(): NamedPattern {
return new BindPattern( this.name.clone() ); return new NamedPattern( this.name.clone() );
} }
public get isHole(): boolean { public get isHole(): boolean {
@ -1513,7 +1565,6 @@ export class StructPattern extends SyntaxBase {
public readonly kind = SyntaxKind.StructPattern; public readonly kind = SyntaxKind.StructPattern;
public constructor( public constructor(
public name: IdentifierAlt,
public lbrace: LBrace, public lbrace: LBrace,
public members: StructPatternElement[], public members: StructPatternElement[],
public rbrace: RBrace, public rbrace: RBrace,
@ -1523,7 +1574,6 @@ export class StructPattern extends SyntaxBase {
public clone(): StructPattern { public clone(): StructPattern {
return new StructPattern( return new StructPattern(
this.name.clone(),
this.lbrace.clone(), this.lbrace.clone(),
this.members.map(member => member.clone()), this.members.map(member => member.clone()),
this.rbrace.clone(), this.rbrace.clone(),
@ -1531,7 +1581,7 @@ export class StructPattern extends SyntaxBase {
} }
public getFirstToken(): Token { public getFirstToken(): Token {
return this.name; return this.lbrace;
} }
public getLastToken(): Token { public getLastToken(): Token {
@ -1626,7 +1676,7 @@ export class LiteralPattern extends SyntaxBase {
} }
export type Pattern export type Pattern
= BindPattern = NamedPattern
| NestedPattern | NestedPattern
| StructPattern | StructPattern
| NamedTuplePattern | NamedTuplePattern
@ -2793,10 +2843,10 @@ export class ClassDeclaration extends SyntaxBase {
switch (element.kind) { switch (element.kind) {
case SyntaxKind.LetDeclaration: case SyntaxKind.LetDeclaration:
assert(element.pattern.kind === SyntaxKind.BindPattern); assert(element.pattern.kind === SyntaxKind.NamedPattern);
for (const other of this.elements) { for (const other of this.elements) {
if (other.kind === SyntaxKind.LetDeclaration if (other.kind === SyntaxKind.LetDeclaration
&& other.pattern.kind === SyntaxKind.BindPattern && other.pattern.kind === SyntaxKind.NamedPattern
&& other.pattern.name.text === element.pattern.name.text) { && other.pattern.name.text === element.pattern.name.text) {
return other; return other;
} }
@ -2818,6 +2868,20 @@ export class ClassDeclaration extends SyntaxBase {
} }
public *getInstances(): Iterable<InstanceDeclaration> {
let curr = this.parent!;
for (;;) {
if (!canHaveInstanceDeclaration(curr)) {
curr = curr.parent!;
}
for (const element of getElements(curr)) {
if (element.kind === SyntaxKind.InstanceDeclaration && element.constraint.name === this.constraint.name) {
yield element;
}
}
}
}
public clone(): ClassDeclaration { public clone(): ClassDeclaration {
return new ClassDeclaration( return new ClassDeclaration(
this.pubKeyword !== null ? this.pubKeyword.clone() : null, this.pubKeyword !== null ? this.pubKeyword.clone() : null,
@ -2980,3 +3044,77 @@ export class SourceFile extends SyntaxBase {
} }
} }
export function isSyntax(value: any): value is Syntax {
return typeof value === 'object'
&& value !== null
&& value instanceof SyntaxBase;
}
export function isToken(value: any): value is Token {
return typeof value === 'object'
&& value !== null
&& value instanceof TokenBase;
}
export function vistEachChild<T extends Syntax>(node: T, proc: (node: Syntax) => Syntax | undefined): Syntax {
const newArgs = [];
let changed = false;
const traverse = (value: any): any => {
if (Array.isArray(value)) {
const newElements = [];
let changed = false;
for (const element of value) {
const newElement = traverse(element);
if (newElement !== element) {
changed = true;
}
newElements.push(newElement);
}
return changed ? newElements : value;
} else if (isSyntax(value)) {
let newValue = proc(value);
if (newValue === undefined) {
newValue = value;
}
if (newValue !== value) {
changed = true;
}
return newValue;
} else {
return value;
}
}
for (const [_key, value] of node.getFields()) {
newArgs.push(traverse(value));
}
if (!changed) {
return node;
}
return new node.constructor(...newArgs);
}
export function canHaveInstanceDeclaration(node: Syntax): boolean {
return node.kind === SyntaxKind.SourceFile
|| node.kind === SyntaxKind.ModuleDeclaration
|| node.kind === SyntaxKind.LetDeclaration;
}
export function getElements(node: Syntax): Iterable<Syntax> {
switch (node.kind) {
case SyntaxKind.SourceFile:
case SyntaxKind.ModuleDeclaration:
return node.elements;
case SyntaxKind.LetDeclaration:
if (node.body !== null && node.body.kind === SyntaxKind.BlockBody) {
return node.body.elements;
}
// falls through
default:
return [];
}
}

View file

@ -221,26 +221,30 @@ export function describeType(type: Type): string {
{ {
return type.decl.name.text; return type.decl.name.text;
} }
case TypeKind.Record: case TypeKind.Field:
{ {
let out = '{ '; let out = '{ ' + type.name + ': ' + describeType(type.type);
let first = true; type = type.restType;
for (const [fieldName, fieldType] of type.fields) { while (type.kind === TypeKind.Field) {
if (first) first = false; out += '; ' + type.name + ': ' + describeType(type.type);
else out += ', '; type = type.restType;
out += fieldName + ': ' + describeType(fieldType);
} }
return out + ' }';
} if (type.kind !== TypeKind.Nil) {
case TypeKind.Labeled: out += '; ' + describeType(type);
{ }
// FIXME may need to include fields that were added during unification return out + ' }'
return '{ ' + type.name + ': ' + describeType(type.type) + ' }';
} }
case TypeKind.App: case TypeKind.App:
{ {
return describeType(type.right) + ' ' + describeType(type.left); return describeType(type.right) + ' ' + describeType(type.left);
} }
case TypeKind.Nil:
return '{}';
case TypeKind.Absent:
return 'Abs';
case TypeKind.Present:
return describeType(type.type);
} }
} }
@ -271,6 +275,7 @@ export class UnificationFailedDiagnostic {
public left: Type, public left: Type,
public right: Type, public right: Type,
public nodes: Syntax[], public nodes: Syntax[],
public path: string[],
) { ) {
} }
@ -281,7 +286,11 @@ export class UnificationFailedDiagnostic {
const node = this.nodes[0]; const node = this.nodes[0];
out.write(ANSI_FG_RED + ANSI_BOLD + `error: ` + ANSI_RESET); out.write(ANSI_FG_RED + ANSI_BOLD + `error: ` + ANSI_RESET);
out.write(`unification of ` + ANSI_FG_GREEN + describeType(this.left) + ANSI_RESET); out.write(`unification of ` + ANSI_FG_GREEN + describeType(this.left) + ANSI_RESET);
out.write(' and ' + ANSI_FG_GREEN + describeType(this.right) + ANSI_RESET + ' failed.\n\n'); out.write(' and ' + ANSI_FG_GREEN + describeType(this.right) + ANSI_RESET + ' failed');
if (this.path.length > 0) {
out.write(` in field '${this.path.join('.')}'`);
}
out.write('.\n\n');
out.write(printNode(node) + '\n'); out.write(printNode(node) + '\n');
for (let i = 1; i < this.nodes.length; i++) { for (let i = 1; i < this.nodes.length; i++) {
const node = this.nodes[i]; const node = this.nodes[i];
@ -311,43 +320,34 @@ export class FieldMissingDiagnostic {
public readonly level = Level.Error; public readonly level = Level.Error;
public constructor( public constructor(
public recordType: Type,
public fieldName: string, public fieldName: string,
public node: Syntax | null, public missing: Syntax | null,
public present: Syntax | null,
public cause: Syntax | null = null,
) { ) {
} }
public format(out: IndentWriter): void { public format(out: IndentWriter): void {
out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET); out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
out.write(`field '${this.fieldName}' is missing from `); out.write(`field '${this.fieldName}' is required in one type but missing in another\n\n`);
out.write(describeType(this.recordType) + '\n\n'); out.indent();
if (this.node !== null) { if (this.missing !== null) {
out.write(printNode(this.node) + '\n'); out.write(ANSI_FG_YELLOW + ANSI_BOLD + 'info: ' + ANSI_RESET);
out.write(`field '${this.fieldName}' is missing in this construct\n\n`);
out.write(printNode(this.missing) + '\n');
} }
} if (this.present !== null) {
out.write(ANSI_FG_YELLOW + ANSI_BOLD + 'info: ' + ANSI_RESET);
} out.write(`field '${this.fieldName}' is required in this construct\n\n`);
out.write(printNode(this.present) + '\n');
export class FieldDoesNotExistDiagnostic {
public readonly level = Level.Error;
public constructor(
public recordType: TRecord,
public fieldName: string,
public node: Syntax | null,
) {
}
public format(out: IndentWriter): void {
out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
out.write(`field '${this.fieldName}' does not exist on type `);
out.write(describeType(this.recordType) + '\n\n');
if (this.node !== null) {
out.write(printNode(this.node) + '\n');
} }
if (this.cause !== null) {
out.write(ANSI_FG_YELLOW + ANSI_BOLD + 'info: ' + ANSI_RESET);
out.write(`because of a constraint on this node:\n\n`);
out.write(printNode(this.cause) + '\n');
}
out.dedent();
} }
} }
@ -400,7 +400,6 @@ export type Diagnostic
| UnificationFailedDiagnostic | UnificationFailedDiagnostic
| UnexpectedTokenDiagnostic | UnexpectedTokenDiagnostic
| FieldMissingDiagnostic | FieldMissingDiagnostic
| FieldDoesNotExistDiagnostic
| KindMismatchDiagnostic | KindMismatchDiagnostic
| ModuleNotFoundDiagnostic | ModuleNotFoundDiagnostic

197
src/emitter.ts Normal file
View file

@ -0,0 +1,197 @@
import { Syntax, SyntaxKind } from "./cst";
import { IndentWriter, assertNever } from "./util";
export class Emitter {
public constructor(
public writer: IndentWriter,
) {
}
public emit(node: Syntax): void {
switch (node.kind) {
case SyntaxKind.ModuleDeclaration:
this.writer.write(`mod ${node.name.text}`);
if (node.elements === null) {
this.writer.write('\n');
break;
}
this.writer.write('.\n');
this.writer.indent();
for (const element of node.elements) {
this.emit(element);
}
this.writer.dedent();
break;
case SyntaxKind.ReferenceExpression:
for (const [name, _dot] of node.modulePath) {
this.writer.write(name.text);
this.writer.write('.');
}
this.writer.write(node.name.text);
break;
case SyntaxKind.CallExpression:
this.emit(node.func);
for (const arg of node.args) {
this.writer.write(' ');
this.emit(arg);
}
break;
case SyntaxKind.ReferenceTypeExpression:
for (const [name, _dot] of node.modulePath) {
this.writer.write(name.text);
this.writer.write('.');
}
this.writer.write(node.name.text);
break;
case SyntaxKind.StructExpressionField:
this.writer.write(node.name.text);
this.writer.write(' = ');
this.emit(node.expression);
break;
case SyntaxKind.StructExpression:
this.writer.write('{ ');
for (const member of node.members) {
this.emit(member);
this.writer.write(', ');
}
this.writer.write(' }');
break;
case SyntaxKind.ConstantExpression:
this.writer.write(node.token.text);
break;
case SyntaxKind.FunctionExpression:
this.writer.write('\\');
for (const param of node.params) {
this.emit(param);
this.writer.write(' ');
}
this.emit(node.body);
break;
case SyntaxKind.ArrowTypeExpression:
for (const typeExpr of node.paramTypeExprs) {
this.emit(typeExpr);
this.writer.write(' -> ');
}
this.emit(node.returnTypeExpr);
break;
case SyntaxKind.VarTypeExpression:
this.writer.write(node.name.text);
break;
case SyntaxKind.Param:
this.emit(node.pattern);
break;
case SyntaxKind.NamedPattern:
this.writer.write(node.name.text);
break;
case SyntaxKind.ExpressionStatement:
this.emit(node.expression);
this.writer.write('\n');
break;
case SyntaxKind.SourceFile:
for (const element of node.elements) {
this.emit(element);
}
break;
case SyntaxKind.TypeAssert:
this.writer.write(': ');
this.emit(node.typeExpression);
break;
case SyntaxKind.ExprBody:
this.writer.write(node.equals.text);
this.writer.write(' ');
this.emit(node.expression);
break
case SyntaxKind.BlockBody:
this.writer.write('.\n');
this.writer.indent();
for (const element of node.elements) {
this.emit(element);
}
this.writer.dedent();
break;
case SyntaxKind.LetDeclaration:
if (node.pubKeyword) {
this.writer.write('pub ');
}
this.writer.write('let ');
if (node.mutKeyword) {
this.writer.write(' mut ');
}
this.emit(node.pattern);
this.writer.write(' ');
for (const param of node.params) {
this.emit(param);
this.writer.write(' ');
}
if (node.typeAssert) {
this.emit(node.typeAssert);
this.writer.write(' ');
}
if (node.body) {
this.emit(node.body);
}
this.writer.write('\n\n');
break;
case SyntaxKind.ClassConstraint:
this.writer.write(node.name.text);
for (const type of node.types) {
this.writer.write(' ');
this.emit(type);
}
break;
case SyntaxKind.ClassDeclaration:
if (node.pubKeyword) {
this.writer.write('pub ');
}
this.writer.write(`class `);
if (node.constraints) {
for (const constraint of node.constraints.constraints) {
this.emit(constraint);
this.writer.write(`, `);
}
this.writer.write(' => ');
}
this.emit(node.constraint);
if (node.elements !== null) {
this.writer.write('.\n');
this.writer.indent();
for (const element of node.elements) {
this.emit(element);
}
this.writer.dedent();
}
break;
default:
assertNever(node);
}
}
}

View file

@ -20,7 +20,7 @@ import {
ImportDeclaration, ImportDeclaration,
Param, Param,
Pattern, Pattern,
BindPattern, NamedPattern,
LetDeclaration, LetDeclaration,
TypeAssert, TypeAssert,
ExprBody, ExprBody,
@ -642,56 +642,15 @@ export class Parser {
private parsePatternStartingWithConstructor() { private parsePatternStartingWithConstructor() {
const name = this.expectToken(SyntaxKind.IdentifierAlt); const name = this.expectToken(SyntaxKind.IdentifierAlt);
const t2 = this.peekToken(); const patterns = [];
if (t2.kind === SyntaxKind.LBrace) { for (;;) {
this.getToken(); const t3 = this.peekToken();
const fields = []; if (t3.kind === SyntaxKind.RParen) {
let rbrace; break;
for (;;) {
const t3 = this.peekToken();
if (t3.kind === SyntaxKind.RBrace) {
this.getToken();
rbrace = t3;
break;
} else if (t3.kind === SyntaxKind.Identifier) {
this.getToken();
const t4 = this.peekToken();
if (t4.kind === SyntaxKind.Equals) {
this.getToken();
const pattern = this.parsePattern();
fields.push(new StructPatternField(t3, t4, pattern));
} else {
fields.push(new PunnedStructPatternField(t3));
}
} else if (t3.kind === SyntaxKind.DotDot) {
this.getToken();
fields.push(new VariadicStructPatternElement(t3, null));
} else {
this.raiseParseError(t3, [ SyntaxKind.Identifier, SyntaxKind.DotDot ]);
}
const t5 = this.peekToken();
if (t5.kind === SyntaxKind.Comma) {
this.getToken();
} else if (t5.kind === SyntaxKind.RBrace) {
this.getToken();
rbrace = t5;
break;
} else {
this.raiseParseError(t5, [ SyntaxKind.Comma, SyntaxKind.RBrace ]);
}
} }
return new StructPattern(name, t2, fields, rbrace); patterns.push(this.parsePattern());
} else {
const patterns = [];
for (;;) {
const t3 = this.peekToken();
if (t3.kind === SyntaxKind.RParen) {
break;
}
patterns.push(this.parsePattern());
}
return new NamedTuplePattern(name, patterns);
} }
return new NamedTuplePattern(name, patterns);
} }
public parseTuplePattern(): TuplePattern { public parseTuplePattern(): TuplePattern {
@ -719,9 +678,56 @@ export class Parser {
return new TuplePattern(lparen, elements, rparen); return new TuplePattern(lparen, elements, rparen);
} }
public parseStructPattern(): StructPattern {
const t2 = this.expectToken(SyntaxKind.LBrace);
const fields = [];
let rbrace;
for (;;) {
const t3 = this.peekToken();
if (t3.kind === SyntaxKind.RBrace) {
this.getToken();
rbrace = t3;
break;
} else if (t3.kind === SyntaxKind.Identifier) {
this.getToken();
const t4 = this.peekToken();
if (t4.kind === SyntaxKind.Equals) {
this.getToken();
const pattern = this.parsePattern();
fields.push(new StructPatternField(t3, t4, pattern));
} else {
fields.push(new PunnedStructPatternField(t3));
}
} else if (t3.kind === SyntaxKind.DotDot) {
this.getToken();
const t4 = this.peekToken();
let rest = null;
if (t4.kind !== SyntaxKind.RBrace) {
rest = this.parsePattern();
}
fields.push(new VariadicStructPatternElement(t3, rest));
} else {
this.raiseParseError(t3, [ SyntaxKind.Identifier, SyntaxKind.DotDot ]);
}
const t5 = this.peekToken();
if (t5.kind === SyntaxKind.Comma) {
this.getToken();
} else if (t5.kind === SyntaxKind.RBrace) {
this.getToken();
rbrace = t5;
break;
} else {
this.raiseParseError(t5, [ SyntaxKind.Comma, SyntaxKind.RBrace ]);
}
}
return new StructPattern(t2, fields, rbrace);
}
public parsePrimitivePattern(): Pattern { public parsePrimitivePattern(): Pattern {
const t0 = this.peekToken(); const t0 = this.peekToken();
switch (t0.kind) { switch (t0.kind) {
case SyntaxKind.LBrace:
return this.parseStructPattern();
case SyntaxKind.LParen: case SyntaxKind.LParen:
{ {
const t1 = this.peekToken(2); const t1 = this.peekToken(2);
@ -742,7 +748,7 @@ export class Parser {
case SyntaxKind.Identifier: case SyntaxKind.Identifier:
{ {
this.getToken(); this.getToken();
return new BindPattern(t0); return new NamedPattern(t0);
} }
case SyntaxKind.StringLiteral: case SyntaxKind.StringLiteral:
case SyntaxKind.Integer: case SyntaxKind.Integer:

View file

@ -0,0 +1,92 @@
import { TypeExpression } from "../cst";
import {
ExprBody,
NamedPattern,
LBrace,
RBrace,
LetKeyword,
LetDeclaration,
SourceFile,
Syntax,
SyntaxKind,
Identifier,
StructExpression,
StructExpressionField,
Equals,
InstanceDeclaration,
FunctionExpression,
Backslash,
canHaveInstanceDeclaration,
vistEachChild
} from "../cst";
import { Pass } from "../types";
import { assert } from "../util";
function encode(typeExpr: TypeExpression): string {
switch (typeExpr.kind) {
case SyntaxKind.ReferenceTypeExpression:
let out = '';
if (typeExpr.modulePath.length > 0) {
out += '_xm';
for (const [name, _dot] of typeExpr.modulePath) {
out += name + '_';
}
}
return out + typeExpr.name.text;
default:
throw new Error(`Could not encode type.`)
}
}
function lcfirst(text: string): string {
return text[0].toLowerCase() + text.substring(1);
}
export class TypeclassDictPassing implements Pass<SourceFile, SourceFile> {
private mangleInstance(node: InstanceDeclaration): string {
return lcfirst(node.constraint.name.text) + '_' + node.constraint.types.map(encode).join('');
}
private visit(node: Syntax): Syntax {
if (canHaveInstanceDeclaration(node)) {
return vistEachChild(node, this.visit.bind(this));
}
if (node.kind === SyntaxKind.InstanceDeclaration) {
const decl = new LetDeclaration(
node.pubKeyword,
new LetKeyword(),
null,
null,
new NamedPattern(new Identifier(this.mangleInstance(node))),
[],
null, // TODO
new ExprBody(
new Equals(),
new StructExpression(
new LBrace(),
node.elements.map(element => {
assert(element.kind === SyntaxKind.LetDeclaration);
assert(element.pattern.kind === SyntaxKind.NamedPattern);
return new StructExpressionField(
new Identifier(element.pattern.name.text),
new Equals(),
new FunctionExpression(new Backslash(), element.params, element.body!)
);
}),
new RBrace(),
)
)
);
return decl;
}
return node;
}
public apply(input: SourceFile): SourceFile {
return this.visit(input) as SourceFile;
}
}

View file

@ -6,22 +6,17 @@ import { ConsoleDiagnostics, Diagnostics } from "./diagnostics";
import { Checker } from "./checker"; import { Checker } from "./checker";
import { Analyser } from "./analysis"; import { Analyser } from "./analysis";
import { Newable, Pass } from "./types"; import { Newable, Pass } from "./types";
import BoltToC from "./passes/BoltToC";
import BoltToJS from "./passes/BoltToJS";
type AnyPass = Pass<any, any>; type AnyPass = Pass<any, any>;
export enum TargetType { export enum TargetType {
Bolt,
C, C,
JS, JS,
WebAssembly, WebAssembly,
LLVM, LLVM,
} }
interface TargetSpec {
type: TargetType;
}
export class PassManager { export class PassManager {
private registeredPasses: AnyPass[] = []; private registeredPasses: AnyPass[] = [];
@ -30,7 +25,7 @@ export class PassManager {
this.registeredPasses.push(new pass()); this.registeredPasses.push(new pass());
} }
public apply<In>(input: In): unknown { public apply(input: any): any {
for (const pass of this.registeredPasses) { for (const pass of this.registeredPasses) {
input = pass.apply(input); input = pass.apply(input);
} }
@ -72,34 +67,4 @@ export class Program {
} }
} }
public emit(target: TargetSpec): void {
let suffix;
const passes = new PassManager();
switch (target.type) {
case TargetType.C:
suffix = '.c';
passes.add(BoltToC);
break;
case TargetType.JS:
suffix = '.js'
passes.add(BoltToJS);
break;
}
for (const [sourceFilePath, sourceFile] of this.sourceFilesByPath) {
const code = passes.apply(sourceFile) as any;
const targetFilePath = stripExtension(sourceFilePath) + suffix;
const file = fs.createWriteStream(targetFilePath, 'utf-8');
code.emit(file);
}
}
}
function stripExtension(filepath: string): string {
const basename = path.basename(filepath);
const i = basename.lastIndexOf('.');
if (i === -1) {
return filepath;
}
return path.join(path.dirname(filepath), basename.substring(0, i));
} }

View file

@ -128,11 +128,14 @@ export class Scope {
switch (node.kind) { switch (node.kind) {
case SyntaxKind.LiteralPattern: case SyntaxKind.LiteralPattern:
break; break;
case SyntaxKind.BindPattern: case SyntaxKind.NamedPattern:
{ {
this.add(node.name.text, decl, Symkind.Var); this.add(node.name.text, decl, Symkind.Var);
break; break;
} }
case SyntaxKind.NestedPattern:
this.scanPattern(node.pattern, decl);
break;
case SyntaxKind.NamedTuplePattern: case SyntaxKind.NamedTuplePattern:
{ {
for (const element of node.elements) { for (const element of node.elements) {
@ -151,7 +154,7 @@ export class Scope {
} }
case SyntaxKind.PunnedStructPatternField: case SyntaxKind.PunnedStructPatternField:
{ {
this.add(node.name.text, decl, Symkind.Var); this.add(member.name.text, decl, Symkind.Var);
break; break;
} }
} }

View file

@ -1,6 +1,31 @@
import path from "path"
import stream from "stream" import stream from "stream"
export function first<T>(iter: Iterator<T>): T | undefined {
return iter.next().value;
}
export function last<T>(iter: Iterator<T>): T | undefined {
let prevValue;
for (;;) {
const { done, value } = iter.next();
if (done) {
return prevValue;
}
prevValue = value;
}
}
export function stripExtension(filepath: string): string {
const basename = path.basename(filepath);
const i = basename.lastIndexOf('.');
if (i === -1) {
return filepath;
}
return path.join(path.dirname(filepath), basename.substring(0, i));
}
export class IndentWriter { export class IndentWriter {
private atBlankLine = true; private atBlankLine = true;
@ -41,6 +66,11 @@ export function assert(test: boolean): asserts test {
} }
} }
export function assertNever(value: never): never {
console.error(value);
throw new Error(`Assertion failed. See the stack trace for more information.`);
}
export function countDigits(x: number, base: number = 10) { export function countDigits(x: number, base: number = 10) {
return x === 0 ? 1 : Math.ceil(Math.log(x+1) / Math.log(base)) return x === 0 ? 1 : Math.ceil(Math.log(x+1) / Math.log(base))
} }