2020-03-03 14:53:54 +01:00
|
|
|
|
|
|
|
import * as path from "path"
|
2020-05-10 11:58:07 +02:00
|
|
|
import * as fs from "fs-extra"
|
2020-05-10 23:50:42 +02:00
|
|
|
import { now } from "microtime"
|
|
|
|
import { EventEmitter } from "events"
|
2020-03-03 14:53:54 +01:00
|
|
|
|
|
|
|
import { Parser } from "./parser"
|
|
|
|
import { TypeChecker } from "./checker"
|
|
|
|
import { Evaluator } from "./evaluator"
|
|
|
|
import { Expander } from "./expander"
|
|
|
|
import { Scanner } from "./scanner"
|
|
|
|
import { Compiler } from "./compiler"
|
2020-05-10 11:58:07 +02:00
|
|
|
import { emit } from "./emitter"
|
2020-05-10 15:56:34 +02:00
|
|
|
import { TextFile } from "./text"
|
2020-05-10 23:50:42 +02:00
|
|
|
import { BoltSourceFile, Syntax, JSSourceFile } from "./ast"
|
2020-05-10 15:56:34 +02:00
|
|
|
import { upsearchSync, FastStringMap, getFileStem, getLanguage } from "./util"
|
2020-05-10 11:58:07 +02:00
|
|
|
import { Package } from "./package"
|
2020-05-10 23:50:42 +02:00
|
|
|
import { verbose, memoize } from "./util"
|
2020-05-10 11:58:07 +02:00
|
|
|
|
|
|
|
const targetExtensions: FastStringMap<string> = {
|
2020-05-10 15:56:34 +02:00
|
|
|
'JS': '.mjs',
|
|
|
|
'Bolt': '.bolt',
|
|
|
|
'C': '.c',
|
2020-05-10 11:58:07 +02:00
|
|
|
};
|
2020-03-03 14:53:54 +01:00
|
|
|
|
2020-05-10 23:50:42 +02:00
|
|
|
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}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-03-03 14:53:54 +01:00
|
|
|
export class Program {
|
|
|
|
|
2020-05-10 11:58:07 +02:00
|
|
|
public parser: Parser
|
|
|
|
public evaluator: Evaluator;
|
|
|
|
public checker: TypeChecker;
|
|
|
|
public expander: Expander;
|
2020-05-10 23:50:42 +02:00
|
|
|
public timing: Timing;
|
2020-03-03 14:53:54 +01:00
|
|
|
|
2020-05-10 23:50:42 +02:00
|
|
|
constructor(public files: string[]) {
|
2020-03-03 14:53:54 +01:00
|
|
|
this.checker = new TypeChecker();
|
|
|
|
this.parser = new Parser();
|
|
|
|
this.evaluator = new Evaluator(this.checker);
|
|
|
|
this.expander = new Expander(this.parser, this.evaluator, this.checker);
|
2020-05-10 23:50:42 +02:00
|
|
|
this.timing = new Timing();
|
|
|
|
}
|
|
|
|
|
|
|
|
@memoize
|
|
|
|
public getTextFile(filename: string): TextFile {
|
|
|
|
return new TextFile(filename);
|
2020-03-03 14:53:54 +01:00
|
|
|
}
|
|
|
|
|
2020-05-10 23:50:42 +02:00
|
|
|
@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
|
2020-05-10 11:58:07 +02:00
|
|
|
public getPackage(filepath: string) {
|
2020-05-10 23:50:42 +02:00
|
|
|
const file = this.getTextFile(filepath)
|
|
|
|
const projectFile = upsearchSync('Boltfile', path.dirname(file.fullPath));
|
2020-05-10 11:58:07 +02:00
|
|
|
if (projectFile === null) {
|
|
|
|
return null;
|
2020-03-03 14:53:54 +01:00
|
|
|
}
|
2020-05-10 11:58:07 +02:00
|
|
|
const projectDir = path.resolve(path.dirname(projectFile));
|
2020-05-10 23:50:42 +02:00
|
|
|
return new Package(projectDir);
|
2020-05-10 11:58:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public compile(target: string) {
|
|
|
|
const compiler = new Compiler(this, this.checker, { target })
|
2020-05-10 23:50:42 +02:00
|
|
|
const expanded = this.files.map(filename => this.getFullyExpandedSourceFile(this.getTextFile(filename)));
|
|
|
|
const compiled = compiler.compile(expanded) as JSSourceFile[];
|
2020-05-10 11:58:07 +02:00
|
|
|
for (const rootNode of compiled) {
|
2020-05-10 23:50:42 +02:00
|
|
|
//const filepath = rootNode.span!.file.fullPath;
|
|
|
|
//const pkg = this.getPackage(filepath);
|
|
|
|
//if (pkg !== null) {
|
|
|
|
//
|
|
|
|
//}
|
2020-05-10 15:56:34 +02:00
|
|
|
fs.mkdirp('.bolt-work');
|
|
|
|
fs.writeFileSync(this.mapToTargetFile(rootNode), emit(rootNode), 'utf8');
|
2020-05-10 11:58:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
private mapToTargetFile(node: Syntax) {
|
|
|
|
return path.join('.bolt-work', getFileStem(node.span!.file.fullPath) + getDefaultExtension(getLanguage(node)));
|
2020-03-03 14:53:54 +01:00
|
|
|
}
|
|
|
|
|
2020-05-10 23:50:42 +02:00
|
|
|
public eval() {
|
|
|
|
for (const filename of this.files) {
|
|
|
|
const file = this.getTextFile(filename);
|
|
|
|
const expanded = this.getFullyExpandedSourceFile(file);
|
|
|
|
this.evaluator.eval(expanded)
|
2020-03-03 14:53:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-10 11:58:07 +02:00
|
|
|
function getDefaultExtension(target: string) {
|
|
|
|
if (targetExtensions[target] === undefined) {
|
|
|
|
throw new Error(`Could not derive an appropriate extension for target "${target}".`)
|
|
|
|
}
|
|
|
|
return targetExtensions[target];
|
|
|
|
}
|
|
|
|
|