syndicate-js/packages/core/src/compiler/main.ts

186 lines
7.4 KiB
TypeScript

import fs from 'fs';
import * as S from '../syntax/index.js';
import { Substitution } from '../syntax/index.js';
import * as G from './grammar.js';
import { BootProc } from './internals.js';
export function stripShebang(items: S.Items): S.Items {
if ((items.length > 0) &&
S.isToken(items[0]) &&
items[0].text.startsWith('#!')) {
while (items.length > 0 && !S.isTokenType(items[0], S.TokenType.NEWLINE)) items.shift();
}
return items;
}
export function main(argv: string[]) {
let [ inputFilename ] = argv.slice(2);
inputFilename = inputFilename ?? '/dev/stdin';
const source = fs.readFileSync(inputFilename, 'utf-8');
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}`;
let passNumber = 0;
let expansionNeeded = true;
function expand<T>(p: S.Pattern<T>, f: (t: T) => S.Items) {
tree = S.replace(tree, p, t => {
expansionNeeded = true;
return f(t);
});
}
function receiverFor(s: G.FacetAction): Substitution {
return (s.implicitFacet) ? 'thisFacet.' : '.';
}
function expandFacetAction<T extends G.FacetAction>(p: S.Pattern<T>, f: (t: T) => S.Items) {
expand(p, t => macro.template()`${receiverFor(t)}${f(t)}`);
}
function terminalWrap(isTerminal: boolean, body: G.Statement): G.Statement {
if (isTerminal) {
return macro.template()`thisFacet._stop(function (thisFacet) {${body}})`
} else {
return body;
}
}
while (expansionNeeded) {
if (++passNumber >= 128) {
throw new Error(`Too many compiler passes (${passNumber})!`);
}
// console.log(`\n\n\n======================================== PASS ${passNumber}\n`);
// console.log(S.itemText(tree, { color: true, missing: '\x1b[41m□\x1b[0m' }));
expansionNeeded = false;
expandFacetAction(
G.spawn,
s => {
let proc = macro.template()`function (thisFacet) {${s.bootProcBody}}`;
if (s.isDataspace) proc = macro.template()`__SYNDICATE__.inNestedDataspace(${proc})`;
let assertions = (s.initialAssertions.length > 0)
? macro.template()`, new __SYNDICATE__.Set([${S.commaJoin(s.initialAssertions)}])`
: ``;
return macro.template()`_spawn(${s.name ?? 'null'}, ${proc}${assertions});`;
});
expandFacetAction(
G.fieldDeclarationStatement,
s => {
const prop = ('name' in s.property)
? [ { start: s.property.name.start,
end: s.property.name.end,
type: S.TokenType.STRING,
text: JSON.stringify(s.property.name.text) } ]
: s.property.expr;
return macro.template()`declareField(${s.target}, ${prop}, ${s.init ?? 'void 0'});`;
});
expandFacetAction(
G.assertionEndpointStatement,
s => {
if (s.test == void 0) {
return macro.template()`addEndpoint(thisFacet => ({ assertion: ${s.template}, analysis: null }));`;
} else {
return macro.template()`addEndpoint(thisFacet => (${s.test ?? 'true'})
? ({ assertion: ${s.template}, analysis: null })
: ({ assertion: void 0, analysis: null }), ${''+s.isDynamic});`;
}
});
expandFacetAction(
G.dataflowStatement,
s => macro.template()`addDataflow(function (thisFacet) {${s.body}});`);
expandFacetAction(
G.eventHandlerEndpointStatement,
s => {
switch (s.triggerType) {
case 'dataflow':
return macro.template()`withSelfDo(function (thisFacet) { dataflow { if (${s.predicate}) { ${terminalWrap(s.terminal, s.body)} } } });`;
case 'start':
case 'stop': {
const m = s.triggerType === 'start' ? 'addStartScript' : 'addStopScript';
return macro.template()`${m}(function (thisFacet) {${s.body}});`;
}
case 'asserted':
case 'retracted':
case 'message': {
const sa = G.compilePattern(s.pattern);
const expectedEvt = ({
'asserted': 'ADDED',
'retracted': 'REMOVED',
'message': 'MESSAGE',
})[s.triggerType];
return macro.template()`addEndpoint(thisFacet => ({
assertion: __SYNDICATE__.Observe(${sa.assertion}),
analysis: {
skeleton: ${sa.skeleton},
constPaths: ${JSON.stringify(sa.constPaths)},
constVals: [${S.commaJoin(sa.constVals)}],
capturePaths: ${JSON.stringify(sa.capturePaths)},
callback: thisFacet.wrap((thisFacet, __Evt, [${S.commaJoin(sa.captureIds.map(i=>[i]))}]) => {
if (__Evt === __SYNDICATE__.Skeleton.EventType.${expectedEvt}) {
thisFacet.scheduleScript(() => {${terminalWrap(s.terminal, s.body)}});
}
})
}
}), ${'' + s.isDynamic});`;
}
}
});
expandFacetAction(
G.duringStatement,
s => {
// TODO: spawn during
const sa = G.compilePattern(s.pattern);
return macro.template()`withSelfDo(function (thisFacet) {
const _Facets = new __SYNDICATE__.Dictionary();
on asserted ${G.patternText(s.pattern)} => react {
_Facets.set([${S.commaJoin(sa.captureIds.map(t=>[t]))}], thisFacet);
dataflow void 0; // TODO: horrible hack to keep the facet alive if no other endpoints
${s.body}
}
on retracted ${G.patternText(s.pattern)} => {
const _Key = [${S.commaJoin(sa.captureIds.map(t=>[t]))}];
_Facets.get(_Key)._stop();
_Facets.delete(_Key);
}
});`;
});
expand(
G.typeDefinitionStatement,
s => {
const l = JSON.stringify(s.label.text);
const fs = JSON.stringify(s.fields.map(f => f.text));
return macro.template()`const ${[s.label]} = __SYNDICATE__.Record.makeConstructor(${s.wireName ?? l}, ${fs});`;
});
expandFacetAction(
G.messageSendStatement,
s => macro.template()`_send(${s.expr});`);
expandFacetAction(
G.reactStatement,
s => macro.template()`addChildFacet(function (thisFacet) {${s.body}});`);
expand(
G.bootStatement,
s => macro.template()`export 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));
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));
}