Add extensible records and add foundations for typeclasses
This commit is contained in:
parent
f995d887e7
commit
df5f857905
10 changed files with 1062 additions and 473 deletions
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
|
743
src/checker.ts
743
src/checker.ts
File diff suppressed because it is too large
Load diff
180
src/cst.ts
180
src/cst.ts
|
@ -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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
197
src/emitter.ts
Normal 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);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
106
src/parser.ts
106
src/parser.ts
|
@ -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:
|
||||
|
|
92
src/passes/TypeclassDictPass.ts
Normal file
92
src/passes/TypeclassDictPass.ts
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
30
src/util.ts
30
src/util.ts
|
@ -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))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue