syndicate-js/packages/compiler/src/compiler/codegen.ts

349 lines
13 KiB
TypeScript
Raw Normal View History

2021-12-01 16:24:29 +00:00
/// SPDX-License-Identifier: GPL-3.0-or-later
2023-01-17 10:43:15 +00:00
/// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
2021-12-01 16:24:29 +00:00
2021-01-18 22:11:53 +00:00
import {
2021-01-19 23:52:40 +00:00
isToken, isTokenType, replace, commaJoin, startPos, fixPos, joinItems,
2021-12-02 23:55:42 +00:00
laxRead, itemText,
2021-01-18 22:11:53 +00:00
2021-01-22 23:12:11 +00:00
Items, Pattern, Templates, Substitution, TokenType,
2021-12-11 15:49:12 +00:00
SourceMap, CodeWriter, TemplateFunction, Token, SpanIndex, match, TokenBase, getRange, Pos,
2021-01-18 22:11:53 +00:00
} from '../syntax/index.js';
import {
2021-01-19 23:52:40 +00:00
SyndicateParser, SyndicateTypedParser,
Identifier,
2021-12-02 23:55:42 +00:00
TurnAction,
2021-01-19 23:52:40 +00:00
Statement,
Binder,
2021-01-18 22:11:53 +00:00
compilePattern,
2021-12-11 15:54:47 +00:00
SpawnStatement,
2021-01-18 22:11:53 +00:00
} from './grammar.js';
export function stripShebang(items: Items): Items {
if ((items.length > 0) &&
isToken(items[0]) &&
items[0].text.startsWith('#!')) {
while (items.length > 0 && !isTokenType(items[0], TokenType.NEWLINE)) items.shift();
}
return items;
}
export type ModuleType ='es6' | 'require' | 'global';
2021-12-11 15:49:12 +00:00
export type ErrorSink = (message: string, start: Pos | undefined, end: Pos | undefined) => void;
2021-01-18 22:11:53 +00:00
export interface CompileOptions {
source: string,
name?: string,
runtime?: string,
module?: ModuleType,
global?: string,
typescript?: boolean,
emitError: ErrorSink,
2021-01-18 22:11:53 +00:00
}
export interface CompilerOutput {
text: string,
map: SourceMap,
2021-01-22 23:12:11 +00:00
targetToSourceMap: SpanIndex<Token>;
sourceToTargetMap: SpanIndex<number>;
2021-01-18 22:11:53 +00:00
}
export class ExpansionContext {
2021-01-19 23:52:40 +00:00
readonly parser: SyndicateParser;
readonly moduleType: ModuleType;
readonly typescript: boolean;
2021-12-11 15:49:12 +00:00
readonly errorEmitter: ErrorSink;
nextIdNumber = 0;
constructor(moduleType: ModuleType,
typescript: boolean,
2021-12-11 15:49:12 +00:00
errorEmitter: ErrorSink)
{
2021-01-19 23:52:40 +00:00
this.parser = typescript ? new SyndicateTypedParser : new SyndicateParser();
this.moduleType = moduleType;
this.typescript = typescript;
2021-12-11 15:49:12 +00:00
this.errorEmitter = errorEmitter;
}
quasiRandomId(): string {
return '__SYNDICATE__id_' + (this.nextIdNumber++);
}
2021-12-02 23:55:42 +00:00
argDecl(t: TemplateFunction, name: Substitution, type: Substitution): Items {
2021-12-03 00:00:47 +00:00
return (this.typescript) ? t`${name}: ${type}` : t`${name}`;
2021-01-19 23:52:40 +00:00
}
2021-12-11 15:49:12 +00:00
emitError(m: string, loc: TokenBase) {
this.errorEmitter(m, loc.start, loc.end);
}
2021-01-19 23:52:40 +00:00
}
function stringifyId(i: Identifier): Items {
return [ { ... i, type: TokenType.STRING, text: JSON.stringify(i.text) } ];
}
function binderTypeGuard(t: TemplateFunction): (binder: Binder, index: number) => Items {
return (binder, index) => {
if (binder.id.text[0] === '_') {
return t`${`/* Ignoring underscore-prefixed binder ${binder.id.text} */`}`;
}
const raw = t`__vs[${''+index}]`;
const bind = t`const ${[binder.id]} = ${raw};`;
2021-01-19 23:52:40 +00:00
if (binder.type === void 0) {
return bind;
2021-01-19 23:52:40 +00:00
} else {
const typeText = itemText(binder.type);
switch (typeText) {
case 'boolean':
case 'string':
case 'number':
case 'symbol':
return t`if (typeof (${raw}) !== ${JSON.stringify(typeText)}) return;\n${bind}`;
case 'any':
return bind;
default: {
const intermediate = t`__v_${''+index}`;
return t`const ${intermediate} = ${binder.type}.__from_preserve__(${raw});
if (${intermediate} === void 0) return;
const ${[binder.id]} = ${intermediate};`;
}
2021-01-19 23:52:40 +00:00
}
}
};
2021-01-19 14:13:42 +00:00
}
export function expand(tree: Items, ctx: ExpansionContext): Items {
const macro = new Templates(undefined, { extraDelimiters: ':' });
2021-01-18 22:11:53 +00:00
function terminalWrap(t: TemplateFunction, isTerminal: boolean, body: Statement): Statement {
if (isTerminal) {
return t`__SYNDICATE__.Turn.active._stop(__SYNDICATE__.Turn.activeFacet, () => {${body}})`
2021-01-18 22:11:53 +00:00
} else {
return body;
}
}
function x<T>(p: Pattern<T>, f: (v: T, t: TemplateFunction) => Items) {
tree = replace(tree, p, (v, start) => f(v, macro.template(fixPos(start))));
}
2021-12-02 23:55:42 +00:00
function xf<T extends TurnAction>(p: Pattern<T>, f: (v: T, t: TemplateFunction) => Items) {
x(p, (v, t) => t`__SYNDICATE__.Turn.active.${f(v, t)}`);
2021-01-18 22:11:53 +00:00
}
2021-01-19 14:13:42 +00:00
const walk = (tree: Items): Items => expand(tree, ctx);
2021-01-18 22:11:53 +00:00
const maybeWalk = (tree?: Items) : Items | undefined => (tree === void 0) ? tree : walk(tree);
2021-12-03 14:29:20 +00:00
// Unfortunately, because of the incredibly naive repeated
// traversal of the syntax tree we're doing, the order of the
// following transformations matters.
2021-01-19 23:52:40 +00:00
xf(ctx.parser.duringStatement, (s, t) => {
2021-12-11 15:54:47 +00:00
let spawn = match(ctx.parser.spawn, s.body, null);
if (spawn !== null) {
if (spawn.linkedToken !== null) {
ctx.emitError(`during ... spawn doesn't need "linked", it's always linked`,
spawn.linkedToken);
}
spawn.linkedToken = getRange(s.body);
}
2021-12-13 11:21:11 +00:00
let body = (spawn === null)
? walk(s.body)
: expandSpawn(spawn, t, t`__SYNDICATE__.Turn.activeFacet.preventInertCheck();`);
2021-12-11 15:54:47 +00:00
2021-12-02 23:55:42 +00:00
const sa = compilePattern(s.pattern);
const assertion = t`__SYNDICATE__.Observe({
2021-12-13 11:21:11 +00:00
pattern: __SYNDICATE__.QuasiValue.finish(${sa.skeleton}),
observer: __SYNDICATE__.Turn.ref(__SYNDICATE__.assertionFacetObserver(
(${ctx.argDecl(t, '__vs', '__SYNDICATE__.AnyValue')}) => {
if (Array.isArray(__vs)) {
${joinItems(sa.captureBinders.map(binderTypeGuard(t)), '\n')}
2021-12-13 11:21:11 +00:00
${body}
2021-12-02 23:55:42 +00:00
}
2021-12-13 11:21:11 +00:00
}
))
})`;
2021-12-13 11:21:11 +00:00
if (s.test === void 0) {
return t`assertDataflow(() => ({ target: currentSyndicateTarget, assertion: ${assertion} }));`;
} else {
return t`assertDataflow(() => (${walk(s.test)})
? ({ target: currentSyndicateTarget, assertion: ${assertion} })
: ({ target: void 0, assertion: void 0 }));`;
}
2021-01-18 22:11:53 +00:00
});
function expandSpawn(spawn: SpawnStatement, t: TemplateFunction, inject: Items = []): Items {
// TODO: parentBinders, parentInits
2021-12-02 23:55:42 +00:00
/*
2021-01-18 22:11:53 +00:00
let assertions = (s.initialAssertions.length > 0)
? t`, new __SYNDICATE__.Set([${commaJoin(s.initialAssertions.map(walk))}])`
: ``;
2021-12-02 23:55:42 +00:00
*/
2021-12-11 15:54:47 +00:00
const n = spawn.name === void 0 ? '' : t` __SYNDICATE__.Turn.activeFacet.actor.name = ${walk(spawn.name)};`;
return t`__SYNDICATE__.Turn.active._spawn${spawn.linkedToken ? 'Link': ''}(() => {${n} ${inject} ${walk(spawn.body)} });`;
2021-12-11 15:54:47 +00:00
}
2021-12-11 16:06:50 +00:00
x(ctx.parser.spawn, expandSpawn);
2021-12-02 23:55:42 +00:00
x(ctx.parser.fieldDeclarationStatement, (s, t) => {
const ft = ctx.typescript ? t`<${s.field.type ?? '__SYNDICATE__.AnyValue'}>` : '';
return t`const ${[s.field.id]} = __SYNDICATE__.Turn.active.field${ft}(${maybeWalk(s.init) ?? 'void 0'}, ${stringifyId(s.field.id)});`;
2021-01-18 22:11:53 +00:00
});
2021-12-02 23:55:42 +00:00
x(ctx.parser.atStatement, (s, t) => {
return t`(((${ctx.argDecl(t, 'currentSyndicateTarget', '__SYNDICATE__.Ref')}) => {${walk(s.body)}})(${walk(s.target)}));`;
});
x(ctx.parser.createExpression, (s, t) => {
return t`__SYNDICATE__.Turn.ref(${walk(s.entity)})`;
2021-01-18 22:11:53 +00:00
});
2021-01-19 23:52:40 +00:00
xf(ctx.parser.assertionEndpointStatement, (s, t) => {
2021-12-02 23:55:42 +00:00
if (s.isDynamic) {
2021-12-13 11:21:11 +00:00
if (s.test === void 0) {
return t`assertDataflow(() => ({ target: currentSyndicateTarget, assertion: ${walk(s.template)} }));`;
2021-12-02 23:55:42 +00:00
} else {
return t`assertDataflow(() => (${walk(s.test)})
2021-12-02 23:55:42 +00:00
? ({ target: currentSyndicateTarget, assertion: ${walk(s.template)} })
: ({ target: void 0, assertion: void 0 }));`;
}
2021-01-18 22:11:53 +00:00
} else {
2021-12-13 11:21:11 +00:00
if (s.test === void 0) {
2021-12-02 23:55:42 +00:00
return t`assert(currentSyndicateTarget, ${walk(s.template)});`;
} else {
return t`replace(currentSyndicateTarget, void 0, (${walk(s.test)}) ? (${walk(s.template)}) : void 0);`;
}
2021-01-18 22:11:53 +00:00
}
});
2021-01-19 23:52:40 +00:00
xf(ctx.parser.dataflowStatement, (s, t) =>
t`_dataflow(() => {${walk(s.body)}});`);
2021-01-18 22:11:53 +00:00
x(ctx.parser.eventHandlerEndpointStatement, (s, t) => {
2021-12-02 23:55:42 +00:00
if (s.triggerType === 'dataflow') {
return t`__SYNDICATE__.Turn.active._dataflow(() => { if (${walk(s.predicate)}) { ${terminalWrap(t, s.terminal, walk(s.body))} } });`;
2021-12-02 23:55:42 +00:00
}
2021-01-18 22:11:53 +00:00
2021-12-02 23:55:42 +00:00
if (s.triggerType === 'stop') {
return t`__SYNDICATE__.Turn.activeFacet.onStop(() => {${walk(s.body)}});`;
2021-12-02 23:55:42 +00:00
}
2021-01-18 22:11:53 +00:00
2021-12-02 23:55:42 +00:00
const sa = compilePattern(s.pattern);
const guardBody = (body: Statement) => t`if (Array.isArray(__vs)) {
${joinItems(sa.captureBinders.map(binderTypeGuard(t)), '\n')}
2021-12-02 23:55:42 +00:00
${body}
}`;
let entity: Items;
switch (s.triggerType) {
2021-01-18 22:11:53 +00:00
case 'asserted':
2021-12-02 23:55:42 +00:00
entity = t`{
2022-01-24 08:09:10 +00:00
assert: (${ctx.argDecl(t, '__vs', '__SYNDICATE__.AnyValue')}, ${ctx.argDecl(t, '__handle', '__SYNDICATE__.Handle')}) => {
2021-12-02 23:55:42 +00:00
${guardBody(terminalWrap(t, s.terminal, walk(s.body)))}
}
}`;
break;
2021-01-18 22:11:53 +00:00
case 'retracted':
entity = t`__SYNDICATE__.assertionObserver((${ctx.argDecl(t, '__vs', '__SYNDICATE__.AnyValue')}) => {
${guardBody(t`return () => { ${terminalWrap(t, s.terminal, walk(s.body))} };`)}
2021-12-02 23:55:42 +00:00
})`;
break;
case 'message':
entity = t`{
2022-01-24 08:09:10 +00:00
message: (${ctx.argDecl(t, '__vs', '__SYNDICATE__.AnyValue')}) => {
2021-12-02 23:55:42 +00:00
${guardBody(terminalWrap(t, s.terminal, walk(s.body)))}
}
}`;
break;
}
const assertion = t`__SYNDICATE__.Observe({
2021-12-09 17:55:18 +00:00
pattern: __SYNDICATE__.QuasiValue.finish(${sa.skeleton}),
observer: __SYNDICATE__.Turn.ref(${entity}),
})`;
2021-12-02 23:55:42 +00:00
if (s.isDynamic) {
return t`__SYNDICATE__.Turn.active.assertDataflow(() => ({
2021-12-02 23:55:42 +00:00
target: currentSyndicateTarget,
assertion: ${assertion},
}));`;
} else {
return t`__SYNDICATE__.Turn.active.replace(currentSyndicateTarget, void 0, ${assertion});`;
2021-01-18 22:11:53 +00:00
}
});
2021-01-19 23:52:40 +00:00
x(ctx.parser.typeDefinitionStatement, (s, t) => {
2021-03-03 09:28:10 +00:00
const l = `Symbol.for(${JSON.stringify(s.label.text)})`;
const fns = JSON.stringify(s.fields.map(f => f.id.text));
2021-12-02 23:55:42 +00:00
const formatBinder = (b: Binder) => t`${[b.id]}: ${b.type ?? '__SYNDICATE__.AnyValue'}`;
2021-03-03 09:28:10 +00:00
const fs = ctx.typescript
2021-12-02 23:55:42 +00:00
? t`<{${commaJoin(s.fields.map(formatBinder))}}, __SYNDICATE__.Ref>`
2021-03-03 09:28:10 +00:00
: '';
return t`const ${[s.label]} = __SYNDICATE__.Record.makeConstructor${fs}()(${maybeWalk(s.wireName) ?? l}, ${fns});`;
2021-01-18 22:11:53 +00:00
});
2021-12-02 23:55:42 +00:00
xf(ctx.parser.messageSendStatement, (s, t) => t`message(currentSyndicateTarget, ${walk(s.expr)});`);
2021-01-18 22:11:53 +00:00
2021-01-19 23:52:40 +00:00
xf(ctx.parser.reactStatement, (s, t) => {
return t`facet(() => {${s.body}});`;
2021-01-18 22:11:53 +00:00
});
x(ctx.parser.stopStatement, (s, t) =>
t`__SYNDICATE__.Turn.active._stop(__SYNDICATE__.Turn.activeFacet, () => {${walk(s.body)}});`)
2021-01-18 22:11:53 +00:00
return tree;
}
export function compile(options: CompileOptions): CompilerOutput {
const inputFilename = options.name ?? '/dev/stdin';
2021-01-19 23:52:40 +00:00
// console.info(`Syndicate: compiling ${inputFilename}`);
2021-01-19 23:52:40 +00:00
2021-01-18 22:11:53 +00:00
const source = options.source;
const moduleType = options.module ?? 'es6';
const typescript = options.typescript ?? false;
2021-01-18 22:11:53 +00:00
const start = startPos(inputFilename);
2021-01-19 23:52:40 +00:00
let tree = stripShebang(laxRead(source, { start, extraDelimiters: ':' }));
// const end = tree.length > 0 ? tree[tree.length - 1].end : start;
2021-01-19 14:13:42 +00:00
const macro = new Templates(undefined, { extraDelimiters: ':' });
2021-01-18 22:11:53 +00:00
const ctx = new ExpansionContext(moduleType, typescript, options.emitError);
2021-01-19 14:13:42 +00:00
tree = expand(tree, ctx);
const ts = macro.template(fixPos(start));
// const te = macro.template(fixPos(end));
2021-01-19 14:13:42 +00:00
2021-01-18 22:11:53 +00:00
{
const runtime = options.runtime ?? '@syndicate-lang/core';
switch (moduleType) {
case 'es6':
2021-01-19 14:13:42 +00:00
tree = ts`import * as __SYNDICATE__ from ${JSON.stringify(runtime)};\n${tree}`;
2021-01-18 22:11:53 +00:00
break;
case 'require':
2021-01-19 14:13:42 +00:00
tree = ts`const __SYNDICATE__ = require(${JSON.stringify(runtime)});\n${tree}`;
2021-01-18 22:11:53 +00:00
break;
case 'global':
2021-01-19 14:13:42 +00:00
tree = ts`const __SYNDICATE__ = ${runtime};\n${tree}`;
2021-01-18 22:11:53 +00:00
break;
}
}
const cw = new CodeWriter(inputFilename);
2021-01-19 14:13:42 +00:00
cw.emit(tree);
2021-01-18 22:11:53 +00:00
2021-01-22 23:12:11 +00:00
const text = cw.text;
2021-01-18 22:11:53 +00:00
return {
2021-01-22 23:12:11 +00:00
text,
2021-01-18 22:11:53 +00:00
map: cw.map,
2021-01-22 23:12:11 +00:00
targetToSourceMap: cw.targetToSourceMap.index(),
sourceToTargetMap: cw.sourceToTargetMap.index(),
2021-01-18 22:11:53 +00:00
};
}