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 yargs from "yargs"
|
||||||
|
|
||||||
import { Program } from "../program"
|
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) {
|
global.debug = function (value: any) {
|
||||||
console.error(require('util').inspect(value, { depth: Infinity, colors: true }))
|
console.error(require('util').inspect(value, { depth: Infinity, colors: true }))
|
||||||
|
@ -64,17 +66,16 @@ yargs
|
||||||
.string('work-dir')
|
.string('work-dir')
|
||||||
.describe('work-dir', 'The working directory where files will be resolved against.')
|
.describe('work-dir', 'The working directory where files will be resolved against.')
|
||||||
.default('work-dir', '.')
|
.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 sourceFiles = toArray(args.files as string[] | string).map(parseSourceFile);
|
||||||
const opts = {};
|
const program = new Program(sourceFiles);
|
||||||
if (args['inspect-server'] !== undefined) {
|
const frontend = new Frontend();
|
||||||
opts.inspector = connectToInspector(args['inspect-server'] as string);
|
frontend.compile(program, args.target);
|
||||||
}
|
|
||||||
const program = new Program(files, opts);
|
|
||||||
program.compile("JS");
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -90,22 +91,16 @@ yargs
|
||||||
|
|
||||||
args => {
|
args => {
|
||||||
|
|
||||||
const files = toArray(args.files as string | string[]).map(p => new TextFile(p));
|
const sourceFiles = toArray(args.files as string | string[]).map(parseSourceFile);
|
||||||
|
|
||||||
if (files.length > 0) {
|
|
||||||
|
|
||||||
const program = new Program(files);
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
program.eval(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
|
if (sourceFiles.length === 0) {
|
||||||
throw new Error(`Executing packages is not yet supported.`)
|
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) {
|
export function inject(target: any, key: PropertyKey, index: number) {
|
||||||
//return function(target: any) {
|
if (!Reflect.hasMetadata('di:paramindices', target)) {
|
||||||
// This is a no-op because adding the decorator
|
Reflect.defineMetadata('di:paramindices', [], target)
|
||||||
// is enough for TypeScript to emit metadata
|
}
|
||||||
//}
|
const indices = Reflect.getMetadata('di:paramindices', target);
|
||||||
|
indices.push(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
function describeFactory(factory: Factory<any>): string {
|
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<T>(factory: Factory<T>): T;
|
||||||
private resolve(serviceId: ServiceID): any {
|
private resolve(serviceId: ServiceID): any {
|
||||||
return this.singletons.get(serviceId);
|
return this.singletons.get(serviceId);
|
||||||
|
@ -49,12 +46,13 @@ export class Container {
|
||||||
public createInstance<T>(factory: Factory<T>, ...args: any[]): T {
|
public createInstance<T>(factory: Factory<T>, ...args: any[]): T {
|
||||||
const newArgs: any[] = [];
|
const newArgs: any[] = [];
|
||||||
const paramTypes = Reflect.getMetadata('design:paramtypes', factory);
|
const paramTypes = Reflect.getMetadata('design:paramtypes', factory);
|
||||||
|
const indices = Reflect.getMetadata('di:paramindices', factory);
|
||||||
if (paramTypes === undefined) {
|
if (paramTypes === undefined) {
|
||||||
return new factory(...args);
|
return new factory(...args);
|
||||||
}
|
}
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (const paramType of paramTypes) {
|
for (const paramType of paramTypes) {
|
||||||
if (this.canResolve(paramType)) {
|
if (indices.indexOf(i) !== -1) {
|
||||||
newArgs.push(this.resolve(paramType));
|
newArgs.push(this.resolve(paramType));
|
||||||
} else {
|
} else {
|
||||||
if (i >= args.length) {
|
if (i >= args.length) {
|
||||||
|
|
|
@ -9,6 +9,10 @@ export class Emitter {
|
||||||
|
|
||||||
switch (node.kind) {
|
switch (node.kind) {
|
||||||
|
|
||||||
|
case SyntaxKind.JSExpressionStatement:
|
||||||
|
out += this.emit(node.expression) + ';\n';
|
||||||
|
break;
|
||||||
|
|
||||||
case SyntaxKind.JSReferenceExpression:
|
case SyntaxKind.JSReferenceExpression:
|
||||||
out += node.name;
|
out += node.name;
|
||||||
break;
|
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];
|
||||||
|
}
|
||||||
|
|
306
src/parser.ts
306
src/parser.ts
|
@ -24,7 +24,7 @@ import {
|
||||||
createBoltParameter,
|
createBoltParameter,
|
||||||
BoltBindPattern,
|
BoltBindPattern,
|
||||||
createBoltRecordDeclaration,
|
createBoltRecordDeclaration,
|
||||||
createBoltRecordDeclarationField,
|
createBoltRecordField,
|
||||||
createBoltImportDeclaration,
|
createBoltImportDeclaration,
|
||||||
BoltDeclarationModifiers,
|
BoltDeclarationModifiers,
|
||||||
BoltStringLiteral,
|
BoltStringLiteral,
|
||||||
|
@ -37,7 +37,7 @@ import {
|
||||||
createBoltVariableDeclaration,
|
createBoltVariableDeclaration,
|
||||||
BoltReturnStatement,
|
BoltReturnStatement,
|
||||||
createBoltReturnStatement,
|
createBoltReturnStatement,
|
||||||
BoltRecordDeclarationField,
|
BoltRecordMember,
|
||||||
BoltModule,
|
BoltModule,
|
||||||
createBoltModule,
|
createBoltModule,
|
||||||
BoltTypeAliasDeclaration,
|
BoltTypeAliasDeclaration,
|
||||||
|
@ -54,6 +54,10 @@ import {
|
||||||
createBoltTraitDeclaration,
|
createBoltTraitDeclaration,
|
||||||
createBoltImplDeclaration,
|
createBoltImplDeclaration,
|
||||||
BoltImplDeclaration,
|
BoltImplDeclaration,
|
||||||
|
BoltSourceFile,
|
||||||
|
BoltFunctionBodyElement,
|
||||||
|
createBoltSourceFile,
|
||||||
|
BoltRecordField,
|
||||||
} from "./ast"
|
} from "./ast"
|
||||||
|
|
||||||
import { parseForeignLanguage } from "./foreign"
|
import { parseForeignLanguage } from "./foreign"
|
||||||
|
@ -71,6 +75,11 @@ import {
|
||||||
|
|
||||||
export type BoltTokenStream = Stream<BoltToken>;
|
export type BoltTokenStream = Stream<BoltToken>;
|
||||||
|
|
||||||
|
export function isModifierKeyword(kind: SyntaxKind) {
|
||||||
|
return kind === SyntaxKind.BoltPubKeyword
|
||||||
|
|| kind === SyntaxKind.BoltForeignKeyword;
|
||||||
|
}
|
||||||
|
|
||||||
const KIND_EXPRESSION_T0 = [
|
const KIND_EXPRESSION_T0 = [
|
||||||
SyntaxKind.BoltStringLiteral,
|
SyntaxKind.BoltStringLiteral,
|
||||||
SyntaxKind.BoltIntegerLiteral,
|
SyntaxKind.BoltIntegerLiteral,
|
||||||
|
@ -525,36 +534,43 @@ export class Parser {
|
||||||
t2 = tokens.peek();
|
t2 = tokens.peek();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t2.kind !== SyntaxKind.BoltBraced) {
|
let members = null;
|
||||||
throw new ParseError(t2, [SyntaxKind.BoltBraced])
|
|
||||||
|
if (t2.kind !== SyntaxKind.BoltSemi) {
|
||||||
|
|
||||||
|
if (t2.kind !== SyntaxKind.BoltBraced) {
|
||||||
|
throw new ParseError(t2, [SyntaxKind.BoltBraced])
|
||||||
|
}
|
||||||
|
|
||||||
|
members = [];
|
||||||
|
|
||||||
|
tokens.get();
|
||||||
|
const innerTokens = createTokenStream(t2);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const t3 = innerTokens.peek();
|
||||||
|
if (t3.kind === SyntaxKind.EndOfFile) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
assertToken(t3, SyntaxKind.BoltIdentifier);
|
||||||
|
innerTokens.get();
|
||||||
|
const name = t3 as BoltIdentifier;
|
||||||
|
const t4 = innerTokens.get();
|
||||||
|
assertToken(t4, SyntaxKind.BoltColon);
|
||||||
|
const type = this.parseTypeExpression(innerTokens);
|
||||||
|
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);
|
||||||
|
members.push(field);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let fields: BoltRecordDeclarationField[] = [];
|
const node = createBoltRecordDeclaration(modifiers, name, typeParams, members);
|
||||||
tokens.get();
|
|
||||||
const innerTokens = createTokenStream(t2);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const t3 = innerTokens.peek();
|
|
||||||
if (t3.kind === SyntaxKind.EndOfFile) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
assertToken(t3, SyntaxKind.BoltIdentifier);
|
|
||||||
innerTokens.get();
|
|
||||||
const name = t3 as BoltIdentifier;
|
|
||||||
const t4 = innerTokens.get();
|
|
||||||
assertToken(t4, SyntaxKind.BoltColon);
|
|
||||||
const type = this.parseTypeExpression(innerTokens);
|
|
||||||
const field = createBoltRecordDeclarationField(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
const node = createBoltRecordDeclaration(modifiers, name, typeParams, fields);
|
|
||||||
setOrigNodeRange(node, firstToken, t2);
|
setOrigNodeRange(node, firstToken, t2);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
@ -594,7 +610,7 @@ export class Parser {
|
||||||
if (t1.kind !== SyntaxKind.BoltBraced) {
|
if (t1.kind !== SyntaxKind.BoltBraced) {
|
||||||
throw new ParseError(t1, [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);
|
const node = createBoltModule(modifiers, name, sentences);
|
||||||
setOrigNodeRange(node, firstToken, t1);
|
setOrigNodeRange(node, firstToken, t1);
|
||||||
|
@ -816,7 +832,7 @@ export class Parser {
|
||||||
}
|
}
|
||||||
const t3 = tokens.get();
|
const t3 = tokens.get();
|
||||||
assertToken(t3, SyntaxKind.BoltBraced);
|
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[]);
|
const result = createBoltTraitDeclaration(modifiers, t1 as BoltIdentifier, typeParams, elements as BoltDeclaration[]);
|
||||||
setOrigNodeRange(result, firstToken, t3);
|
setOrigNodeRange(result, firstToken, t3);
|
||||||
return result;
|
return result;
|
||||||
|
@ -843,7 +859,7 @@ export class Parser {
|
||||||
}
|
}
|
||||||
const t4 = tokens.get();
|
const t4 = tokens.get();
|
||||||
assertToken(t4, SyntaxKind.BoltBraced);
|
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[]);
|
const result = createBoltImplDeclaration(modifiers, t1 as BoltIdentifier, typeExpr, typeParams, elements as BoltDeclaration[]);
|
||||||
setOrigNodeRange(result, firstToken, t4);
|
setOrigNodeRange(result, firstToken, t4);
|
||||||
return result;
|
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 {
|
public parseSourceElement(tokens: BoltTokenStream): BoltSourceElement {
|
||||||
try {
|
const t0 = this.getFirstTokenAfterModifiers(tokens);
|
||||||
return this.parseDeclaration(tokens)
|
if (KIND_STATEMENT_T0.indexOf(t0.kind) !== -1) {
|
||||||
} catch (e1) {
|
return this.parseStatement(tokens);
|
||||||
if (!(e1 instanceof ParseError)) {
|
}
|
||||||
throw e1;
|
return this.parseDeclaration(tokens)
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
return this.parseStatement(tokens);
|
public parseFunctionBodyElement(tokens: BoltTokenStream): BoltFunctionBodyElement {
|
||||||
} catch (e2) {
|
const t0 = this.getFirstTokenAfterModifiers(tokens);
|
||||||
if (!(e2 instanceof ParseError)) {
|
if (KIND_STATEMENT_T0.indexOf(t0.kind) !== -1) {
|
||||||
throw e2;
|
return this.parseStatement(tokens);
|
||||||
}
|
} else if (t0.kind === SyntaxKind.BoltLetKeyword) {
|
||||||
throw e1;
|
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[] = []
|
const elements: BoltSourceElement[] = []
|
||||||
while (true) {
|
while (true) {
|
||||||
const t0 = tokens.peek();
|
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 { BoltSourceFile, JSSourceFile } from "./ast"
|
||||||
import * as fs from "fs-extra"
|
import { FastStringMap } from "./util";
|
||||||
import { now } from "microtime"
|
|
||||||
import { EventEmitter } from "events"
|
|
||||||
|
|
||||||
import { Parser } from "./parser"
|
export type SourceFile
|
||||||
import { TypeChecker } from "./checker"
|
= BoltSourceFile
|
||||||
import { Evaluator } from "./evaluator"
|
| JSSourceFile
|
||||||
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 class Program {
|
export class Program {
|
||||||
|
|
||||||
public parser: Parser
|
private transformed = new FastStringMap<string, SourceFile>();
|
||||||
public evaluator: Evaluator;
|
|
||||||
public checker: TypeChecker;
|
|
||||||
public expander: Expander;
|
|
||||||
public timing: Timing;
|
|
||||||
|
|
||||||
constructor(public files: string[]) {
|
constructor(
|
||||||
this.checker = new TypeChecker();
|
sourceFiles: BoltSourceFile[]
|
||||||
this.parser = new Parser();
|
) {
|
||||||
this.evaluator = new Evaluator(this.checker);
|
for (const sourceFile of sourceFiles) {
|
||||||
this.expander = new Expander(this.parser, this.evaluator, this.checker);
|
this.transformed.set(sourceFile.span!.file.fullPath, sourceFile);
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapToTargetFile(node: Syntax) {
|
public getAllSourceFiles() {
|
||||||
return path.join('.bolt-work', getFileStem(node.span!.file.fullPath) + getDefaultExtension(getLanguage(node)));
|
return this.transformed.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
public eval() {
|
public updateSourceFile(oldSourceFile: SourceFile, newSourceFile: SourceFile): void {
|
||||||
for (const filename of this.files) {
|
if (!this.transformed.has(oldSourceFile.span!.file.fullPath)) {
|
||||||
const file = this.getTextFile(filename);
|
throw new Error(`Could not update ${oldSourceFile.span!.file.origPath} because it was not found in this program.`);
|
||||||
const expanded = this.getFullyExpandedSourceFile(file);
|
|
||||||
this.evaluator.eval(expanded)
|
|
||||||
}
|
}
|
||||||
|
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,9 +2,54 @@
|
||||||
const exported = {};
|
const exported = {};
|
||||||
|
|
||||||
const nodeProto = {
|
const nodeProto = {
|
||||||
preorder() {
|
|
||||||
|
|
||||||
|
*getChildNodes() {
|
||||||
|
for (const key of Object.keys(this)) {
|
||||||
|
if (key === 'span' || key === 'parentNode') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const value = this[key];
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
for (const element of value) {
|
||||||
|
if (isSyntax(element)) {
|
||||||
|
yield element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (isSyntax(value)) {
|
||||||
|
if (isSyntax(value)) {
|
||||||
|
yield value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
*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) {
|
function isSyntax(value) {
|
||||||
|
@ -13,26 +58,6 @@ function isSyntax(value) {
|
||||||
&& value.__NODE_TYPE !== undefined;
|
&& value.__NODE_TYPE !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function* getChildNodes(node) {
|
|
||||||
for (const key of Object.keys(node)) {
|
|
||||||
if (key === 'span' || key === 'parentNode') {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const value = node[key];
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
for (const element of value) {
|
|
||||||
if (isSyntax(element)) {
|
|
||||||
yield element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (isSyntax(value)) {
|
|
||||||
if (isSyntax(value)) {
|
|
||||||
yield value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createNode(nodeType) {
|
function createNode(nodeType) {
|
||||||
const obj = Object.create(nodeProto);
|
const obj = Object.create(nodeProto);
|
||||||
Object.defineProperty(obj, '__NODE_TYPE', {
|
Object.defineProperty(obj, '__NODE_TYPE', {
|
||||||
|
|
|
@ -110,17 +110,22 @@ export function setParents(node: Syntax): void;
|
||||||
|
|
||||||
export type SyntaxRange = [Syntax, Syntax];
|
export type SyntaxRange = [Syntax, Syntax];
|
||||||
|
|
||||||
interface SyntaxBase {
|
interface SyntaxBase<K extends SyntaxKind> {
|
||||||
kind: SyntaxKind;
|
kind: K;
|
||||||
parentNode: Syntax | null;
|
parentNode: ParentTypesOf<K> | null;
|
||||||
span: TextSpan | 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) {
|
for (const decl of decls) {
|
||||||
if (decl.type === 'NodeDeclaration') {
|
if (decl.type === 'NodeDeclaration') {
|
||||||
if (isLeafNode(decl.name)) {
|
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.indent()
|
||||||
dtsFile.write(`kind: SyntaxKind.${decl.name};\n`);
|
dtsFile.write(`kind: SyntaxKind.${decl.name};\n`);
|
||||||
for (const field of getAllFields(decl)) {
|
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) {
|
for (const langName of langNames) {
|
||||||
dtsFile.write(`export type ${langName}Syntax\n`);
|
dtsFile.write(`export type ${langName}Syntax\n`);
|
||||||
let first = true;
|
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 { Scanner } from "./scanner"
|
||||||
import { kindToString, Syntax, BoltQualName, BoltDeclaration, BoltDeclarationModifiers, createEndOfFile, SyntaxKind, isBoltPunctuated } from "./ast"
|
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) {
|
export function createTokenStream(node: Syntax) {
|
||||||
if (isBoltPunctuated(node)) {
|
if (isBoltPunctuated(node)) {
|
||||||
const origPos = node.span!.start;
|
const origPos = node.span!.start;
|
||||||
|
@ -52,6 +58,12 @@ export class FastStringMap<K extends PropertyKey, V> {
|
||||||
return this.mapping[key];
|
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 {
|
public set(key: K, value: V): void {
|
||||||
if (key in this.mapping) {
|
if (key in this.mapping) {
|
||||||
throw new Error(`A value for key '${key}' already exists.`);
|
throw new Error(`A value for key '${key}' already exists.`);
|
||||||
|
|
Loading…
Reference in a new issue