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

324 lines
12 KiB
TypeScript
Raw Normal View History

2021-12-01 16:24:29 +00:00
/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
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,
SourceMap, CodeWriter, TemplateFunction, Token, SpanIndex,
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,
} 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';
export type ErrorSink = (message: string) => 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;
readonly emitError: (message: string) => void;
nextIdNumber = 0;
constructor(moduleType: ModuleType,
typescript: boolean,
emitError: ErrorSink)
{
2021-01-19 23:52:40 +00:00
this.parser = typescript ? new SyndicateTypedParser : new SyndicateParser();
this.moduleType = moduleType;
this.typescript = typescript;
this.emitError = emitError;
}
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
}
}
function stringifyId(i: Identifier): Items {
return [ { ... i, type: TokenType.STRING, text: JSON.stringify(i.text) } ];
}
function binderTypeGuard(ctx: ExpansionContext, 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;
2021-01-19 23:52:40 +00:00
default:
ctx.emitError(`Cannot emit guard for binding of type: ${JSON.stringify(typeText)}`);
return bind; /* act as if "any", for now */
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-02 23:55:42 +00:00
// TODO: untyped template
const sa = compilePattern(s.pattern);
return t`assertDataflow(() => ({
2021-12-02 23:55:42 +00:00
target: currentSyndicateTarget,
assertion: __SYNDICATE__.fromObserve(__SYNDICATE__.Observe({
pattern: ${sa.skeleton},
observer: __SYNDICATE__.Turn.ref(__SYNDICATE__.assertionFacetObserver(
(${ctx.argDecl(t, '__vs', '__SYNDICATE__.AnyValue')}) => {
2021-12-02 23:55:42 +00:00
if (Array.isArray(__vs)) {
${joinItems(sa.captureBinders.map(binderTypeGuard(ctx, t)), '\n')}
2021-12-02 23:55:42 +00:00
${walk(s.body)}
}
}
))
})),
}));`;
2021-01-18 22:11:53 +00:00
});
2021-01-19 23:52:40 +00:00
xf(ctx.parser.spawn, (s, t) => {
// TODO: parentBinders, parentInits
2021-12-02 23:55:42 +00:00
let body = walk(s.body);
/*
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
*/
const n = s.name === void 0 ? '' : t` __SYNDICATE__.Turn.activeFacet.actor.name = ${walk(s.name)};`;
return t`_spawn${s.isLink ? 'Link': ''}(() => {${n} ${body} });`;
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) {
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-02 23:55:42 +00:00
if (s.test == void 0) {
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(ctx, 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`{
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`{
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__.fromObserve(__SYNDICATE__.Observe({
pattern: ${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
};
}