Major restructuring
- Add a new `TransformManager` that automatically sets up pipelines - Integrate with a small IoC-like-framework - Add template code for some new transformations - `Program` is not seperate from a new class `Frontend` - Fixed some bugs in `src/treegen/`
This commit is contained in:
parent
3f96b88866
commit
f3d8b021c2
19 changed files with 1343 additions and 904 deletions
510
src/ast.d.ts
vendored
510
src/ast.d.ts
vendored
File diff suppressed because it is too large
Load diff
|
@ -6,7 +6,9 @@ import "source-map-support/register"
|
|||
import yargs from "yargs"
|
||||
|
||||
import { Program } from "../program"
|
||||
import { TextFile } from "../text"
|
||||
import { parseSourceFile } from "../parser"
|
||||
import { BoltSourceFile} from "../ast"
|
||||
import { Frontend } from "../frontend"
|
||||
|
||||
global.debug = function (value: any) {
|
||||
console.error(require('util').inspect(value, { depth: Infinity, colors: true }))
|
||||
|
@ -64,17 +66,16 @@ yargs
|
|||
.string('work-dir')
|
||||
.describe('work-dir', 'The working directory where files will be resolved against.')
|
||||
.default('work-dir', '.')
|
||||
.string('inspect-server'),
|
||||
.string('target')
|
||||
.describe('target', 'The target language to compile to.')
|
||||
.default('target', 'JS')
|
||||
|
||||
args => {
|
||||
, args => {
|
||||
|
||||
const files = toArray(args.files as string[] | string);
|
||||
const opts = {};
|
||||
if (args['inspect-server'] !== undefined) {
|
||||
opts.inspector = connectToInspector(args['inspect-server'] as string);
|
||||
}
|
||||
const program = new Program(files, opts);
|
||||
program.compile("JS");
|
||||
const sourceFiles = toArray(args.files as string[] | string).map(parseSourceFile);
|
||||
const program = new Program(sourceFiles);
|
||||
const frontend = new Frontend();
|
||||
frontend.compile(program, args.target);
|
||||
|
||||
})
|
||||
|
||||
|
@ -90,22 +91,16 @@ yargs
|
|||
|
||||
args => {
|
||||
|
||||
const files = toArray(args.files as string | string[]).map(p => new TextFile(p));
|
||||
|
||||
if (files.length > 0) {
|
||||
|
||||
const program = new Program(files);
|
||||
|
||||
for (const file of files) {
|
||||
program.eval(file)
|
||||
}
|
||||
|
||||
} else {
|
||||
const sourceFiles = toArray(args.files as string | string[]).map(parseSourceFile);
|
||||
|
||||
if (sourceFiles.length === 0) {
|
||||
throw new Error(`Executing packages is not yet supported.`)
|
||||
|
||||
}
|
||||
|
||||
const program = new Program(sourceFiles);
|
||||
const frontend = new Frontend();
|
||||
frontend.eval(program);
|
||||
|
||||
}
|
||||
|
||||
)
|
||||
|
|
177
src/compiler.ts
177
src/compiler.ts
|
@ -1,177 +0,0 @@
|
|||
|
||||
import acorn from "acorn"
|
||||
|
||||
import {
|
||||
TypeChecker,
|
||||
Scope
|
||||
} from "./checker"
|
||||
|
||||
import {
|
||||
Syntax,
|
||||
SyntaxKind,
|
||||
kindToString,
|
||||
BoltSourceFile,
|
||||
BoltStatement,
|
||||
BoltExpression,
|
||||
BoltDeclaration,
|
||||
BoltBindPattern,
|
||||
JSExpression,
|
||||
JSStatement,
|
||||
JSSourceElement,
|
||||
createJSExpressionStatement,
|
||||
createJSSourceFile,
|
||||
createJSCallExpression,
|
||||
createJSReferenceExpression,
|
||||
createJSConstantExpression,
|
||||
createJSLetDeclaration,
|
||||
createJSIdentifier,
|
||||
createJSFunctionDeclaration,
|
||||
isBoltExpression,
|
||||
createJSBindPattern,
|
||||
JSDeclarationModifiers,
|
||||
JSParameter,
|
||||
BoltSyntax,
|
||||
BoltPattern,
|
||||
} from "./ast"
|
||||
|
||||
import { getFullTextOfQualName, hasPublicModifier } from "./util"
|
||||
|
||||
import { Program } from "./program"
|
||||
|
||||
export interface CompilerOptions {
|
||||
target: string;
|
||||
}
|
||||
|
||||
function pushAll<T>(arr: T[], els: T[]) {
|
||||
for (const el of els) {
|
||||
arr.push(el)
|
||||
}
|
||||
}
|
||||
|
||||
export class Compiler {
|
||||
|
||||
readonly target: string;
|
||||
|
||||
constructor(public program: Program, public checker: TypeChecker, options: CompilerOptions) {
|
||||
this.target = options.target;
|
||||
}
|
||||
|
||||
compile(files: BoltSourceFile[]) {
|
||||
return files.map(s => {
|
||||
const body: JSSourceElement[] = [];
|
||||
for (const element of s.elements) {
|
||||
this.compileDecl(element, body);
|
||||
}
|
||||
return createJSSourceFile(body, s.span);
|
||||
});
|
||||
}
|
||||
|
||||
protected compileExpr(node: Syntax, preamble: Syntax[]): JSExpression {
|
||||
|
||||
switch (node.kind) {
|
||||
|
||||
case SyntaxKind.BoltCallExpression:
|
||||
const compiledOperator = this.compileExpr(node.operator, preamble);
|
||||
const compiledArgs = node.operands.map(a => this.compileExpr(a, preamble))
|
||||
return createJSCallExpression(
|
||||
compiledOperator,
|
||||
compiledArgs,
|
||||
node.span,
|
||||
);
|
||||
|
||||
case SyntaxKind.BoltReferenceExpression:
|
||||
return createJSReferenceExpression(
|
||||
getFullTextOfQualName(node.name),
|
||||
node.span,
|
||||
);
|
||||
|
||||
case SyntaxKind.BoltConstantExpression:
|
||||
return createJSConstantExpression(
|
||||
node.value,
|
||||
node.span,
|
||||
);
|
||||
|
||||
default:
|
||||
throw new Error(`Could not compile expression node ${kindToString(node.kind)}`)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private compilePattern(node: BoltPattern) {
|
||||
|
||||
}
|
||||
|
||||
protected compileDecl(node: BoltSyntax, preamble: Syntax[]) {
|
||||
|
||||
//if (isBoltExpression(node)) {
|
||||
// const compiled = this.compileExpr(node, preamble);
|
||||
// preamble.push(createJSExpressionStatement(compiled));
|
||||
// return;
|
||||
//}
|
||||
|
||||
switch (node.kind) {
|
||||
|
||||
case SyntaxKind.BoltModule:
|
||||
for (const element of node.elements) {
|
||||
this.compileDecl(element, preamble);
|
||||
}
|
||||
break;
|
||||
|
||||
case SyntaxKind.BoltExpressionStatement:
|
||||
preamble.push(this.compileExpr(node.expression, preamble));
|
||||
break;
|
||||
|
||||
case SyntaxKind.BoltImportDeclaration:
|
||||
// TODO
|
||||
break;
|
||||
|
||||
case SyntaxKind.BoltTypeAliasDeclaration:
|
||||
break;
|
||||
|
||||
case SyntaxKind.BoltVariableDeclaration:
|
||||
const compiledValue = node.value !== null ? this.compileExpr(node.value, preamble) : null;
|
||||
preamble.push(
|
||||
createJSLetDeclaration(
|
||||
createJSBindPattern((node.bindings as BoltBindPattern).name, node.bindings.span),
|
||||
compiledValue,
|
||||
node.span,
|
||||
),
|
||||
);
|
||||
break;
|
||||
|
||||
case SyntaxKind.BoltFunctionDeclaration:
|
||||
if (node.target === this.target) {
|
||||
if (node.body === null) {
|
||||
break;
|
||||
}
|
||||
const params: JSParameter[] = [];
|
||||
let body: JSStatement[] = [];
|
||||
for (const param of node.params) {
|
||||
params.push(this.compilePattern(param.bindings, body));
|
||||
}
|
||||
let result = createJSFunctionDeclaration(
|
||||
0,
|
||||
createJSIdentifier(node.name.text, node.name.span),
|
||||
params,
|
||||
body,
|
||||
node.span,
|
||||
);
|
||||
if (hasPublicModifier(node)) {
|
||||
result.modifiers |= JSDeclarationModifiers.IsExported;;
|
||||
}
|
||||
preamble.push(result)
|
||||
} else {
|
||||
// TODO
|
||||
throw new Error(`Compiling native functions is not yet implemented.`);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Could not compile node ${kindToString(node.kind)}`);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,7 +1 @@
|
|||
|
||||
export const TYPES = {
|
||||
Program: Symbol('a Bolt program'),
|
||||
TypeChecker: Symbol('the Bolt type checking system'),
|
||||
FileManager: Symbol('the file manager'),
|
||||
}
|
||||
|
||||
|
|
16
src/di.ts
16
src/di.ts
|
@ -14,10 +14,11 @@ function isFactory(value: any): boolean {
|
|||
}
|
||||
|
||||
export function inject(target: any, key: PropertyKey, index: number) {
|
||||
//return function(target: any) {
|
||||
// This is a no-op because adding the decorator
|
||||
// is enough for TypeScript to emit metadata
|
||||
//}
|
||||
if (!Reflect.hasMetadata('di:paramindices', target)) {
|
||||
Reflect.defineMetadata('di:paramindices', [], target)
|
||||
}
|
||||
const indices = Reflect.getMetadata('di:paramindices', target);
|
||||
indices.push(index);
|
||||
}
|
||||
|
||||
function describeFactory(factory: Factory<any>): string {
|
||||
|
@ -37,10 +38,6 @@ export class Container {
|
|||
}
|
||||
}
|
||||
|
||||
private canResolve(serviceId: ServiceID): boolean {
|
||||
return this.singletons.has(serviceId);
|
||||
}
|
||||
|
||||
private resolve<T>(factory: Factory<T>): T;
|
||||
private resolve(serviceId: ServiceID): any {
|
||||
return this.singletons.get(serviceId);
|
||||
|
@ -49,12 +46,13 @@ export class Container {
|
|||
public createInstance<T>(factory: Factory<T>, ...args: any[]): T {
|
||||
const newArgs: any[] = [];
|
||||
const paramTypes = Reflect.getMetadata('design:paramtypes', factory);
|
||||
const indices = Reflect.getMetadata('di:paramindices', factory);
|
||||
if (paramTypes === undefined) {
|
||||
return new factory(...args);
|
||||
}
|
||||
let i = 0;
|
||||
for (const paramType of paramTypes) {
|
||||
if (this.canResolve(paramType)) {
|
||||
if (indices.indexOf(i) !== -1) {
|
||||
newArgs.push(this.resolve(paramType));
|
||||
} else {
|
||||
if (i >= args.length) {
|
||||
|
|
|
@ -9,6 +9,10 @@ export class Emitter {
|
|||
|
||||
switch (node.kind) {
|
||||
|
||||
case SyntaxKind.JSExpressionStatement:
|
||||
out += this.emit(node.expression) + ';\n';
|
||||
break;
|
||||
|
||||
case SyntaxKind.JSReferenceExpression:
|
||||
out += node.name;
|
||||
break;
|
||||
|
|
240
src/expander.ts
240
src/expander.ts
|
@ -1,240 +0,0 @@
|
|||
|
||||
// FIXME Actually, the syntax expander could make use of algebraic effects to
|
||||
// easily specify how the next expansion should happen. Just a thought.
|
||||
|
||||
import {
|
||||
SyntaxKind,
|
||||
setParents,
|
||||
kindToString,
|
||||
BoltSyntax,
|
||||
BoltSentence,
|
||||
createBoltRecordPattern,
|
||||
createBoltExpressionPattern,
|
||||
createBoltIdentifier,
|
||||
createBoltReferenceTypeExpression,
|
||||
createBoltConstantExpression,
|
||||
createBoltTuplePattern,
|
||||
createBoltQualName,
|
||||
createBoltTypePattern,
|
||||
createBoltBindPattern,
|
||||
createBoltMatchExpression,
|
||||
createBoltMatchArm,
|
||||
createBoltModule,
|
||||
createBoltSourceFile,
|
||||
BoltPattern,
|
||||
BoltSourceElement,
|
||||
BoltReferenceTypeExpression,
|
||||
createBoltRecordDeclaration,
|
||||
createBoltRecordDeclarationField,
|
||||
isBoltSourceElement,
|
||||
createBoltExpressionStatement,
|
||||
isBoltExpression,
|
||||
} from "./ast"
|
||||
|
||||
import { TextSpan } from "./text"
|
||||
import { TypeChecker } from "./checker"
|
||||
import { ParseError } from "./util"
|
||||
import { Parser } from "./parser"
|
||||
import { Evaluator, TRUE, FALSE } from "./evaluator"
|
||||
import { StreamWrapper, setOrigNodeRange, BoltTokenStream, createTokenStream } from "./util"
|
||||
|
||||
interface Transformer {
|
||||
pattern: BoltPattern;
|
||||
transform: (node: BoltTokenStream) => BoltSyntax;
|
||||
}
|
||||
|
||||
function createSimpleBoltReferenceTypeExpression(text: string): BoltReferenceTypeExpression {
|
||||
const ids = text.split('.').map(name => createBoltIdentifier(name))
|
||||
return createBoltReferenceTypeExpression(createBoltQualName(ids.slice(0, -1), ids[ids.length-1]), [])
|
||||
}
|
||||
|
||||
/// This is actually a hand-parsed version of the following:
|
||||
///
|
||||
/// Bolt.AST.Braced {
|
||||
/// elements = [
|
||||
/// Bolt.AST.Identifier { text = "name" },
|
||||
/// Bolt.AST.Braced {
|
||||
/// elements = [
|
||||
/// pattern: Bolt.AST.Pattern,
|
||||
/// _: RArrow,
|
||||
/// expression: Bolt.AST.Expr
|
||||
/// ]
|
||||
/// }
|
||||
/// ],
|
||||
/// }
|
||||
//const PATTERN_SYNTAX: BoltPattern =
|
||||
// createBoltRecordPattern(
|
||||
// createSimpleBoltReferenceTypeExpression('Bolt.AST.Sentence'),
|
||||
// [
|
||||
// createBoltRecordDeclarationField(
|
||||
// createBoltIdentifier('elements'),
|
||||
// createBoltTuplePattern([
|
||||
// createBoltRecordPattern(
|
||||
// createSimpleBoltReferenceTypeExpression('Bolt.AST.Identifier'),
|
||||
// [{
|
||||
// name: createBoltIdentifier('text'),
|
||||
// pattern: createBoltConstantExpression('syntax')
|
||||
// }]
|
||||
// ),
|
||||
// createBoltRecordPattern(
|
||||
// createSimpleBoltReferenceTypeExpression('Bolt.AST.Braced'),
|
||||
// [{
|
||||
// name: createBoltIdentifier('elements'),
|
||||
// pattern: createBoltTuplePattern([
|
||||
// createBoltTypePattern(createSimpleBoltReferenceTypeExpression('Bolt.AST.Pattern'), createBoltBindPattern(createBoltIdentifier('pattern'))),
|
||||
// createBoltTypePattern(createSimpleBoltReferenceTypeExpression('Bolt.AST.RArrow'), createBoltBindPattern(createBoltIdentifier('_'))),
|
||||
// createBoltTypePattern(createSimpleBoltReferenceTypeExpression('Bolt.AST.Expr'), createBoltBindPattern(createBoltIdentifier('expression')))
|
||||
// ])
|
||||
// }]
|
||||
// )
|
||||
// ])
|
||||
// )]
|
||||
// )
|
||||
|
||||
export class Expander {
|
||||
|
||||
protected transformers: Transformer[] = []
|
||||
|
||||
constructor(public parser: Parser, public evaluator: Evaluator, public checker: TypeChecker) {
|
||||
// this.transformers.push({
|
||||
// pattern: PATTERN_SYNTAX,
|
||||
// transform: this.parser.parseSyntax.bind(this.parser)
|
||||
// })
|
||||
}
|
||||
|
||||
getFullyExpanded(node: BoltSyntax): BoltSyntax {
|
||||
|
||||
if (node.kind === SyntaxKind.BoltSourceFile) {
|
||||
|
||||
const expanded: BoltSourceElement[] = [];
|
||||
|
||||
let didExpand = false;
|
||||
|
||||
for (const element of node.elements) {
|
||||
|
||||
let newElement = this.getFullyExpanded(element);
|
||||
|
||||
// Automatically lift top-level expressions into so that they are valid.
|
||||
|
||||
if (isBoltExpression(newElement)) {
|
||||
newElement = createBoltExpressionStatement(newElement);
|
||||
}
|
||||
|
||||
// From this point, newElement really should be a BoltSourceElement
|
||||
|
||||
if (!isBoltSourceElement(newElement)) {
|
||||
throw new Error(`Expanded element ${kindToString(newElement.kind)} is not valid in a top-level context.`);
|
||||
}
|
||||
|
||||
if (newElement !== element) {
|
||||
didExpand = true;
|
||||
}
|
||||
|
||||
expanded.push(newElement);
|
||||
}
|
||||
|
||||
if (!didExpand) {
|
||||
return node;
|
||||
}
|
||||
|
||||
const newSourceFile = createBoltSourceFile(expanded);
|
||||
setOrigNodeRange(newSourceFile, node, node);
|
||||
setParents(newSourceFile);
|
||||
return newSourceFile;
|
||||
|
||||
} else if (node.kind == SyntaxKind.BoltModule) {
|
||||
|
||||
const expanded: BoltSourceElement[] = [];
|
||||
|
||||
let didExpand = false;
|
||||
|
||||
for (const element of node.elements) {
|
||||
let newElement = this.getFullyExpanded(element);
|
||||
if (!isBoltSourceElement(newElement)) {
|
||||
throw new Error(`Expanded element is invalid in a module context.`);
|
||||
}
|
||||
if (newElement !== element) {
|
||||
didExpand = true;
|
||||
}
|
||||
expanded.push(newElement);
|
||||
}
|
||||
|
||||
if (!didExpand) {
|
||||
return node;
|
||||
}
|
||||
|
||||
const newModule = createBoltModule(0, node.name, expanded);
|
||||
setOrigNodeRange(newModule, node, node);
|
||||
setParents(newModule);
|
||||
return newModule;
|
||||
|
||||
} else if (node.kind === SyntaxKind.BoltSentence) {
|
||||
|
||||
let newElement;
|
||||
|
||||
const tokens = createTokenStream(node);
|
||||
|
||||
try {
|
||||
|
||||
newElement = this.parser.parseSourceElement(tokens)
|
||||
setOrigNodeRange(newElement, node, node);
|
||||
|
||||
} catch (e) {
|
||||
|
||||
// Regular errors should be propagated.
|
||||
|
||||
if (!(e instanceof ParseError)) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
// The following applies a user-defined transformer to the token tree.
|
||||
|
||||
while (true) {
|
||||
let didExpand = false;
|
||||
const expanded: BoltSyntax[] = [];
|
||||
const tokens = createTokenStream(node);
|
||||
for (const transformer of this.transformers) {
|
||||
if (this.evaluator.eval(createBoltMatchExpression(createBoltConstantExpression(this.evaluator.createValue(node)), [
|
||||
createBoltMatchArm(
|
||||
transformer.pattern,
|
||||
createBoltConstantExpression(TRUE)
|
||||
),
|
||||
createBoltMatchArm(
|
||||
createBoltExpressionPattern(createBoltConstantExpression(TRUE)),
|
||||
createBoltConstantExpression(FALSE)
|
||||
)
|
||||
]))) {
|
||||
expanded.push(transformer.transform(tokens))
|
||||
didExpand = true;
|
||||
// break; // FIXME
|
||||
}
|
||||
}
|
||||
if (!didExpand) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no transformer matched, then throw the original parse error.
|
||||
|
||||
if (!newElement) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Perform a full expansion on the transformed element.
|
||||
|
||||
return this.getFullyExpanded(newElement)
|
||||
|
||||
} else {
|
||||
|
||||
//this.checker.check(node);
|
||||
|
||||
return node;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
136
src/frontend.ts
Normal file
136
src/frontend.ts
Normal file
|
@ -0,0 +1,136 @@
|
|||
|
||||
import * as path from "path"
|
||||
import * as fs from "fs-extra"
|
||||
import { now } from "microtime"
|
||||
import { EventEmitter } from "events"
|
||||
|
||||
import { Program } from "./program"
|
||||
import { TypeChecker } from "./checker"
|
||||
import { Evaluator } from "./evaluator"
|
||||
import { emit } from "./emitter"
|
||||
import { Syntax } from "./ast"
|
||||
import { upsearchSync, FastStringMap, getFileStem, getLanguage } from "./util"
|
||||
import { Package } from "./package"
|
||||
import { verbose, memoize } from "./util"
|
||||
import { Container } from "./di"
|
||||
import ExpandBoltTransform from "./transforms/expand"
|
||||
import CompileBoltToJSTransform from "./transforms/boltToJS"
|
||||
import ConstFoldTransform from "./transforms/constFold"
|
||||
import EliminateModulesTransform from "./transforms/eliminateModules"
|
||||
import { TransformManager } from "./transforms/index"
|
||||
|
||||
const targetExtensions: MapLike<string> = {
|
||||
'JS': '.mjs',
|
||||
'Bolt': '.bolt',
|
||||
'C': '.c',
|
||||
};
|
||||
|
||||
interface TimingInfo {
|
||||
timestamp: number;
|
||||
refCount: number;
|
||||
}
|
||||
|
||||
class Timing extends EventEmitter {
|
||||
|
||||
private runningTasks: FastStringMap<TimingInfo> = Object.create(null);
|
||||
|
||||
public start(name: string) {
|
||||
if (this.runningTasks[name] !== undefined) {
|
||||
this.runningTasks[name].refCount++;
|
||||
return;
|
||||
}
|
||||
this.runningTasks[name] = { timestamp: now(), refCount: 1 };
|
||||
this.emit(`start ${name}`);
|
||||
}
|
||||
|
||||
public end(name: string) {
|
||||
if (this.runningTasks[name] === undefined) {
|
||||
throw new Error(`Task '${name}' was never started.`);
|
||||
}
|
||||
const info = this.runningTasks[name];
|
||||
info.refCount--;
|
||||
if (info.refCount === 0) {
|
||||
const usecs = now() - info.timestamp;
|
||||
verbose(`Task '${name}' completed after ${usecs} microseconds.`);
|
||||
this.emit(`end ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Frontend {
|
||||
|
||||
public evaluator: Evaluator;
|
||||
public checker: TypeChecker;
|
||||
public timing: Timing;
|
||||
|
||||
private container = new Container();
|
||||
|
||||
constructor() {
|
||||
this.checker = new TypeChecker();
|
||||
this.evaluator = new Evaluator(this.checker);
|
||||
this.timing = new Timing();
|
||||
this.container.bindSelf(this.evaluator);
|
||||
this.container.bindSelf(this.checker);
|
||||
}
|
||||
|
||||
@memoize
|
||||
public getPackage(filepath: string) {
|
||||
const file = this.getTextFile(filepath)
|
||||
const projectFile = upsearchSync('Boltfile', path.dirname(file.fullPath));
|
||||
if (projectFile === null) {
|
||||
return null;
|
||||
}
|
||||
const projectDir = path.resolve(path.dirname(projectFile));
|
||||
return new Package(projectDir);
|
||||
}
|
||||
|
||||
public compile(program: Program, target: string) {
|
||||
|
||||
switch (target) {
|
||||
|
||||
case "JS":
|
||||
const transforms = new TransformManager(this.container);
|
||||
transforms.register(ExpandBoltTransform);
|
||||
transforms.register(EliminateModulesTransform);
|
||||
transforms.register(CompileBoltToJSTransform);
|
||||
transforms.register(ConstFoldTransform);
|
||||
transforms.apply(program);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`"${target}" is an invalid compile target.`);
|
||||
|
||||
}
|
||||
|
||||
for (const sourceFile of program.getAllSourceFiles()) {
|
||||
//const filepath = rootNode.span!.file.fullPath;
|
||||
//const pkg = this.getPackage(filepath);
|
||||
//if (pkg !== null) {
|
||||
//
|
||||
//}
|
||||
fs.mkdirp('.bolt-work');
|
||||
fs.writeFileSync(this.mapToTargetFile(sourceFile), emit(sourceFile), 'utf8');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private mapToTargetFile(node: Syntax) {
|
||||
return path.join('.bolt-work', getFileStem(node.span!.file.fullPath) + getDefaultExtension(getLanguage(node)));
|
||||
}
|
||||
|
||||
public eval(program: Program) {
|
||||
for (const sourceFile of program.getAllSourceFiles()) {
|
||||
this.evaluator.eval(sourceFile)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getDefaultExtension(target: string) {
|
||||
if (targetExtensions[target] === undefined) {
|
||||
throw new Error(`Could not derive an appropriate extension for target "${target}".`)
|
||||
}
|
||||
return targetExtensions[target];
|
||||
}
|
||||
|
252
src/parser.ts
252
src/parser.ts
|
@ -24,7 +24,7 @@ import {
|
|||
createBoltParameter,
|
||||
BoltBindPattern,
|
||||
createBoltRecordDeclaration,
|
||||
createBoltRecordDeclarationField,
|
||||
createBoltRecordField,
|
||||
createBoltImportDeclaration,
|
||||
BoltDeclarationModifiers,
|
||||
BoltStringLiteral,
|
||||
|
@ -37,7 +37,7 @@ import {
|
|||
createBoltVariableDeclaration,
|
||||
BoltReturnStatement,
|
||||
createBoltReturnStatement,
|
||||
BoltRecordDeclarationField,
|
||||
BoltRecordMember,
|
||||
BoltModule,
|
||||
createBoltModule,
|
||||
BoltTypeAliasDeclaration,
|
||||
|
@ -54,6 +54,10 @@ import {
|
|||
createBoltTraitDeclaration,
|
||||
createBoltImplDeclaration,
|
||||
BoltImplDeclaration,
|
||||
BoltSourceFile,
|
||||
BoltFunctionBodyElement,
|
||||
createBoltSourceFile,
|
||||
BoltRecordField,
|
||||
} from "./ast"
|
||||
|
||||
import { parseForeignLanguage } from "./foreign"
|
||||
|
@ -71,6 +75,11 @@ import {
|
|||
|
||||
export type BoltTokenStream = Stream<BoltToken>;
|
||||
|
||||
export function isModifierKeyword(kind: SyntaxKind) {
|
||||
return kind === SyntaxKind.BoltPubKeyword
|
||||
|| kind === SyntaxKind.BoltForeignKeyword;
|
||||
}
|
||||
|
||||
const KIND_EXPRESSION_T0 = [
|
||||
SyntaxKind.BoltStringLiteral,
|
||||
SyntaxKind.BoltIntegerLiteral,
|
||||
|
@ -525,11 +534,16 @@ export class Parser {
|
|||
t2 = tokens.peek();
|
||||
}
|
||||
|
||||
let members = null;
|
||||
|
||||
if (t2.kind !== SyntaxKind.BoltSemi) {
|
||||
|
||||
if (t2.kind !== SyntaxKind.BoltBraced) {
|
||||
throw new ParseError(t2, [SyntaxKind.BoltBraced])
|
||||
}
|
||||
|
||||
let fields: BoltRecordDeclarationField[] = [];
|
||||
members = [];
|
||||
|
||||
tokens.get();
|
||||
const innerTokens = createTokenStream(t2);
|
||||
|
||||
|
@ -544,17 +558,19 @@ export class Parser {
|
|||
const t4 = innerTokens.get();
|
||||
assertToken(t4, SyntaxKind.BoltColon);
|
||||
const type = this.parseTypeExpression(innerTokens);
|
||||
const field = createBoltRecordDeclarationField(name as BoltIdentifier, type);
|
||||
const field = createBoltRecordField(name as BoltIdentifier, type);
|
||||
const t5 = innerTokens.get();
|
||||
if (t5.kind === SyntaxKind.EndOfFile) {
|
||||
break;
|
||||
}
|
||||
assertToken(t5, SyntaxKind.BoltComma);
|
||||
setOrigNodeRange(field, name, type);
|
||||
fields.push(field);
|
||||
members.push(field);
|
||||
}
|
||||
|
||||
const node = createBoltRecordDeclaration(modifiers, name, typeParams, fields);
|
||||
}
|
||||
|
||||
const node = createBoltRecordDeclaration(modifiers, name, typeParams, members);
|
||||
setOrigNodeRange(node, firstToken, t2);
|
||||
return node;
|
||||
}
|
||||
|
@ -594,7 +610,7 @@ export class Parser {
|
|||
if (t1.kind !== SyntaxKind.BoltBraced) {
|
||||
throw new ParseError(t1, [SyntaxKind.BoltBraced])
|
||||
}
|
||||
const sentences = this.parseSourceElementList(createTokenStream(t1));
|
||||
const sentences = this.parseSourceElements(createTokenStream(t1));
|
||||
|
||||
const node = createBoltModule(modifiers, name, sentences);
|
||||
setOrigNodeRange(node, firstToken, t1);
|
||||
|
@ -816,7 +832,7 @@ export class Parser {
|
|||
}
|
||||
const t3 = tokens.get();
|
||||
assertToken(t3, SyntaxKind.BoltBraced);
|
||||
const elements = this.parseSourceElementList(createTokenStream(t3));
|
||||
const elements = this.parseSourceElements(createTokenStream(t3));
|
||||
const result = createBoltTraitDeclaration(modifiers, t1 as BoltIdentifier, typeParams, elements as BoltDeclaration[]);
|
||||
setOrigNodeRange(result, firstToken, t3);
|
||||
return result;
|
||||
|
@ -843,7 +859,7 @@ export class Parser {
|
|||
}
|
||||
const t4 = tokens.get();
|
||||
assertToken(t4, SyntaxKind.BoltBraced);
|
||||
const elements = this.parseSourceElementList(createTokenStream(t4));
|
||||
const elements = this.parseSourceElements(createTokenStream(t4));
|
||||
const result = createBoltImplDeclaration(modifiers, t1 as BoltIdentifier, typeExpr, typeParams, elements as BoltDeclaration[]);
|
||||
setOrigNodeRange(result, firstToken, t4);
|
||||
return result;
|
||||
|
@ -890,25 +906,69 @@ export class Parser {
|
|||
}
|
||||
}
|
||||
|
||||
private getFirstTokenAfterModifiers(tokens: BoltTokenStream): BoltToken {
|
||||
let mustBeDecl = false;
|
||||
let mustBeFunctionOrVariable = false;
|
||||
let i = 1;
|
||||
let t0 = tokens.peek(i);
|
||||
if (t0.kind === SyntaxKind.BoltPubKeyword) {
|
||||
mustBeDecl = true;
|
||||
t0 = tokens.peek(++i);
|
||||
}
|
||||
if (t0.kind === SyntaxKind.BoltForeignKeyword) {
|
||||
mustBeFunctionOrVariable = true;
|
||||
i++;
|
||||
t0 = tokens.peek(++i);
|
||||
}
|
||||
if (mustBeFunctionOrVariable
|
||||
&& t0.kind !== SyntaxKind.BoltStructKeyword
|
||||
&& t0.kind !== SyntaxKind.BoltFnKeyword) {
|
||||
throw new ParseError(t0, [SyntaxKind.BoltStructKeyword, SyntaxKind.BoltFnKeyword]);
|
||||
}
|
||||
if (mustBeDecl && KIND_DECLARATION_T0.indexOf(t0.kind) === -1) {
|
||||
throw new ParseError(t0, KIND_DECLARATION_KEYWORD);
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
public parseSourceElement(tokens: BoltTokenStream): BoltSourceElement {
|
||||
try {
|
||||
return this.parseDeclaration(tokens)
|
||||
} catch (e1) {
|
||||
if (!(e1 instanceof ParseError)) {
|
||||
throw e1;
|
||||
}
|
||||
try {
|
||||
const t0 = this.getFirstTokenAfterModifiers(tokens);
|
||||
if (KIND_STATEMENT_T0.indexOf(t0.kind) !== -1) {
|
||||
return this.parseStatement(tokens);
|
||||
} catch (e2) {
|
||||
if (!(e2 instanceof ParseError)) {
|
||||
throw e2;
|
||||
}
|
||||
throw e1;
|
||||
return this.parseDeclaration(tokens)
|
||||
}
|
||||
|
||||
public parseFunctionBodyElement(tokens: BoltTokenStream): BoltFunctionBodyElement {
|
||||
const t0 = this.getFirstTokenAfterModifiers(tokens);
|
||||
if (KIND_STATEMENT_T0.indexOf(t0.kind) !== -1) {
|
||||
return this.parseStatement(tokens);
|
||||
} else if (t0.kind === SyntaxKind.BoltLetKeyword) {
|
||||
return this.parseVariableDeclaration(tokens);
|
||||
} else if (t0.kind === SyntaxKind.BoltFnKeyword) {
|
||||
return this.parseFunctionDeclaration(tokens);
|
||||
} else {
|
||||
throw new ParseError(t0, [...KIND_STATEMENT_T0, SyntaxKind.BoltLetKeyword, SyntaxKind.BoltFnKeyword]);
|
||||
}
|
||||
}
|
||||
|
||||
public parseSourceElementList(tokens: BoltTokenStream): BoltSourceElement[] {
|
||||
public parseFunctionBodyElements(tokens: BoltTokenStream): BoltFunctionBodyElement[] {
|
||||
const elements: BoltFunctionBodyElement[] = []
|
||||
while (true) {
|
||||
const t0 = tokens.peek();
|
||||
if (t0.kind === SyntaxKind.EndOfFile) {
|
||||
break;
|
||||
}
|
||||
if (t0.kind === SyntaxKind.BoltSemi) {
|
||||
tokens.get();
|
||||
continue;
|
||||
}
|
||||
elements.push(this.parseFunctionBodyElement(tokens));
|
||||
}
|
||||
return elements
|
||||
}
|
||||
|
||||
public parseSourceElements(tokens: BoltTokenStream): BoltSourceElement[] {
|
||||
const elements: BoltSourceElement[] = []
|
||||
while (true) {
|
||||
const t0 = tokens.peek();
|
||||
|
@ -983,5 +1043,155 @@ export class Parser {
|
|||
|
||||
}
|
||||
|
||||
public parseSourceFile(tokens: BoltTokenStream): BoltSourceFile {
|
||||
const elements = this.parseSourceElements(tokens);
|
||||
const t1 = tokens.peek();
|
||||
assertToken(t1, SyntaxKind.EndOfFile);
|
||||
return createBoltSourceFile(
|
||||
elements,
|
||||
new TextSpan(t1.span!.file, new TextPos(0,1,1), t1.span!.end.clone())
|
||||
);
|
||||
}
|
||||
|
||||
private canParseExpression(tokens: BoltTokenStream): boolean {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
private canParseReturnStatement(tokens: BoltTokenStream): boolean {
|
||||
const t0 = tokens.get();
|
||||
if (t0.kind !== SyntaxKind.BoltReturnKeyword) {
|
||||
return false;
|
||||
}
|
||||
const t1 = tokens.peek();
|
||||
if (t1.kind === SyntaxKind.EndOfFile) {
|
||||
return true;
|
||||
}
|
||||
return this.canParseExpression(tokens);
|
||||
}
|
||||
|
||||
private canParseStatement(tokens: BoltTokenStream): boolean {
|
||||
const t0 = tokens.peek();
|
||||
switch (t0.kind) {
|
||||
case SyntaxKind.BoltReturnKeyword:
|
||||
return this.canParseReturnStatement(tokens);
|
||||
case SyntaxKind.BoltLoopKeyword:
|
||||
return this.canParseLoopStatement(tokens);
|
||||
default:
|
||||
return this.canParseExpression(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
private canParseFunctionDeclaration(tokens: BoltTokenStream): boolean {
|
||||
let t0 = tokens.peek();
|
||||
if (t0.kind === SyntaxKind.BoltPubKeyword) {
|
||||
tokens.get();
|
||||
t0 = tokens.peek();
|
||||
}
|
||||
if (t0.kind === SyntaxKind.BoltForeignKeyword) {
|
||||
tokens.get();
|
||||
const t1 = tokens.get();
|
||||
if (t1.kind !== SyntaxKind.BoltStringLiteral) {
|
||||
return false;
|
||||
}
|
||||
t0 = tokens.peek();
|
||||
}
|
||||
// TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
private canParseRecordDeclaration(tokens: BoltTokenStream): boolean {
|
||||
// TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
private canParseVariableDeclaration(tokens: BoltTokenStream): boolean {
|
||||
// TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
private canParseDeclaration(tokens: BoltTokenStream): boolean {
|
||||
let i = 0;
|
||||
let t0 = tokens.peek(i);
|
||||
while (isModifierKeyword(t0.kind)) {
|
||||
t0 = tokens.peek(++i);
|
||||
}
|
||||
switch (t0.kind) {
|
||||
case SyntaxKind.BoltFnKeyword:
|
||||
return this.canParseFunctionDeclaration(tokens);
|
||||
case SyntaxKind.BoltStructKeyword:
|
||||
return this.canParseRecordDeclaration(tokens);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private canParseSourceElement(tokens: BoltTokenStream): boolean {
|
||||
return this.canParseStatement(tokens)
|
||||
|| this.canParseDeclaration(tokens)
|
||||
}
|
||||
|
||||
private canParseRecordMember(tokens: BoltTokenStream): boolean {
|
||||
// TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
private canParseFunctionBodyElement(tokens: BoltTokenStream): boolean {
|
||||
return this.canParseFunctionDeclaration(tokens)
|
||||
|| this.canParseStatement(tokens)
|
||||
|| this.canParseVariableDeclaration(tokens);
|
||||
}
|
||||
|
||||
private canParseRecordMembers(tokens: BoltTokenStream): boolean {
|
||||
while (true) {
|
||||
const t0 = tokens.peek();
|
||||
if (t0.kind === SyntaxKind.EndOfFile) {
|
||||
break;
|
||||
}
|
||||
if (!this.canParseRecordMember(tokens)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private canParseSourceElements(tokens: BoltTokenStream): boolean {
|
||||
while (true) {
|
||||
const t0 = tokens.peek();
|
||||
if (t0.kind === SyntaxKind.EndOfFile) {
|
||||
break;
|
||||
}
|
||||
if (!this.canParseSourceElement(tokens)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private canParseFunctionBodyElements(tokens: BoltTokenStream): boolean {
|
||||
while (true) {
|
||||
const t0 = tokens.peek();
|
||||
if (t0.kind === SyntaxKind.EndOfFile) {
|
||||
break;
|
||||
}
|
||||
if (!this.canParseFunctionBodyElement(tokens)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
import { Scanner } from "./scanner"
|
||||
import { TextFile, TextSpan, TextPos } from "./text"
|
||||
import * as fs from "fs"
|
||||
|
||||
export function parseSourceFile(filepath: string): BoltSourceFile {
|
||||
const file = new TextFile(filepath);
|
||||
const contents = fs.readFileSync(file.origPath, 'utf8');
|
||||
const scanner = new Scanner(file, contents)
|
||||
const parser = new Parser();
|
||||
return parser.parseSourceFile(scanner);
|
||||
}
|
||||
|
||||
|
|
153
src/program.ts
153
src/program.ts
|
@ -1,151 +1,34 @@
|
|||
|
||||
import * as path from "path"
|
||||
import * as fs from "fs-extra"
|
||||
import { now } from "microtime"
|
||||
import { EventEmitter } from "events"
|
||||
import { BoltSourceFile, JSSourceFile } from "./ast"
|
||||
import { FastStringMap } from "./util";
|
||||
|
||||
import { Parser } from "./parser"
|
||||
import { TypeChecker } from "./checker"
|
||||
import { Evaluator } from "./evaluator"
|
||||
import { Expander } from "./expander"
|
||||
import { Scanner } from "./scanner"
|
||||
import { Compiler } from "./compiler"
|
||||
import { emit } from "./emitter"
|
||||
import { TextFile } from "./text"
|
||||
import { BoltSourceFile, Syntax, JSSourceFile } from "./ast"
|
||||
import { upsearchSync, FastStringMap, getFileStem, getLanguage } from "./util"
|
||||
import { Package } from "./package"
|
||||
import { verbose, memoize } from "./util"
|
||||
|
||||
const targetExtensions: FastStringMap<string> = {
|
||||
'JS': '.mjs',
|
||||
'Bolt': '.bolt',
|
||||
'C': '.c',
|
||||
};
|
||||
|
||||
export interface TransformationContext {
|
||||
|
||||
}
|
||||
|
||||
interface TimingInfo {
|
||||
timestamp: number;
|
||||
refCount: number;
|
||||
}
|
||||
|
||||
class Timing extends EventEmitter {
|
||||
|
||||
private runningTasks: FastStringMap<TimingInfo> = Object.create(null);
|
||||
|
||||
public start(name: string) {
|
||||
if (this.runningTasks[name] !== undefined) {
|
||||
this.runningTasks[name].refCount++;
|
||||
return;
|
||||
}
|
||||
this.runningTasks[name] = { timestamp: now(), refCount: 1 };
|
||||
this.emit(`start ${name}`);
|
||||
}
|
||||
|
||||
public end(name: string) {
|
||||
if (this.runningTasks[name] === undefined) {
|
||||
throw new Error(`Task '${name}' was never started.`);
|
||||
}
|
||||
const info = this.runningTasks[name];
|
||||
info.refCount--;
|
||||
if (info.refCount === 0) {
|
||||
const usecs = now() - info.timestamp;
|
||||
verbose(`Task '${name}' completed after ${usecs} microseconds.`);
|
||||
this.emit(`end ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
export type SourceFile
|
||||
= BoltSourceFile
|
||||
| JSSourceFile
|
||||
|
||||
export class Program {
|
||||
|
||||
public parser: Parser
|
||||
public evaluator: Evaluator;
|
||||
public checker: TypeChecker;
|
||||
public expander: Expander;
|
||||
public timing: Timing;
|
||||
private transformed = new FastStringMap<string, SourceFile>();
|
||||
|
||||
constructor(public files: string[]) {
|
||||
this.checker = new TypeChecker();
|
||||
this.parser = new Parser();
|
||||
this.evaluator = new Evaluator(this.checker);
|
||||
this.expander = new Expander(this.parser, this.evaluator, this.checker);
|
||||
this.timing = new Timing();
|
||||
}
|
||||
|
||||
@memoize
|
||||
public getTextFile(filename: string): TextFile {
|
||||
return new TextFile(filename);
|
||||
}
|
||||
|
||||
@memoize
|
||||
public getSourceFile(file: TextFile): BoltSourceFile {
|
||||
this.timing.start('read');
|
||||
const contents = fs.readFileSync(file.origPath, 'utf8');
|
||||
this.timing.end('read');
|
||||
const scanner = new Scanner(file, contents)
|
||||
this.timing.start('scan');
|
||||
const sourceFile = scanner.scan();
|
||||
this.timing.end('scan');
|
||||
return sourceFile;
|
||||
}
|
||||
|
||||
@memoize
|
||||
public getFullyExpandedSourceFile(file: TextFile): BoltSourceFile {
|
||||
const sourceFile = this.getSourceFile(file);
|
||||
this.timing.start('expand');
|
||||
const expanded = this.expander.getFullyExpanded(sourceFile) as BoltSourceFile;
|
||||
this.timing.end('expand');
|
||||
return expanded;
|
||||
}
|
||||
|
||||
@memoize
|
||||
public getPackage(filepath: string) {
|
||||
const file = this.getTextFile(filepath)
|
||||
const projectFile = upsearchSync('Boltfile', path.dirname(file.fullPath));
|
||||
if (projectFile === null) {
|
||||
return null;
|
||||
}
|
||||
const projectDir = path.resolve(path.dirname(projectFile));
|
||||
return new Package(projectDir);
|
||||
}
|
||||
|
||||
public compile(target: string) {
|
||||
const compiler = new Compiler(this, this.checker, { target })
|
||||
const expanded = this.files.map(filename => this.getFullyExpandedSourceFile(this.getTextFile(filename)));
|
||||
const compiled = compiler.compile(expanded) as JSSourceFile[];
|
||||
for (const rootNode of compiled) {
|
||||
//const filepath = rootNode.span!.file.fullPath;
|
||||
//const pkg = this.getPackage(filepath);
|
||||
//if (pkg !== null) {
|
||||
//
|
||||
//}
|
||||
fs.mkdirp('.bolt-work');
|
||||
fs.writeFileSync(this.mapToTargetFile(rootNode), emit(rootNode), 'utf8');
|
||||
constructor(
|
||||
sourceFiles: BoltSourceFile[]
|
||||
) {
|
||||
for (const sourceFile of sourceFiles) {
|
||||
this.transformed.set(sourceFile.span!.file.fullPath, sourceFile);
|
||||
}
|
||||
}
|
||||
|
||||
private mapToTargetFile(node: Syntax) {
|
||||
return path.join('.bolt-work', getFileStem(node.span!.file.fullPath) + getDefaultExtension(getLanguage(node)));
|
||||
public getAllSourceFiles() {
|
||||
return this.transformed.values();
|
||||
}
|
||||
|
||||
public eval() {
|
||||
for (const filename of this.files) {
|
||||
const file = this.getTextFile(filename);
|
||||
const expanded = this.getFullyExpandedSourceFile(file);
|
||||
this.evaluator.eval(expanded)
|
||||
public updateSourceFile(oldSourceFile: SourceFile, newSourceFile: SourceFile): void {
|
||||
if (!this.transformed.has(oldSourceFile.span!.file.fullPath)) {
|
||||
throw new Error(`Could not update ${oldSourceFile.span!.file.origPath} because it was not found in this program.`);
|
||||
}
|
||||
this.transformed.delete(oldSourceFile.span!.file.fullPath);
|
||||
this.transformed.set(newSourceFile.span!.file.fullPath, newSourceFile);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getDefaultExtension(target: string) {
|
||||
if (targetExtensions[target] === undefined) {
|
||||
throw new Error(`Could not derive an appropriate extension for target "${target}".`)
|
||||
}
|
||||
return targetExtensions[target];
|
||||
}
|
||||
|
||||
|
|
216
src/transforms/boltToJS.ts
Normal file
216
src/transforms/boltToJS.ts
Normal file
|
@ -0,0 +1,216 @@
|
|||
|
||||
import {
|
||||
TypeChecker,
|
||||
Scope
|
||||
} from "../checker"
|
||||
|
||||
import {
|
||||
Syntax,
|
||||
SyntaxKind,
|
||||
kindToString,
|
||||
BoltSourceFile,
|
||||
BoltStatement,
|
||||
BoltExpression,
|
||||
BoltDeclaration,
|
||||
BoltBindPattern,
|
||||
JSExpression,
|
||||
JSStatement,
|
||||
JSSourceElement,
|
||||
createJSExpressionStatement,
|
||||
createJSSourceFile,
|
||||
createJSCallExpression,
|
||||
createJSReferenceExpression,
|
||||
createJSConstantExpression,
|
||||
createJSLetDeclaration,
|
||||
createJSIdentifier,
|
||||
createJSFunctionDeclaration,
|
||||
createJSBindPattern,
|
||||
JSDeclarationModifiers,
|
||||
JSParameter,
|
||||
BoltSyntax,
|
||||
BoltPattern,
|
||||
createJSImportDeclaration,
|
||||
JSSyntax,
|
||||
JSSourceFile,
|
||||
isBoltSourceFile,
|
||||
BoltImportDeclaration,
|
||||
BoltIdentifier,
|
||||
isBoltDeclaration,
|
||||
isBoltStatement,
|
||||
JSBindPattern,
|
||||
BoltSourceElement,
|
||||
} from "../ast"
|
||||
|
||||
import { getFullTextOfQualName, hasPublicModifier, setOrigNodeRange, FastStringMap } from "../util"
|
||||
import { Program, SourceFile } from "../program"
|
||||
import { Transformer, TransformManager } from "./index"
|
||||
import { assert } from "../util"
|
||||
import { inject } from "../di"
|
||||
|
||||
export interface JSCompilerOptions {
|
||||
|
||||
}
|
||||
|
||||
function toJSIdentifier(node: BoltIdentifier) {
|
||||
const result = createJSIdentifier(node.text);
|
||||
setOrigNodeRange(result, node, node);
|
||||
return result;
|
||||
}
|
||||
|
||||
function pushAll<T>(arr: T[], els: T[]) {
|
||||
for (const el of els) {
|
||||
arr.push(el)
|
||||
}
|
||||
}
|
||||
|
||||
type Ref<T> = { value: T };
|
||||
|
||||
class CompileContext {
|
||||
|
||||
private generatedNodes: JSSyntax[] = [];
|
||||
|
||||
constructor(public scope: Scope) {
|
||||
|
||||
}
|
||||
|
||||
public appendNode(node: JSSyntax): void {
|
||||
this.generatedNodes.push(node);
|
||||
}
|
||||
|
||||
public getGeneratedNodes(): JSSyntax[] {
|
||||
return this.generatedNodes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class BoltToJSTransform implements Transformer {
|
||||
|
||||
constructor(
|
||||
private transforms: TransformManager,
|
||||
@inject private program: Program,
|
||||
@inject private checker: TypeChecker,
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
public isApplicable(sourceFile: SourceFile): boolean {
|
||||
return isBoltSourceFile(sourceFile);
|
||||
}
|
||||
|
||||
public transform(sourceFile: BoltSourceFile): JSSourceFile {
|
||||
const ctx = new CompileContext(this.checker.getScope(sourceFile))
|
||||
for (const element of sourceFile.elements) {
|
||||
this.compileSourceElement(element, ctx);
|
||||
}
|
||||
return createJSSourceFile(ctx.getGeneratedNodes() as JSSourceElement[], sourceFile.span);
|
||||
}
|
||||
|
||||
private compileExpression(node: Syntax, ctx: CompileContext): JSExpression {
|
||||
|
||||
switch (node.kind) {
|
||||
|
||||
case SyntaxKind.BoltCallExpression:
|
||||
const compiledOperator = this.compileExpression(node.operator, ctx);
|
||||
const compiledArgs = node.operands.map(arg => this.compileExpression(arg, ctx))
|
||||
return createJSCallExpression(
|
||||
compiledOperator,
|
||||
compiledArgs,
|
||||
node.span,
|
||||
);
|
||||
|
||||
case SyntaxKind.BoltReferenceExpression:
|
||||
return createJSReferenceExpression(
|
||||
getFullTextOfQualName(node.name),
|
||||
node.span,
|
||||
);
|
||||
|
||||
case SyntaxKind.BoltConstantExpression:
|
||||
return createJSConstantExpression(
|
||||
node.value,
|
||||
node.span,
|
||||
);
|
||||
|
||||
default:
|
||||
throw new Error(`Could not compile expression node ${kindToString(node.kind)}`)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private convertPattern(node: BoltPattern): JSBindPattern {
|
||||
if (node.kind !== SyntaxKind.BoltBindPattern) {
|
||||
throw new Error(`The provided node should have been eliminated by a previous pass.`);
|
||||
}
|
||||
const jsIdent = toJSIdentifier((node as BoltBindPattern).name);
|
||||
const jsBindPatt = createJSBindPattern(jsIdent);
|
||||
setOrigNodeRange(jsBindPatt, node, node);
|
||||
return jsBindPatt;
|
||||
}
|
||||
|
||||
protected compileSourceElement(node: BoltSourceElement, ctx: CompileContext) {
|
||||
|
||||
switch (node.kind) {
|
||||
|
||||
case SyntaxKind.BoltRecordDeclaration:
|
||||
case SyntaxKind.BoltTypeAliasDeclaration:
|
||||
break;
|
||||
|
||||
case SyntaxKind.BoltExpressionStatement:
|
||||
const jsExpr = this.compileExpression(node.expression, ctx)
|
||||
const jsExprStmt = createJSExpressionStatement(jsExpr)
|
||||
setOrigNodeRange(jsExprStmt, node, node);
|
||||
ctx.appendNode(jsExprStmt);
|
||||
break;
|
||||
|
||||
case SyntaxKind.BoltImportDeclaration:
|
||||
// TODO
|
||||
break;
|
||||
|
||||
case SyntaxKind.BoltVariableDeclaration:
|
||||
const jsValue = node.value !== null ? this.compileExpression(node.value, ctx) : null;
|
||||
const jsValueBindPatt = this.convertPattern(node.bindings);
|
||||
const jsValueDecl = createJSLetDeclaration(
|
||||
jsValueBindPatt,
|
||||
jsValue,
|
||||
);
|
||||
ctx.appendNode(jsValueDecl);
|
||||
break;
|
||||
|
||||
case SyntaxKind.BoltFunctionDeclaration:
|
||||
if (node.body === null) {
|
||||
break;
|
||||
}
|
||||
if (node.target === "JS") {
|
||||
const params: JSParameter[] = [];
|
||||
let body: JSStatement[] = [];
|
||||
for (const param of node.params) {
|
||||
assert(param.defaultValue === null);
|
||||
const jsPatt = this.convertPattern(param.bindings)
|
||||
params.push(jsPatt);
|
||||
}
|
||||
let result = createJSFunctionDeclaration(
|
||||
0,
|
||||
createJSIdentifier(node.name.text, node.name.span),
|
||||
params,
|
||||
body,
|
||||
node.span,
|
||||
);
|
||||
if (hasPublicModifier(node)) {
|
||||
result.modifiers |= JSDeclarationModifiers.IsExported;;
|
||||
}
|
||||
ctx.appendNode(result)
|
||||
} else {
|
||||
// TODO
|
||||
throw new Error(`Compiling native functions is not yet implemented.`);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Could not compile node ${kindToString(node.kind)}`);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BoltToJSTransform;
|
22
src/transforms/constFold.ts
Normal file
22
src/transforms/constFold.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
|
||||
import { SourceFile } from "../program"
|
||||
import { TransformManager } from "../transformers";
|
||||
|
||||
export class ConstFoldTransform {
|
||||
|
||||
constructor(public transformers: TransformManager) {
|
||||
|
||||
}
|
||||
|
||||
public isApplicable(node: SourceFile): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public transform(node: SourceFile): SourceFile {
|
||||
return node;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ConstFoldTransform;
|
||||
|
0
src/transforms/eliminateMatchExpressions.ts
Normal file
0
src/transforms/eliminateMatchExpressions.ts
Normal file
55
src/transforms/eliminateModules.ts
Normal file
55
src/transforms/eliminateModules.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import {TransformManager} from ".";
|
||||
import {SourceFile} from "../program";
|
||||
import {isBoltSourceFile, createBoltSourceFile, BoltSourceElement, SyntaxKind, BoltModule, isBoltModule} from "../ast";
|
||||
import {setOrigNodeRange} from "../util";
|
||||
|
||||
export class EliminateModulesTransform {
|
||||
|
||||
constructor(private transformers: TransformManager) {
|
||||
|
||||
}
|
||||
|
||||
public isApplicable(sourceFile: SourceFile) {
|
||||
return isBoltSourceFile(sourceFile);
|
||||
}
|
||||
|
||||
public transform(sourceFile: SourceFile): SourceFile {
|
||||
|
||||
let needsUpdate = false;
|
||||
const elements: BoltSourceElement[] = [];
|
||||
|
||||
for (const element of sourceFile.elements) {
|
||||
if (element.kind === SyntaxKind.BoltModule) {
|
||||
this.extractModuleElements(element, elements);
|
||||
needsUpdate = true;
|
||||
} else {
|
||||
elements.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
if (!needsUpdate) {
|
||||
return sourceFile;
|
||||
}
|
||||
|
||||
const newSourceFile = createBoltSourceFile(elements);
|
||||
setOrigNodeRange(newSourceFile, sourceFile, sourceFile);
|
||||
return newSourceFile;
|
||||
}
|
||||
|
||||
public extractModuleElements(node: BoltModule, out: BoltSourceElement[]) {
|
||||
for (const element of node.elements) {
|
||||
switch (element.kind) {
|
||||
case SyntaxKind.BoltModule:
|
||||
this.extractModuleElements(node, out);
|
||||
break;
|
||||
case SyntaxKind.BoltRecordDeclaration:
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default EliminateModulesTransform;
|
||||
|
215
src/transforms/expand.ts
Normal file
215
src/transforms/expand.ts
Normal file
|
@ -0,0 +1,215 @@
|
|||
|
||||
// FIXME Actually, the syntax expander could make use of algebraic effects to
|
||||
// easily specify how the next expansion should happen. Just a thought.
|
||||
|
||||
import {
|
||||
SyntaxKind,
|
||||
BoltSyntax,
|
||||
BoltPattern,
|
||||
isBoltSourceFile,
|
||||
BoltMacroCall,
|
||||
} from "../ast"
|
||||
|
||||
import { TextSpan } from "../text"
|
||||
import { TypeChecker, Scope } from "../checker"
|
||||
import { ParseError } from "../util"
|
||||
import { BoltTokenStream, Parser, isModifierKeyword } from "../parser"
|
||||
import { Evaluator, TRUE, FALSE } from "../evaluator"
|
||||
import { setOrigNodeRange, createTokenStream } from "../util"
|
||||
import { Transformer, TransformManager } from "./index"
|
||||
import { inject } from "../di"
|
||||
import {SourceFile} from "../program"
|
||||
|
||||
interface SyntaxTransformer {
|
||||
pattern: BoltPattern;
|
||||
transform: (node: BoltTokenStream) => BoltSyntax;
|
||||
}
|
||||
|
||||
export class ExpandBoltTransform implements Transformer {
|
||||
|
||||
private parser = new Parser();
|
||||
private syntaxTransformers: SyntaxTransformer[] = []
|
||||
|
||||
private toExpand: BoltMacroCall[] = [];
|
||||
|
||||
constructor(
|
||||
private transforms: TransformManager,
|
||||
@inject private evaluator: Evaluator,
|
||||
@inject private checker: TypeChecker
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
//private getRequiredMacros(node: BoltSentence): boolean {
|
||||
// const scope = this.checker.getScope(node);
|
||||
// const tokens = createTokenStream(node);
|
||||
// if (node.parentNode === null || node.parentNode.kind === SyntaxKind.BoltModule) {
|
||||
// return this.canParseSourceElements(tokens, scope)
|
||||
// } else if (node.parentNode.kind === SyntaxKind.BoltFunctionDeclaration) {
|
||||
// return this.canParseFunctionBodyElements(tokens, scope);
|
||||
// } else if (node.parentNode.kind === SyntaxKind.BoltRecordDeclaration) {
|
||||
// return this.canParseRecordMembers(tokens, scope);
|
||||
// } else if (isBoltExpression(node.parentNode)) {
|
||||
// return this.canParseExpression(node, scope);
|
||||
// } else {
|
||||
// throw new Error(`Could not auto-detect the context in which the node is declared.`);
|
||||
// }
|
||||
//}
|
||||
|
||||
public isApplicable(node: SourceFile): boolean {
|
||||
return isBoltSourceFile(node)
|
||||
}
|
||||
|
||||
public transform(node: BoltSyntax) {
|
||||
return this.expand(node);
|
||||
}
|
||||
|
||||
private expand(node: BoltSyntax) {
|
||||
|
||||
for (const identNode of node.findAllChildrenOfKind(SyntaxKind.BoltIdentifier)) {
|
||||
if (identNode.text.endsWith('1')) {
|
||||
this.toExpand.push(node.parentNode!);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME
|
||||
return node;
|
||||
|
||||
}
|
||||
|
||||
//private getFullyExpanded(node: BoltSyntax): BoltSyntax {
|
||||
|
||||
// if (node.kind === SyntaxKind.BoltSourceFile) {
|
||||
|
||||
// const expanded: BoltSourceElement[] = [];
|
||||
|
||||
// let didExpand = false;
|
||||
|
||||
// for (const element of node.elements) {
|
||||
|
||||
// let newElement = this.getFullyExpanded(element);
|
||||
|
||||
// // Automatically lift top-level expressions into so that they are valid.
|
||||
|
||||
// if (isBoltExpression(newElement)) {
|
||||
// newElement = createBoltExpressionStatement(newElement);
|
||||
// }
|
||||
|
||||
// // From this point, newElement really should be a BoltSourceElement
|
||||
|
||||
// if (!isBoltSourceElement(newElement)) {
|
||||
// throw new Error(`Expanded element ${kindToString(newElement.kind)} is not valid in a top-level context.`);
|
||||
// }
|
||||
|
||||
// if (newElement !== element) {
|
||||
// didExpand = true;
|
||||
// }
|
||||
|
||||
// expanded.push(newElement);
|
||||
// }
|
||||
|
||||
// if (!didExpand) {
|
||||
// return node;
|
||||
// }
|
||||
|
||||
// const newSourceFile = createBoltSourceFile(expanded);
|
||||
// setOrigNodeRange(newSourceFile, node, node);
|
||||
// setParents(newSourceFile);
|
||||
// return newSourceFile;
|
||||
|
||||
// } else if (node.kind == SyntaxKind.BoltModule) {
|
||||
|
||||
// const expanded: BoltSourceElement[] = [];
|
||||
|
||||
// let didExpand = false;
|
||||
|
||||
// for (const element of node.elements) {
|
||||
// let newElement = this.getFullyExpanded(element);
|
||||
// if (!isBoltSourceElement(newElement)) {
|
||||
// throw new Error(`Expanded element is invalid in a module context.`);
|
||||
// }
|
||||
// if (newElement !== element) {
|
||||
// didExpand = true;
|
||||
// }
|
||||
// expanded.push(newElement);
|
||||
// }
|
||||
|
||||
// if (!didExpand) {
|
||||
// return node;
|
||||
// }
|
||||
|
||||
// const newModule = createBoltModule(0, node.name, expanded);
|
||||
// setOrigNodeRange(newModule, node, node);
|
||||
// setParents(newModule);
|
||||
// return newModule;
|
||||
|
||||
// } else if (node.kind === SyntaxKind.BoltSentence) {
|
||||
|
||||
// let newElement;
|
||||
|
||||
// const tokens = createTokenStream(node);
|
||||
|
||||
// try {
|
||||
|
||||
// newElement = this.parser.parseSourceElement(tokens)
|
||||
// setOrigNodeRange(newElement, node, node);
|
||||
|
||||
// } catch (e) {
|
||||
|
||||
// // Regular errors should be propagated.
|
||||
|
||||
// if (!(e instanceof ParseError)) {
|
||||
// throw e;
|
||||
// }
|
||||
|
||||
// // The following applies a user-defined transformer to the token tree.
|
||||
|
||||
// while (true) {
|
||||
// let didExpand = false;
|
||||
// const expanded: BoltSyntax[] = [];
|
||||
// const tokens = createTokenStream(node);
|
||||
// for (const transformer of this.syntaxTransformers) {
|
||||
// if (this.evaluator.eval(createBoltMatchExpression(createBoltConstantExpression(this.evaluator.createValue(node)), [
|
||||
// createBoltMatchArm(
|
||||
// transformer.pattern,
|
||||
// createBoltConstantExpression(TRUE)
|
||||
// ),
|
||||
// createBoltMatchArm(
|
||||
// createBoltExpressionPattern(createBoltConstantExpression(TRUE)),
|
||||
// createBoltConstantExpression(FALSE)
|
||||
// )
|
||||
// ]))) {
|
||||
// expanded.push(transformer.transform(tokens))
|
||||
// didExpand = true;
|
||||
// // break; // FIXME
|
||||
// }
|
||||
// }
|
||||
// if (!didExpand) {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // If no transformer matched, then throw the original parse error.
|
||||
|
||||
// if (!newElement) {
|
||||
// throw e;
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// // Perform a full expansion on the transformed element.
|
||||
|
||||
// return this.getFullyExpanded(newElement)
|
||||
|
||||
// } else {
|
||||
|
||||
// return node;
|
||||
|
||||
// }
|
||||
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
export default ExpandBoltTransform;
|
||||
|
47
src/transforms/index.ts
Normal file
47
src/transforms/index.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
|
||||
import { SourceFile, Program } from "./program"
|
||||
import { Container } from "./di"
|
||||
import {Evaluator} from "./evaluator";
|
||||
import {TypeChecker} from "./checker";
|
||||
|
||||
export interface Transformer {
|
||||
isApplicable(node: SourceFile): boolean;
|
||||
transform(node: SourceFile): SourceFile;
|
||||
}
|
||||
|
||||
export interface RegisterTransformerOptions {
|
||||
|
||||
}
|
||||
|
||||
function createInstance<T>(factory: Factory<T>, ...args: any[]): T {
|
||||
return new factory(...args);
|
||||
}
|
||||
|
||||
export class TransformManager {
|
||||
|
||||
private transformers: Transformer[] = [];
|
||||
|
||||
constructor(private container: Container) {
|
||||
|
||||
}
|
||||
|
||||
public register(transformerFactory: Newable<Transformer>, options: RegisterTransformerOptions = {}) {
|
||||
const transformer = this.container.createInstance(transformerFactory, this);
|
||||
this.transformers.push(transformer);
|
||||
}
|
||||
|
||||
public apply(program: Program) {
|
||||
for (const transformer of this.transformers) {
|
||||
for (const sourceFile of program.getAllSourceFiles()) {
|
||||
if (transformer.isApplicable(sourceFile)) {
|
||||
const newSourceFile = transformer.transform(sourceFile);
|
||||
if (newSourceFile !== sourceFile) {
|
||||
program.updateSourceFile(sourceFile, newSourceFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -2,23 +2,13 @@
|
|||
const exported = {};
|
||||
|
||||
const nodeProto = {
|
||||
preorder() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function isSyntax(value) {
|
||||
return typeof value === 'object'
|
||||
&& value !== null
|
||||
&& value.__NODE_TYPE !== undefined;
|
||||
}
|
||||
|
||||
function* getChildNodes(node) {
|
||||
for (const key of Object.keys(node)) {
|
||||
*getChildNodes() {
|
||||
for (const key of Object.keys(this)) {
|
||||
if (key === 'span' || key === 'parentNode') {
|
||||
continue
|
||||
}
|
||||
const value = node[key];
|
||||
const value = this[key];
|
||||
if (Array.isArray(value)) {
|
||||
for (const element of value) {
|
||||
if (isSyntax(element)) {
|
||||
|
@ -31,6 +21,41 @@ function* getChildNodes(node) {
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
*preorder() {
|
||||
const stack = [this];
|
||||
while (stack.length > 0) {
|
||||
const node = stack.pop();
|
||||
yield node
|
||||
for (const childNode of node.getChildNodes()) {
|
||||
stack.push(childNode);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mayContainKind(kind) {
|
||||
// TODO
|
||||
return true;
|
||||
},
|
||||
|
||||
*findAllChildrenOfKind(kind) {
|
||||
for (const node of this.preorder()) {
|
||||
if (!node.mayContainKind(kind)) {
|
||||
break;
|
||||
}
|
||||
if (node.kind === kind) {
|
||||
yield node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function isSyntax(value) {
|
||||
return typeof value === 'object'
|
||||
&& value !== null
|
||||
&& value.__NODE_TYPE !== undefined;
|
||||
}
|
||||
|
||||
function createNode(nodeType) {
|
||||
|
|
|
@ -110,17 +110,22 @@ export function setParents(node: Syntax): void;
|
|||
|
||||
export type SyntaxRange = [Syntax, Syntax];
|
||||
|
||||
interface SyntaxBase {
|
||||
kind: SyntaxKind;
|
||||
parentNode: Syntax | null;
|
||||
interface SyntaxBase<K extends SyntaxKind> {
|
||||
kind: K;
|
||||
parentNode: ParentTypesOf<K> | null;
|
||||
span: TextSpan | null;
|
||||
getChildNodes(): IterableIterator<ChildTypesOf<K>>,
|
||||
findAllChildrenOfKind<K1 extends SyntaxKind>(kind: K1): IterableIterator<ResolveSyntaxKind<K1>>;
|
||||
}
|
||||
|
||||
export type ResolveSyntaxKind<K extends SyntaxKind> = Extract<Syntax, { kind: K }>;
|
||||
|
||||
`);
|
||||
|
||||
for (const decl of decls) {
|
||||
if (decl.type === 'NodeDeclaration') {
|
||||
if (isLeafNode(decl.name)) {
|
||||
dtsFile.write(`export interface ${decl.name} extends SyntaxBase {\n`)
|
||||
dtsFile.write(`export interface ${decl.name} extends SyntaxBase<SyntaxKind.${decl.name}> {\n`)
|
||||
dtsFile.indent()
|
||||
dtsFile.write(`kind: SyntaxKind.${decl.name};\n`);
|
||||
for (const field of getAllFields(decl)) {
|
||||
|
@ -150,6 +155,19 @@ interface SyntaxBase {
|
|||
}
|
||||
}
|
||||
|
||||
//dtsFile.write('export type ResolveSyntaxKind<K extends SyntaxKind>\n');
|
||||
//{
|
||||
//let i = 0;
|
||||
//for (const decl of leafNodes) {
|
||||
//dtsFile.write(i === 0 ? ' =' : ' |');
|
||||
//dtsFile.write(` K extends SyntaxKind.${decl.name} ? ${decl.name}`);
|
||||
//dtsFile.write(' :');
|
||||
//dtsFile.write('\n');
|
||||
//i++;
|
||||
//}
|
||||
//dtsFile.write(' never\n\n');
|
||||
//}
|
||||
|
||||
for (const langName of langNames) {
|
||||
dtsFile.write(`export type ${langName}Syntax\n`);
|
||||
let first = true;
|
||||
|
|
12
src/util.ts
12
src/util.ts
|
@ -8,6 +8,12 @@ import { TextFile, TextSpan, TextPos } from "./text"
|
|||
import { Scanner } from "./scanner"
|
||||
import { kindToString, Syntax, BoltQualName, BoltDeclaration, BoltDeclarationModifiers, createEndOfFile, SyntaxKind, isBoltPunctuated } from "./ast"
|
||||
|
||||
export function assert(test: boolean): void {
|
||||
if (!test) {
|
||||
throw new Error(`Invariant violation: an internal sanity check failed.`);
|
||||
}
|
||||
}
|
||||
|
||||
export function createTokenStream(node: Syntax) {
|
||||
if (isBoltPunctuated(node)) {
|
||||
const origPos = node.span!.start;
|
||||
|
@ -52,6 +58,12 @@ export class FastStringMap<K extends PropertyKey, V> {
|
|||
return this.mapping[key];
|
||||
}
|
||||
|
||||
public *values(): IterableIterator<V> {
|
||||
for (const key of Object.keys(this.mapping)) {
|
||||
yield this.mapping[key];
|
||||
}
|
||||
}
|
||||
|
||||
public set(key: K, value: V): void {
|
||||
if (key in this.mapping) {
|
||||
throw new Error(`A value for key '${key}' already exists.`);
|
||||
|
|
Loading…
Reference in a new issue