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:
Sam Vervaeck 2020-05-23 14:18:20 +02:00
parent 3f96b88866
commit f3d8b021c2
19 changed files with 1343 additions and 904 deletions

510
src/ast.d.ts vendored

File diff suppressed because it is too large Load diff

View file

@ -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);
} }
) )

View file

@ -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)}`);
}
}
}

View file

@ -1,7 +1 @@
export const TYPES = {
Program: Symbol('a Bolt program'),
TypeChecker: Symbol('the Bolt type checking system'),
FileManager: Symbol('the file manager'),
}

View file

@ -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) {

View file

@ -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;

View file

@ -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
View 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];
}

View file

@ -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);
} }

View file

@ -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
View 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;

View 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;

View 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
View 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
View 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);
}
}
}
}
}
}

View file

@ -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', {

View file

@ -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;

View file

@ -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.`);