2020-02-24 18:30:39 +01:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
2020-02-25 12:26:21 +01:00
|
|
|
import "reflect-metadata"
|
2020-02-24 18:35:28 +01:00
|
|
|
import "source-map-support/register"
|
|
|
|
|
2020-02-25 17:55:17 +01:00
|
|
|
import * as path from "path"
|
|
|
|
import * as fs from "fs-extra"
|
|
|
|
import { spawnSync } from "child_process"
|
2020-02-24 18:30:39 +01:00
|
|
|
|
|
|
|
import yargs from "yargs"
|
|
|
|
|
|
|
|
import { Scanner } from "../scanner"
|
2020-02-25 12:26:21 +01:00
|
|
|
import { Parser } from "../parser"
|
|
|
|
import { Expander } from "../expander"
|
2020-02-25 17:55:17 +01:00
|
|
|
import { TypeChecker } from "../checker"
|
|
|
|
import { Compiler } from "../compiler"
|
2020-02-26 18:53:28 +01:00
|
|
|
import { Evaluator } from "../evaluator"
|
2020-02-25 17:55:17 +01:00
|
|
|
import { Emitter } from "../emitter"
|
|
|
|
import { TextFile, SourceFile, setParents } from "../ast"
|
2020-02-24 18:30:39 +01:00
|
|
|
|
2020-02-25 17:55:17 +01:00
|
|
|
function toArray<T>(value: T | T[]): T[] {
|
2020-02-24 18:30:39 +01:00
|
|
|
if (Array.isArray(value)) {
|
|
|
|
return value as T[]
|
|
|
|
}
|
|
|
|
return value === null || value === undefined ? [] : [value]
|
|
|
|
}
|
|
|
|
|
|
|
|
function pushAll<T>(array: T[], elements: T[]) {
|
|
|
|
for (const element of elements) {
|
|
|
|
array.push(element);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-25 17:55:17 +01:00
|
|
|
function stripExtension(filepath: string) {
|
|
|
|
const i = filepath.lastIndexOf('.');
|
|
|
|
return i !== -1 ? filepath.substring(0, i) : filepath
|
|
|
|
}
|
|
|
|
|
2020-02-24 18:30:39 +01:00
|
|
|
function flatMap<T>(array: T[], proc: (element: T) => T[]) {
|
|
|
|
let out: T[] = []
|
|
|
|
for (const element of array) {
|
|
|
|
pushAll(out, proc(element));
|
|
|
|
}
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
interface Hook {
|
|
|
|
timing: 'before' | 'after'
|
|
|
|
name: string
|
|
|
|
effects: string[]
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseHook(str: string): Hook {
|
|
|
|
let timing: 'before' | 'after' = 'before';
|
|
|
|
if (str[0] === '+') {
|
|
|
|
str = str.substring(1)
|
|
|
|
timing = 'after';
|
|
|
|
}
|
|
|
|
const [name, rawEffects] = str.split('=');
|
|
|
|
return {
|
|
|
|
timing,
|
|
|
|
name,
|
|
|
|
effects: rawEffects.split(','),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
yargs
|
2020-02-25 17:55:17 +01:00
|
|
|
|
2020-02-24 18:30:39 +01:00
|
|
|
.command(
|
|
|
|
|
|
|
|
'compile [files..]',
|
|
|
|
'Compile a set of source files',
|
|
|
|
|
|
|
|
yargs => yargs
|
|
|
|
.string('hook')
|
|
|
|
.describe('hook', 'Add a hook to a specific compile phase. See the manual for details.'),
|
|
|
|
|
|
|
|
args => {
|
|
|
|
|
|
|
|
const hooks: Hook[] = toArray(args.hook as string[] | string).map(parseHook);
|
2020-02-24 19:16:33 +01:00
|
|
|
const sourceFiles: SourceFile[] = [];
|
2020-02-24 18:30:39 +01:00
|
|
|
|
|
|
|
for (const filepath of toArray(args.files as string[] | string)) {
|
|
|
|
|
|
|
|
const file = new TextFile(filepath);
|
|
|
|
const content = fs.readFileSync(filepath, 'utf8')
|
|
|
|
const scanner = new Scanner(file, content)
|
|
|
|
|
|
|
|
for (const hook of hooks) {
|
|
|
|
if (hook.name === 'scan' && hook.timing === 'before') {
|
|
|
|
for (const effect of hook.effects) {
|
|
|
|
switch (effect) {
|
|
|
|
case 'abort':
|
|
|
|
process.exit(0);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error(`Could not execute hook effect '${effect}.`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-24 19:16:33 +01:00
|
|
|
const sourceFile = scanner.scan();
|
|
|
|
// while (true) {
|
|
|
|
// const token = scanner.scanToken()
|
|
|
|
// if (token === null) {
|
|
|
|
// break;
|
|
|
|
// }
|
|
|
|
// tokens.push(token);
|
|
|
|
// }
|
2020-02-24 18:30:39 +01:00
|
|
|
|
|
|
|
for (const hook of hooks) {
|
|
|
|
if (hook.name === 'scan' && hook.timing == 'after') {
|
|
|
|
for (const effect of hook.effects) {
|
|
|
|
switch (effect) {
|
|
|
|
case 'dump':
|
2020-02-24 19:16:33 +01:00
|
|
|
console.log(JSON.stringify(sourceFile.toJSON(), undefined, 2));
|
2020-02-24 18:30:39 +01:00
|
|
|
break;
|
|
|
|
case 'abort':
|
|
|
|
process.exit(0);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error(`Could not execute hook effect '${effect}'.`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-24 19:16:33 +01:00
|
|
|
sourceFiles.push(sourceFile);
|
|
|
|
|
2020-02-24 18:30:39 +01:00
|
|
|
}
|
2020-02-24 19:16:33 +01:00
|
|
|
|
2020-02-26 18:53:28 +01:00
|
|
|
const checker = new TypeChecker()
|
|
|
|
|
2020-02-25 12:26:21 +01:00
|
|
|
for (const sourceFile of sourceFiles) {
|
|
|
|
const parser = new Parser()
|
2020-02-26 18:53:28 +01:00
|
|
|
const evaluator = new Evaluator(checker)
|
|
|
|
const expander = new Expander(parser, evaluator, checker)
|
2020-02-25 12:26:21 +01:00
|
|
|
const expandedSourceFile = expander.getFullyExpanded(sourceFile)
|
|
|
|
|
|
|
|
for (const hook of hooks) {
|
|
|
|
if (hook.name === 'expand' && hook.timing == 'after') {
|
|
|
|
for (const effect of hook.effects) {
|
|
|
|
switch (effect) {
|
|
|
|
case 'dump':
|
|
|
|
console.log(JSON.stringify(expandedSourceFile.toJSON(), undefined, 2));
|
|
|
|
break;
|
|
|
|
case 'abort':
|
|
|
|
process.exit(0);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error(`Could not execute hook effect '${effect}'.`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-02-24 18:30:39 +01:00
|
|
|
})
|
|
|
|
|
2020-02-25 17:55:17 +01:00
|
|
|
.command(
|
|
|
|
|
|
|
|
'exec [files..]',
|
|
|
|
'Run the specified Bolt scripts',
|
|
|
|
|
|
|
|
yargs =>
|
|
|
|
yargs,
|
|
|
|
|
|
|
|
args => {
|
|
|
|
|
|
|
|
const parser = new Parser()
|
2020-02-26 18:53:28 +01:00
|
|
|
const checker = new TypeChecker()
|
2020-02-25 17:55:17 +01:00
|
|
|
|
|
|
|
const sourceFiles = toArray(args.files as string[]).map(filepath => {
|
|
|
|
const file = new TextFile(filepath)
|
|
|
|
const contents = fs.readFileSync(filepath, 'utf8')
|
|
|
|
const scanner = new Scanner(file, contents)
|
|
|
|
const sourceFile = scanner.scan();
|
2020-02-26 18:53:28 +01:00
|
|
|
const evaluator = new Evaluator(checker)
|
|
|
|
const expander = new Expander(parser, evaluator, checker)
|
|
|
|
const expanded = expander.getFullyExpanded(sourceFile) as SourceFile;
|
2020-02-25 17:55:17 +01:00
|
|
|
// console.log(require('util').inspect(expanded.toJSON(), { colors: true, depth: Infinity }))
|
|
|
|
setParents(expanded)
|
|
|
|
return expanded;
|
|
|
|
})
|
|
|
|
|
|
|
|
const compiler = new Compiler(checker, { target: "JS" })
|
|
|
|
const bundle = compiler.compile(sourceFiles)
|
|
|
|
const emitter = new Emitter()
|
2020-02-25 18:34:17 +01:00
|
|
|
const outfiles = bundle.map(file => {
|
2020-02-25 17:55:17 +01:00
|
|
|
const text = emitter.emit(file);
|
|
|
|
fs.mkdirpSync('.bolt-work')
|
2020-02-25 18:34:17 +01:00
|
|
|
const filepath = path.join('.bolt-work', path.relative(process.cwd(), stripExtension(path.resolve(file.loc.source)) + '.mjs'))
|
2020-02-25 17:55:17 +01:00
|
|
|
fs.writeFileSync(filepath, text, 'utf8')
|
2020-02-25 18:34:17 +01:00
|
|
|
return filepath
|
|
|
|
})
|
2020-02-25 17:55:17 +01:00
|
|
|
|
2020-02-25 18:34:17 +01:00
|
|
|
spawnSync('node', [outfiles[0]], { stdio: 'inherit' })
|
2020-02-25 17:55:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
)
|
|
|
|
|
2020-02-26 18:53:28 +01:00
|
|
|
.command(
|
|
|
|
|
|
|
|
'dump [file]',
|
|
|
|
'Dump a representation of a given primitive node to disk',
|
|
|
|
|
|
|
|
yargs => yargs,
|
|
|
|
|
|
|
|
args => {
|
|
|
|
|
|
|
|
const file = new TextFile(args.file as string)
|
|
|
|
const contents = fs.readFileSync(args.file, 'utf8')
|
|
|
|
const scanner = new Scanner(file, contents)
|
|
|
|
const parser = new Parser();
|
|
|
|
const patt = parser.parsePattern(scanner)
|
|
|
|
console.log(JSON.stringify(patt.toJSON(), undefined, 2))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
)
|
|
|
|
|
2020-02-24 18:30:39 +01:00
|
|
|
.help()
|
|
|
|
.version()
|
|
|
|
.argv
|
|
|
|
|