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 fs from "fs"
import util from "util"
import path from "path"
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) {
console.error(util.inspect(value, { colors: true, depth: Infinity }));
@ -23,14 +28,21 @@ program
program
.command('build', 'Build a set of Bolt sources')
.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')
.action((file, opts) => {
const cwd = opts.workDir;
const filename = path.resolve(cwd, file);
const shouldTypecheck = opts.typecheck;
const shouldEmit = opts.emit;
let targetType: TargetType;
switch (opts.target) {
case 'bolt':
targetType = TargetType.Bolt;
break;
case 'js':
targetType = TargetType.JS;
break;
@ -47,14 +59,44 @@ program
process.exit(1);
}
program.check();
if (program.diagnostics.hasError) {
process.exit(1);
if (shouldTypecheck) {
program.check();
if (program.diagnostics.hasError) {
process.exit(1);
}
}
program.emit({ type: targetType });
if (program.diagnostics.hasError) {
process.exit(1);
if (shouldEmit) {
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 type { InferContext, Kind, KindEnv, Scheme, Type, TypeEnv } from "./checker"
import { array, middleware } from "yargs";
import { warn } from "console";
import { InferContext, Kind, KindEnv, Scheme, Type, TypeEnv } from "./checker"
import { Emitter } from "./emitter";
export type TextSpan = [number, number];
@ -70,6 +73,10 @@ export class TextFile {
}
public getFullPath(): string {
return path.resolve(this.origPath);
}
}
export const enum SyntaxKind {
@ -126,7 +133,7 @@ export const enum SyntaxKind {
TupleTypeExpression,
// Patterns
BindPattern,
NamedPattern,
TuplePattern,
StructPattern,
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 {
const obj: JSONObject = {};
obj['type'] = this.constructor.name;
for (const key of Object.getOwnPropertyNames(this)) {
for (const [key, value] of this.getFields()) {
if (isIgnoredProperty(key)) {
continue;
}
obj[key] = encode((this as any)[key]);
obj[key] = encode(value);
}
return obj;
function encode(value: any): JSONValue {
@ -328,7 +380,7 @@ abstract class SyntaxBase {
return null;
} else if (Array.isArray(value)) {
return value.map(encode);
} else if (value instanceof SyntaxBase) {
} else if (isSyntax(value)) {
return value.toJSON();
} else {
return value;
@ -1329,9 +1381,9 @@ export type TypeExpression
| NestedTypeExpression
| TupleTypeExpression
export class BindPattern extends SyntaxBase {
export class NamedPattern extends SyntaxBase {
public readonly kind = SyntaxKind.BindPattern;
public readonly kind = SyntaxKind.NamedPattern;
public constructor(
public name: Identifier,
@ -1339,8 +1391,8 @@ export class BindPattern extends SyntaxBase {
super();
}
public clone(): BindPattern {
return new BindPattern( this.name.clone() );
public clone(): NamedPattern {
return new NamedPattern( this.name.clone() );
}
public get isHole(): boolean {
@ -1513,7 +1565,6 @@ export class StructPattern extends SyntaxBase {
public readonly kind = SyntaxKind.StructPattern;
public constructor(
public name: IdentifierAlt,
public lbrace: LBrace,
public members: StructPatternElement[],
public rbrace: RBrace,
@ -1523,7 +1574,6 @@ export class StructPattern extends SyntaxBase {
public clone(): StructPattern {
return new StructPattern(
this.name.clone(),
this.lbrace.clone(),
this.members.map(member => member.clone()),
this.rbrace.clone(),
@ -1531,7 +1581,7 @@ export class StructPattern extends SyntaxBase {
}
public getFirstToken(): Token {
return this.name;
return this.lbrace;
}
public getLastToken(): Token {
@ -1626,7 +1676,7 @@ export class LiteralPattern extends SyntaxBase {
}
export type Pattern
= BindPattern
= NamedPattern
| NestedPattern
| StructPattern
| NamedTuplePattern
@ -2793,10 +2843,10 @@ export class ClassDeclaration extends SyntaxBase {
switch (element.kind) {
case SyntaxKind.LetDeclaration:
assert(element.pattern.kind === SyntaxKind.BindPattern);
assert(element.pattern.kind === SyntaxKind.NamedPattern);
for (const other of this.elements) {
if (other.kind === SyntaxKind.LetDeclaration
&& other.pattern.kind === SyntaxKind.BindPattern
&& other.pattern.kind === SyntaxKind.NamedPattern
&& other.pattern.name.text === element.pattern.name.text) {
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 {
return new ClassDeclaration(
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;
}
case TypeKind.Record:
case TypeKind.Field:
{
let out = '{ ';
let first = true;
for (const [fieldName, fieldType] of type.fields) {
if (first) first = false;
else out += ', ';
out += fieldName + ': ' + describeType(fieldType);
let out = '{ ' + type.name + ': ' + describeType(type.type);
type = type.restType;
while (type.kind === TypeKind.Field) {
out += '; ' + type.name + ': ' + describeType(type.type);
type = type.restType;
}
return out + ' }';
}
case TypeKind.Labeled:
{
// FIXME may need to include fields that were added during unification
return '{ ' + type.name + ': ' + describeType(type.type) + ' }';
if (type.kind !== TypeKind.Nil) {
out += '; ' + describeType(type);
}
return out + ' }'
}
case TypeKind.App:
{
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 right: Type,
public nodes: Syntax[],
public path: string[],
) {
}
@ -281,7 +286,11 @@ export class UnificationFailedDiagnostic {
const node = this.nodes[0];
out.write(ANSI_FG_RED + ANSI_BOLD + `error: ` + 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');
for (let i = 1; i < this.nodes.length; i++) {
const node = this.nodes[i];
@ -311,43 +320,34 @@ export class FieldMissingDiagnostic {
public readonly level = Level.Error;
public constructor(
public recordType: Type,
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 {
out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
out.write(`field '${this.fieldName}' is missing from `);
out.write(describeType(this.recordType) + '\n\n');
if (this.node !== null) {
out.write(printNode(this.node) + '\n');
out.write(`field '${this.fieldName}' is required in one type but missing in another\n\n`);
out.indent();
if (this.missing !== null) {
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');
}
}
}
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.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');
}
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
| UnexpectedTokenDiagnostic
| FieldMissingDiagnostic
| FieldDoesNotExistDiagnostic
| KindMismatchDiagnostic
| 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,
Param,
Pattern,
BindPattern,
NamedPattern,
LetDeclaration,
TypeAssert,
ExprBody,
@ -642,56 +642,15 @@ export class Parser {
private parsePatternStartingWithConstructor() {
const name = this.expectToken(SyntaxKind.IdentifierAlt);
const t2 = this.peekToken();
if (t2.kind === SyntaxKind.LBrace) {
this.getToken();
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();
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 ]);
}
const patterns = [];
for (;;) {
const t3 = this.peekToken();
if (t3.kind === SyntaxKind.RParen) {
break;
}
return new StructPattern(name, t2, fields, rbrace);
} else {
const patterns = [];
for (;;) {
const t3 = this.peekToken();
if (t3.kind === SyntaxKind.RParen) {
break;
}
patterns.push(this.parsePattern());
}
return new NamedTuplePattern(name, patterns);
patterns.push(this.parsePattern());
}
return new NamedTuplePattern(name, patterns);
}
public parseTuplePattern(): TuplePattern {
@ -719,9 +678,56 @@ export class Parser {
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 {
const t0 = this.peekToken();
switch (t0.kind) {
case SyntaxKind.LBrace:
return this.parseStructPattern();
case SyntaxKind.LParen:
{
const t1 = this.peekToken(2);
@ -742,7 +748,7 @@ export class Parser {
case SyntaxKind.Identifier:
{
this.getToken();
return new BindPattern(t0);
return new NamedPattern(t0);
}
case SyntaxKind.StringLiteral:
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 { Analyser } from "./analysis";
import { Newable, Pass } from "./types";
import BoltToC from "./passes/BoltToC";
import BoltToJS from "./passes/BoltToJS";
type AnyPass = Pass<any, any>;
export enum TargetType {
Bolt,
C,
JS,
WebAssembly,
LLVM,
}
interface TargetSpec {
type: TargetType;
}
export class PassManager {
private registeredPasses: AnyPass[] = [];
@ -30,7 +25,7 @@ export class PassManager {
this.registeredPasses.push(new pass());
}
public apply<In>(input: In): unknown {
public apply(input: any): any {
for (const pass of this.registeredPasses) {
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) {
case SyntaxKind.LiteralPattern:
break;
case SyntaxKind.BindPattern:
case SyntaxKind.NamedPattern:
{
this.add(node.name.text, decl, Symkind.Var);
break;
}
case SyntaxKind.NestedPattern:
this.scanPattern(node.pattern, decl);
break;
case SyntaxKind.NamedTuplePattern:
{
for (const element of node.elements) {
@ -151,7 +154,7 @@ export class Scope {
}
case SyntaxKind.PunnedStructPatternField:
{
this.add(node.name.text, decl, Symkind.Var);
this.add(member.name.text, decl, Symkind.Var);
break;
}
}

View file

@ -1,6 +1,31 @@
import path from "path"
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 {
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) {
return x === 0 ? 1 : Math.ceil(Math.log(x+1) / Math.log(base))
}