bolt/src/bin/bolt.ts

228 lines
6 KiB
TypeScript
Raw Normal View History

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"
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
const checker = new TypeChecker()
2020-02-25 12:26:21 +01:00
for (const sourceFile of sourceFiles) {
const parser = new Parser()
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()
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();
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()
const outfiles = bundle.map(file => {
2020-02-25 17:55:17 +01:00
const text = emitter.emit(file);
fs.mkdirpSync('.bolt-work')
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')
return filepath
})
2020-02-25 17:55:17 +01:00
spawnSync('node', [outfiles[0]], { stdio: 'inherit' })
2020-02-25 17:55:17 +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