Reusable compiler

This commit is contained in:
Tony Garnock-Jones 2021-01-17 22:27:04 +01:00
parent 6408493ea3
commit 2ff3067fad
4 changed files with 127 additions and 15 deletions

View File

@ -1,4 +1,79 @@
#!/usr/bin/env node
import { main } from '../lib/compiler/main.js';
main(process.argv);
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
const argv =
yargs(hideBin(process.argv))
.completion()
.command('$0 [input]', 'Compile a single file', (yargs) => {
yargs
.positional('input', {
type: 'string',
description: 'Input filename',
})
.option('output', {
alias: 'o',
type: 'string',
description: 'Output filename (stdout if omitted)',
default: null,
})
.option('map', {
type: 'boolean',
description: 'Generate source maps',
default: true,
})
.option('map-extension', {
type: 'string',
description: 'Extension (e.g. ".map") to add to source map files; if omitted, source maps are generated inline',
default: null,
})
.option('runtime', {
type: 'string',
description: 'Path to require or import to get the Syndicate runtime',
default: '@syndicate/core',
})
.option('module', {
type: 'string',
description: 'es6 | require | global',
})
})
.argv;
const fs = require('fs');
const { compile } = require('../dist/syndicate.js').Compiler;
// console.log(argv);
const inputFilename = 'input' in argv ? argv.input : '/dev/stdin';
const source = fs.readFileSync(inputFilename, 'utf-8');
const { text, map } = compile({
source,
name: inputFilename,
runtime: argv.runtime,
module: argv.module,
});
map.sourcesContent = [source];
function mapDataURL() {
const mapData = Buffer.from(JSON.stringify(map)).toString('base64')
return `data:application/json;base64,${mapData}`;
}
if (argv.output !== null) {
if (!argv.map) {
fs.writeFileSync(argv.output, text);
} else if (argv.mapExtension) {
const mapFilename = argv.output + argv.mapExtension;
fs.writeFileSync(argv.output, text + `\n//# sourceMappingURL=${mapFilename}`);
fs.writeFileSync(mapFilename, JSON.stringify(map));
} else {
fs.writeFileSync(argv.output, text + `\n//# sourceMappingURL=${mapDataURL()}`);
}
} else {
if (!argv.map) {
console.log(text);
} else {
console.log(text + `\n//# sourceMappingURL=${mapDataURL()}`);
}
}

View File

@ -20,7 +20,8 @@
"types": "lib/index.d.ts",
"author": "Tony Garnock-Jones <tonyg@leastfixedpoint.com>",
"dependencies": {
"preserves": "0.4.0"
"preserves": "0.4.0",
"yargs": "^16.2.0"
},
"bin": {
"syndicatec": "./bin/syndicatec.js"

View File

@ -1,4 +1,3 @@
import * as fs from 'fs';
import * as S from '../syntax/index.js';
import { Substitution } from '../syntax/index.js';
import * as G from './grammar.js';
@ -13,17 +12,41 @@ export function stripShebang(items: S.Items): S.Items {
return items;
}
export function main(argv: string[]) {
let [ inputFilename ] = argv.slice(2);
inputFilename = inputFilename ?? '/dev/stdin';
const source = fs.readFileSync(inputFilename, 'utf-8');
export interface CompileOptions {
source: string,
name?: string,
runtime?: string,
module?: 'es6' | 'require' | 'global',
global?: string,
}
export interface CompilerOutput {
text: string,
map: S.SourceMap,
}
export function compile(options: CompileOptions): CompilerOutput {
const inputFilename = options.name ?? '/dev/stdin';
const source = options.source;
const moduleType = options.module ?? 'es6';
const scanner = new S.StringScanner(S.startPos(inputFilename), source);
const reader = new S.LaxReader(scanner);
let tree = stripShebang(reader.readToEnd());
let macro = new S.Templates();
tree = macro.template()`import * as __SYNDICATE__ from '@syndicate/core';\n${tree}`;
const runtime = options.runtime ?? '@syndicate/core';
switch (moduleType) {
case 'es6':
tree = macro.template()`import * as __SYNDICATE__ from ${JSON.stringify(runtime)};\n${tree}`;
break;
case 'require':
tree = macro.template()`const __SYNDICATE__ = require(${JSON.stringify(runtime)});\n${tree}`;
break;
case 'global':
tree = macro.template()`const __SYNDICATE__ = ${runtime};\n${tree}`;
break;
}
let passNumber = 0;
let expansionNeeded = true;
@ -167,19 +190,29 @@ export function main(argv: string[]) {
s => macro.template()`addChildFacet(function (thisFacet) {${s.body}});`);
expand(
G.bootStatement,
s => macro.template()`export function ${BootProc}(thisFacet) {${s}}`);
s => {
switch (moduleType) {
case 'es6':
return macro.template()`export function ${BootProc}(thisFacet) {${s}}`;
case 'global':
return macro.template()`module.exports.${BootProc} = function (thisFacet) {${s}};`;
case 'require':
return macro.template()`function ${BootProc}(thisFacet) {${s}}`;
}
});
expandFacetAction(
G.stopStatement,
s => macro.template()`_stop(function (thisFacet) {${s.body}});`);
}
// console.log(`\n\n\n======================================== FINAL OUTPUT\n`);
console.log(S.itemText(tree));
// console.log(S.itemText(tree));
const cw = new S.CodeWriter(inputFilename);
cw.emit(tree);
fs.writeFileSync('/tmp/adhoc.syndicate', cw.text);
const mm = cw.map;
mm.sourcesContent = [source];
fs.writeFileSync('/tmp/adhoc.syndicate.map', JSON.stringify(mm));
return {
text: cw.text,
map: cw.map,
};
}

View File

@ -1 +1,4 @@
export * as Grammar from './grammar.js';
export * as Internals from './internals.js';
export * as Codegen from './codegen.js';
export { compile, CompileOptions } from './codegen.js';