Module activation; batch compilation
This commit is contained in:
parent
8c2729e3d8
commit
7be246a400
|
@ -1,8 +1,8 @@
|
|||
import {
|
||||
isToken, isTokenType, replace, commaJoin, startPos, fixPos,
|
||||
isToken, isTokenType, replace, commaJoin, startPos, fixPos, joinItems,
|
||||
|
||||
Items, Pattern, Templates, Substitution, TokenType,
|
||||
SourceMap, StringScanner, LaxReader, CodeWriter, TemplateFunction,
|
||||
SourceMap, StringScanner, LaxReader, CodeWriter, TemplateFunction, Token,
|
||||
} from '../syntax/index.js';
|
||||
import {
|
||||
FacetAction, Statement,
|
||||
|
@ -21,6 +21,9 @@ import {
|
|||
reactStatement,
|
||||
bootStatement,
|
||||
stopStatement,
|
||||
Identifier,
|
||||
activationImport,
|
||||
ActivationImport,
|
||||
} from './grammar.js';
|
||||
import {
|
||||
BootProc,
|
||||
|
@ -54,7 +57,18 @@ function receiverFor(s: FacetAction): Substitution {
|
|||
return (s.implicitFacet) ? 'thisFacet.' : '.';
|
||||
}
|
||||
|
||||
export function expand(tree: Items, moduleType: ModuleType): Items {
|
||||
export interface ActivationRecord {
|
||||
activation: ActivationImport;
|
||||
activationScriptId: Identifier;
|
||||
}
|
||||
|
||||
export interface ExpansionContext {
|
||||
moduleType: ModuleType;
|
||||
activationRecords: Array<ActivationRecord>;
|
||||
hasBootProc: boolean;
|
||||
}
|
||||
|
||||
export function expand(tree: Items, ctx: ExpansionContext): Items {
|
||||
const macro = new Templates();
|
||||
|
||||
function terminalWrap(t: TemplateFunction, isTerminal: boolean, body: Statement): Statement {
|
||||
|
@ -73,7 +87,7 @@ export function expand(tree: Items, moduleType: ModuleType): Items {
|
|||
x(p, (v, t) => t`${receiverFor(v)}${f(v, t)}`);
|
||||
}
|
||||
|
||||
const walk = (tree: Items): Items => expand(tree, moduleType);
|
||||
const walk = (tree: Items): Items => expand(tree, ctx);
|
||||
const maybeWalk = (tree?: Items) : Items | undefined => (tree === void 0) ? tree : walk(tree);
|
||||
|
||||
xf(duringStatement, (s, t) => {
|
||||
|
@ -173,14 +187,29 @@ export function expand(tree: Items, moduleType: ModuleType): Items {
|
|||
|
||||
xf(reactStatement, (s, t) => t`addChildFacet(function (thisFacet) {${walk(s.body)}});`);
|
||||
|
||||
x(activationImport, (s, t) => {
|
||||
const activationScriptId: Token = {
|
||||
start: s.activationKeyword.start,
|
||||
end: s.activationKeyword.end,
|
||||
text: `__SYNDICATE__activationScript${'' + ctx.activationRecords.length}`,
|
||||
type: TokenType.ATOM
|
||||
};
|
||||
ctx.activationRecords.push({ activation: s, activationScriptId });
|
||||
return [];
|
||||
}),
|
||||
|
||||
x(bootStatement, (s, t) => {
|
||||
switch (moduleType) {
|
||||
ctx.hasBootProc = true;
|
||||
const activationStatements = ctx.activationRecords.map(({ activationScriptId: id }) =>
|
||||
t`thisFacet.activate(${[id]}); `);
|
||||
const body = t`${joinItems(activationStatements)}${walk(s)}`;
|
||||
switch (ctx.moduleType) {
|
||||
case 'es6':
|
||||
return t`export function ${BootProc}(thisFacet) {${walk(s)}}`;
|
||||
return t`export function ${BootProc}(thisFacet) {${body}}`;
|
||||
case 'require':
|
||||
return t`module.exports.${BootProc} = function (thisFacet) {${walk(s)}};`;
|
||||
return t`module.exports.${BootProc} = function (thisFacet) {${body}};`;
|
||||
case 'global':
|
||||
return t`function ${BootProc}(thisFacet) {${walk(s)}}`;
|
||||
return t`function ${BootProc}(thisFacet) {${body}}`;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -195,34 +224,67 @@ export function compile(options: CompileOptions): CompilerOutput {
|
|||
const moduleType = options.module ?? 'es6';
|
||||
|
||||
const start = startPos(inputFilename);
|
||||
|
||||
const scanner = new StringScanner(start, source);
|
||||
const reader = new LaxReader(scanner);
|
||||
let tree = stripShebang(reader.readToEnd());
|
||||
const end = tree.length > 0 ? tree[tree.length - 1].end : start;
|
||||
|
||||
let macro = new Templates();
|
||||
|
||||
const end = tree.length > 0 ? tree[tree.length - 1].end : start;
|
||||
const ctx: ExpansionContext = {
|
||||
moduleType,
|
||||
activationRecords: [],
|
||||
hasBootProc: false,
|
||||
}
|
||||
|
||||
tree = expand(tree, ctx);
|
||||
|
||||
const ts = macro.template(fixPos(start));
|
||||
const te = macro.template(fixPos(end));
|
||||
|
||||
if (ctx.hasBootProc) {
|
||||
let bp;
|
||||
switch (moduleType) {
|
||||
case 'es6':
|
||||
case 'global':
|
||||
bp = BootProc;
|
||||
break;
|
||||
case 'require':
|
||||
bp = te`module.exports.${BootProc}`;
|
||||
break;
|
||||
}
|
||||
tree = te`${tree}\nif (typeof module !== 'undefined' && ((typeof require === 'undefined' ? {main: void 0} : require).main === module)) __SYNDICATE__.bootModule(${bp});`;
|
||||
}
|
||||
|
||||
const activationImports = ctx.activationRecords.map(r => {
|
||||
const a = r.activation;
|
||||
const t = macro.template(a.activationKeyword.start);
|
||||
switch (a.target.type) {
|
||||
case 'import':
|
||||
return t`import { ${BootProc} as ${[r.activationScriptId]} } from ${[a.target.moduleName]};\n`;
|
||||
case 'expr':
|
||||
return t`const ${[r.activationScriptId]} = (${a.target.moduleExpr}).${BootProc};\n`;
|
||||
}
|
||||
});
|
||||
tree = ts`${joinItems(activationImports)}${tree}`;
|
||||
|
||||
{
|
||||
const runtime = options.runtime ?? '@syndicate-lang/core';
|
||||
const t = macro.template(fixPos(start));
|
||||
switch (moduleType) {
|
||||
case 'es6':
|
||||
tree = t`import * as __SYNDICATE__ from ${JSON.stringify(runtime)};\n${tree}`;
|
||||
tree = ts`import * as __SYNDICATE__ from ${JSON.stringify(runtime)};\n${tree}`;
|
||||
break;
|
||||
case 'require':
|
||||
tree = t`const __SYNDICATE__ = require(${JSON.stringify(runtime)});\n${tree}`;
|
||||
tree = ts`const __SYNDICATE__ = require(${JSON.stringify(runtime)});\n${tree}`;
|
||||
break;
|
||||
case 'global':
|
||||
tree = t`const __SYNDICATE__ = ${runtime};\n${tree}`;
|
||||
tree = ts`const __SYNDICATE__ = ${runtime};\n${tree}`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
tree = macro.template(fixPos(end))`${tree}\nif (typeof module !== 'undefined' && ((typeof require === 'undefined' ? {main: void 0} : require).main === module)) __SYNDICATE__.bootModule(${BootProc});`;
|
||||
|
||||
const cw = new CodeWriter(inputFilename);
|
||||
cw.emit(expand(tree, moduleType));
|
||||
cw.emit(tree);
|
||||
|
||||
return {
|
||||
text: cw.text,
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
|
||||
scope, bind, seq, alt, upTo, atom, atomString, group, exec,
|
||||
repeat, option, withoutSpace, map, mapm, rest, discard,
|
||||
value, succeed, fail, separatedBy, anything, not,
|
||||
value, succeed, fail, separatedBy, anything, not, follows,
|
||||
} from '../syntax/index.js';
|
||||
import * as Matcher from '../syntax/matcher.js';
|
||||
import { Path, Skeleton } from './internals.js';
|
||||
|
@ -244,6 +244,21 @@ export const bootStatement: Pattern<Statement> =
|
|||
// Principal: Facet
|
||||
export const stopStatement = blockFacetAction(atom('stop'));
|
||||
|
||||
export interface ActivationImport {
|
||||
activationKeyword: Identifier;
|
||||
target: { type: 'import', moduleName: Token } | { type: 'expr', moduleExpr: Expr };
|
||||
}
|
||||
|
||||
// Principal: none
|
||||
export const activationImport: Pattern<ActivationImport> =
|
||||
scope(o => seq(bind(o, 'activationKeyword', atom('activate')),
|
||||
follows(alt<any>(seq(atom('import'),
|
||||
upTo(seq(
|
||||
map(atom(void 0, { tokenType: TokenType.STRING }),
|
||||
n => o.target = { type: 'import', moduleName: n }),
|
||||
statementBoundary))),
|
||||
map(expr(), e => o.target = { type: 'expr', moduleExpr: e })))));
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Syntax of patterns over Value, used in endpoints
|
||||
|
||||
|
|
|
@ -56,6 +56,14 @@ export function not<T>(p: Pattern<any>, v: T): Pattern<T> {
|
|||
return i => p(i) === null ? [v, i] : null;
|
||||
}
|
||||
|
||||
export function follows(p: Pattern<any>): Pattern<undefined> {
|
||||
return i => {
|
||||
const r = p(i);
|
||||
if (r === null) return null;
|
||||
return [r[0], i];
|
||||
};
|
||||
}
|
||||
|
||||
export function seq(... patterns: Pattern<any>[]): Pattern<any> {
|
||||
return i => {
|
||||
for (const p of patterns) {
|
||||
|
|
|
@ -53,7 +53,7 @@ export class Templates {
|
|||
}
|
||||
}
|
||||
|
||||
export function joinItems(itemss: Items[], separator0: Substitution): Items {
|
||||
export function joinItems(itemss: Items[], separator0: Substitution = ''): Items {
|
||||
if (itemss.length === 0) return [];
|
||||
const separator = toItems(separator0, startPos(null));
|
||||
const acc = itemss[0];
|
||||
|
|
|
@ -68,6 +68,8 @@ export function _canonicalizeDataflowDependent(i: DataflowDependent): string {
|
|||
return '' + i.id;
|
||||
}
|
||||
|
||||
export type ActivationScript = Script<void>;
|
||||
|
||||
export abstract class Dataspace {
|
||||
nextId: ActorId = 0;
|
||||
index = new Skeleton.Index();
|
||||
|
@ -77,6 +79,7 @@ export abstract class Dataspace {
|
|||
runnable: Array<Actor> = [];
|
||||
pendingTurns: Array<Turn>;
|
||||
actors: IdentityMap<number, Actor> = new IdentityMap();
|
||||
activations: IdentitySet<ActivationScript> = new IdentitySet();
|
||||
|
||||
constructor(bootProc: Script<void>) {
|
||||
this.pendingTurns = [new Turn(null, [new Spawn(null, bootProc, new Set())])];
|
||||
|
@ -390,6 +393,23 @@ class DeferredTurn extends Action {
|
|||
}
|
||||
}
|
||||
|
||||
class Activation extends Action {
|
||||
readonly script: ActivationScript;
|
||||
readonly name: any;
|
||||
|
||||
constructor(script: ActivationScript, name: any) {
|
||||
super();
|
||||
this.script = script;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
perform(ds: Dataspace, ac: Actor | null): void {
|
||||
if (ds.activations.has(this.script)) return;
|
||||
ds.activations.add(this.script);
|
||||
ds.addActor(this.name, rootFacet => rootFacet.addStartScript(this.script), new Set(), ac);
|
||||
}
|
||||
}
|
||||
|
||||
export class Turn {
|
||||
readonly actor: Actor | null;
|
||||
readonly actions: Array<Action>;
|
||||
|
@ -623,9 +643,9 @@ export class Facet {
|
|||
}
|
||||
}
|
||||
|
||||
ensureNonFacetSetup(what: string, keyword: string) {
|
||||
ensureNonFacetSetup(what: string) {
|
||||
if (!this.inScript) {
|
||||
throw new Error(`Cannot ${what} during facet setup; are you missing \`${keyword} { ... }\`?`);
|
||||
throw new Error(`Cannot ${what} during facet setup; are you missing \`on start { ... }\`?`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -635,7 +655,7 @@ export class Facet {
|
|||
}
|
||||
|
||||
send(body: any) {
|
||||
this.ensureNonFacetSetup('`send`', 'on start');
|
||||
this.ensureNonFacetSetup('`send`');
|
||||
this.enqueueScriptAction(new Message(body));
|
||||
}
|
||||
|
||||
|
@ -645,15 +665,20 @@ export class Facet {
|
|||
}
|
||||
|
||||
spawn(name: any, bootProc: Script<void>, initialAssertions?: Set) {
|
||||
this.ensureNonFacetSetup('`spawn`', 'on start');
|
||||
this.ensureNonFacetSetup('`spawn`');
|
||||
this.enqueueScriptAction(new Spawn(name, bootProc, initialAssertions));
|
||||
}
|
||||
|
||||
deferTurn(continuation: Script<void>) {
|
||||
this.ensureNonFacetSetup('`deferTurn`', 'on start');
|
||||
this.ensureNonFacetSetup('`deferTurn`');
|
||||
this.enqueueScriptAction(new DeferredTurn(this.wrap(continuation)));
|
||||
}
|
||||
|
||||
activate(script: ActivationScript, name?: any) {
|
||||
this.ensureNonFacetSetup('`activate`');
|
||||
this.enqueueScriptAction(new Activation(script, name ?? null));
|
||||
}
|
||||
|
||||
scheduleScript(script: Script<void>, priority?: Priority) {
|
||||
this.actor.scheduleTask(this.wrap(script), priority);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
import { Dataspace, Script } from './dataspace.js';
|
||||
import { ActivationScript, Dataspace } from './dataspace.js';
|
||||
|
||||
export type StopHandler<D extends Dataspace> = (ds: D) => void;
|
||||
|
||||
|
@ -33,7 +33,7 @@ export class Ground extends Dataspace {
|
|||
stopHandlers: Array<StopHandler<this>> = [];
|
||||
backgroundTaskCount = 0;
|
||||
|
||||
constructor(bootProc: Script<void>) {
|
||||
constructor(bootProc: ActivationScript) {
|
||||
super(function (rootFacet) { rootFacet.addStartScript(bootProc); });
|
||||
if (typeof window !== 'undefined') {
|
||||
window._ground = this;
|
||||
|
@ -118,12 +118,14 @@ export class Ground extends Dataspace {
|
|||
// if (k) k(g);
|
||||
// }
|
||||
|
||||
export function bootModule(bootProc: Script<void>): Ground {
|
||||
export function bootModule(bootProc: ActivationScript): Ground {
|
||||
const g = new Ground(bootProc);
|
||||
if (typeof document !== 'undefined') {
|
||||
document.addEventListener('DOMContentLoaded', () => g.start());
|
||||
} else {
|
||||
g.start();
|
||||
}
|
||||
Ground.laterCall(() => {
|
||||
if (typeof document !== 'undefined') {
|
||||
document.addEventListener('DOMContentLoaded', () => g.start());
|
||||
} else {
|
||||
g.start();
|
||||
}
|
||||
});
|
||||
return g;
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
index.js
|
||||
index.js.map
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype HTML>
|
||||
<html>
|
||||
<meta charset=utf-8>
|
||||
<title>Demo</title>
|
||||
<script src="node_modules/@syndicate-lang/core/dist/syndicate.js"></script>
|
||||
<script src="index.js"></script>
|
||||
<h1>Look in the JavaScript console for output.</h1>
|
||||
<main id="main">
|
||||
</main>
|
||||
<script>
|
||||
Syndicate.bootModule(Main.__SYNDICATE__bootProc);
|
||||
</script>
|
||||
</html>
|
|
@ -4,9 +4,10 @@
|
|||
"description": "Simple syndicatec example",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"prepare": "npm run compile",
|
||||
"compile": "npx syndicatec -o index.js --module global --runtime Syndicate index.syndicate.js",
|
||||
"clean": "rm -f index.js"
|
||||
"prepare": "npm run compile && npm run rollup",
|
||||
"compile": "npx syndicatec -d lib -b src 'src/**/*.js'",
|
||||
"rollup": "npx rollup -c",
|
||||
"clean": "rm -rf lib/ index.js index.js.map"
|
||||
},
|
||||
"author": "Tony Garnock-Jones <tonyg@leastfixedpoint.com>",
|
||||
"license": "GPL-3.0+",
|
||||
|
@ -14,6 +15,8 @@
|
|||
"@syndicate-lang/core": "file:../../../core"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@syndicate-lang/syndicatec": "file:../.."
|
||||
"@syndicate-lang/syndicatec": "file:../..",
|
||||
"rollup": "^2.37.0",
|
||||
"rollup-plugin-sourcemaps": "^0.6.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import sourcemaps from 'rollup-plugin-sourcemaps';
|
||||
|
||||
export default {
|
||||
input: 'lib/index.js',
|
||||
plugins: [sourcemaps()],
|
||||
output: {
|
||||
file: 'index.js',
|
||||
format: 'umd',
|
||||
name: 'Main',
|
||||
sourcemap: true,
|
||||
globals: {
|
||||
'@syndicate-lang/core': 'Syndicate',
|
||||
},
|
||||
},
|
||||
};
|
15
packages/syndicatec/examples/javascript/index.syndicate.js → packages/syndicatec/examples/javascript/src/box.js
Executable file → Normal file
15
packages/syndicatec/examples/javascript/index.syndicate.js → packages/syndicatec/examples/javascript/src/box.js
Executable file → Normal file
|
@ -16,12 +16,7 @@
|
|||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
assertion type BoxState(value);
|
||||
message type SetBox(newValue);
|
||||
|
||||
const N = 100000;
|
||||
|
||||
console.time('box-and-client-' + N.toString());
|
||||
import { BoxState, SetBox, N } from './protocol.js';
|
||||
|
||||
boot {
|
||||
spawn named 'box' {
|
||||
|
@ -31,12 +26,4 @@ boot {
|
|||
console.log('terminated box root facet');
|
||||
on message SetBox($v) => this.value = v;
|
||||
}
|
||||
|
||||
spawn named 'client' {
|
||||
on asserted BoxState($v) => send message SetBox(v + 1);
|
||||
on retracted BoxState(_) => console.log('box gone');
|
||||
}
|
||||
|
||||
thisFacet.actor.dataspace.addStopHandler(() =>
|
||||
console.timeEnd('box-and-client-' + N.toString()));
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
//---------------------------------------------------------------------------
|
||||
// @syndicate-lang/core, an implementation of Syndicate dataspaces for JS.
|
||||
// Copyright (C) 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
import { BoxState, SetBox } from './protocol.js';
|
||||
|
||||
boot {
|
||||
spawn named 'client' {
|
||||
on asserted BoxState($v) => send message SetBox(v + 1);
|
||||
on retracted BoxState(_) => console.log('box gone');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
//---------------------------------------------------------------------------
|
||||
// @syndicate-lang/core, an implementation of Syndicate dataspaces for JS.
|
||||
// Copyright (C) 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
import { N } from './protocol.js';
|
||||
activate import './box.js';
|
||||
activate import './client.js';
|
||||
|
||||
console.time('box-and-client-' + N.toString());
|
||||
boot {
|
||||
thisFacet.actor.dataspace.addStopHandler(() =>
|
||||
console.timeEnd('box-and-client-' + N.toString()));
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
//---------------------------------------------------------------------------
|
||||
// @syndicate-lang/core, an implementation of Syndicate dataspaces for JS.
|
||||
// Copyright (C) 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
export assertion type BoxState(value);
|
||||
export message type SetBox(newValue);
|
||||
|
||||
export const N = 100000;
|
|
@ -16,6 +16,7 @@
|
|||
"dependencies": {
|
||||
"@syndicate-lang/compiler": "file:../compiler",
|
||||
"@syndicate-lang/core": "file:../core",
|
||||
"glob": "^7.1.6",
|
||||
"yargs": "^16.2.0"
|
||||
},
|
||||
"bin": {
|
||||
|
|
|
@ -2,6 +2,9 @@ import yargs from 'yargs/yargs';
|
|||
import { Argv } from 'yargs';
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { glob } from 'glob';
|
||||
|
||||
import { compile } from '@syndicate-lang/compiler';
|
||||
|
||||
export type ModuleChoice = 'es6' | 'require' | 'global';
|
||||
|
@ -9,7 +12,9 @@ const moduleChoices: ReadonlyArray<ModuleChoice> = ['es6', 'require', 'global'];
|
|||
|
||||
export type CommandLineArguments = {
|
||||
input: string | undefined;
|
||||
output: string | undefined;
|
||||
outputDirectory?: string | undefined;
|
||||
rootDirectory?: string;
|
||||
rename: string | undefined;
|
||||
map: boolean;
|
||||
mapExtension?: string;
|
||||
runtime: string;
|
||||
|
@ -22,19 +27,62 @@ function checkModuleChoice<T>(t: T & { module: string }): T & { module: ModuleCh
|
|||
throw new Error("Illegal --module argument: " + t.module);
|
||||
}
|
||||
|
||||
function makeRenamer(outputDir: string,
|
||||
rootDir: string,
|
||||
renamePattern: string | undefined): (f: string) => string
|
||||
{
|
||||
const rewrites: Array<(f: string) => (string | null)> =
|
||||
(renamePattern === void 0 ? [] : renamePattern.split(/,/)).map(p => {
|
||||
const [ from, to ] = p.split(/:/);
|
||||
let mFrom = /([^%]*)%([^%]*)/.exec(from);
|
||||
let mTo = /([^%]*)%([^%]*)/.exec(to);
|
||||
if (mFrom === null && mTo === null) {
|
||||
return f => (f === from) ? to : null;
|
||||
} else if (mFrom === null || mTo === null) {
|
||||
throw new Error(`Invalid --rename pattern: ${JSON.stringify(p)}`);
|
||||
} else {
|
||||
const [fh, ft] = mFrom.slice(1);
|
||||
const [th, tt] = mTo.slice(1);
|
||||
return f =>
|
||||
(f.startsWith(fh) && f.endsWith(ft))
|
||||
? th + f.substring(fh.length, f.length - ft.length) + tt
|
||||
: null;
|
||||
}
|
||||
});
|
||||
const relocate = (f: string) => path.join(outputDir, path.relative(rootDir, f));
|
||||
return f => {
|
||||
for (const rewrite of rewrites) {
|
||||
const t = rewrite(f);
|
||||
if (t !== null) return relocate(t);
|
||||
}
|
||||
return relocate(f);
|
||||
};
|
||||
}
|
||||
|
||||
export function main(argv: string[]) {
|
||||
const options: CommandLineArguments = checkModuleChoice(yargs(argv)
|
||||
.command('$0 [input]',
|
||||
'Compile a single file',
|
||||
'Compile away Syndicate extensions',
|
||||
yargs => yargs
|
||||
.positional('input', {
|
||||
type: 'string',
|
||||
description: 'Input filename',
|
||||
description: 'Input filename or glob (stdin if omitted)',
|
||||
})
|
||||
.option('output', {
|
||||
alias: 'o',
|
||||
.option('root-directory', {
|
||||
alias: 'b',
|
||||
type: 'string',
|
||||
description: 'Output filename (stdout if omitted)',
|
||||
description: 'Root directory for input files',
|
||||
default: '.',
|
||||
})
|
||||
.option('output-directory', {
|
||||
alias: 'd',
|
||||
type: 'string',
|
||||
description: 'Output directory (if omitted: stdout if stdin as input, else cwd)',
|
||||
})
|
||||
.option('rename', {
|
||||
type: 'string',
|
||||
description: 'Rewrite input filenames',
|
||||
default: '%.syndicate.js:%.js,%.syndicate.ts:%.ts',
|
||||
})
|
||||
.option('map', {
|
||||
type: 'boolean',
|
||||
|
@ -59,37 +107,52 @@ export function main(argv: string[]) {
|
|||
argv => argv)
|
||||
.argv);
|
||||
|
||||
const inputFilename = options.input ?? '/dev/stdin';
|
||||
const source = fs.readFileSync(inputFilename, 'utf-8');
|
||||
const rename = makeRenamer(options.outputDirectory ?? '',
|
||||
options.rootDirectory ?? '.',
|
||||
options.rename);
|
||||
|
||||
const { text, map } = compile({
|
||||
source,
|
||||
name: inputFilename,
|
||||
runtime: options.runtime,
|
||||
module: options.module,
|
||||
});
|
||||
map.sourcesContent = [source];
|
||||
const STDIN = '/dev/stdin';
|
||||
|
||||
function mapDataURL() {
|
||||
const mapData = Buffer.from(JSON.stringify(map)).toString('base64')
|
||||
return `data:application/json;base64,${mapData}`;
|
||||
}
|
||||
const inputGlob = options.input ?? STDIN;
|
||||
const inputFilenames = glob.sync(inputGlob);
|
||||
|
||||
for (const inputFilename of inputFilenames) {
|
||||
const outputFilename =
|
||||
(inputFilename === STDIN) ? '/dev/stdout' :
|
||||
(inputFilename[0] === '/') ? (() => { throw new Error("Absolute input paths are not supported"); })() :
|
||||
rename(inputFilename);
|
||||
|
||||
if (inputFilenames.indexOf(outputFilename) !== -1) {
|
||||
throw new Error(`Output from ${JSON.stringify(inputFilename)} would trample on existing input file ${JSON.stringify(outputFilename)}`);
|
||||
}
|
||||
|
||||
const source = fs.readFileSync(inputFilename, 'utf-8');
|
||||
|
||||
const { text, map } = compile({
|
||||
source,
|
||||
name: inputFilename,
|
||||
runtime: options.runtime,
|
||||
module: options.module,
|
||||
});
|
||||
map.sourcesContent = [source];
|
||||
|
||||
function mapDataURL() {
|
||||
const mapData = Buffer.from(JSON.stringify(map)).toString('base64')
|
||||
return `data:application/json;base64,${mapData}`;
|
||||
}
|
||||
|
||||
if (inputFilename !== STDIN) {
|
||||
fs.mkdirSync(path.dirname(outputFilename), { recursive: true });
|
||||
}
|
||||
|
||||
if (options.output !== void 0) {
|
||||
if (!options.map) {
|
||||
fs.writeFileSync(options.output, text);
|
||||
} else if (options.mapExtension) {
|
||||
const mapFilename = options.output + options.mapExtension;
|
||||
fs.writeFileSync(options.output, text + `\n//# sourceMappingURL=${mapFilename}`);
|
||||
fs.writeFileSync(outputFilename, text);
|
||||
} else if (options.mapExtension && inputFilename !== STDIN) {
|
||||
const mapFilename = outputFilename + options.mapExtension;
|
||||
fs.writeFileSync(outputFilename, text + `\n//# sourceMappingURL=${mapFilename}`);
|
||||
fs.writeFileSync(mapFilename, JSON.stringify(map));
|
||||
} else {
|
||||
fs.writeFileSync(options.output, text + `\n//# sourceMappingURL=${mapDataURL()}`);
|
||||
}
|
||||
} else {
|
||||
if (!options.map) {
|
||||
console.log(text);
|
||||
} else {
|
||||
console.log(text + `\n//# sourceMappingURL=${mapDataURL()}`);
|
||||
fs.writeFileSync(outputFilename, text + `\n//# sourceMappingURL=${mapDataURL()}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue