Update compiler
This commit is contained in:
parent
f9d1e694e0
commit
dd14c8471d
2
Makefile
2
Makefile
|
@ -1,3 +1,5 @@
|
|||
__ignored__ := $(shell ./setup.sh)
|
||||
|
||||
LERNA=./node_modules/.bin/lerna
|
||||
|
||||
bootstrap: node_modules/lerna
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import {
|
||||
isToken, isTokenType, replace, commaJoin, startPos, fixPos, joinItems,
|
||||
laxRead, itemText, match,
|
||||
laxRead, itemText,
|
||||
|
||||
Items, Pattern, Templates, Substitution, TokenType,
|
||||
SourceMap, CodeWriter, TemplateFunction, Token, SpanIndex,
|
||||
|
@ -11,19 +11,12 @@ import {
|
|||
import {
|
||||
SyndicateParser, SyndicateTypedParser,
|
||||
Identifier,
|
||||
FacetAction,
|
||||
TurnAction,
|
||||
Statement,
|
||||
ActivationImport,
|
||||
FacetFields,
|
||||
Binder,
|
||||
|
||||
compilePattern,
|
||||
patternText,
|
||||
instantiatePatternToPattern,
|
||||
} from './grammar.js';
|
||||
import {
|
||||
BootProc,
|
||||
} from './internals.js';
|
||||
|
||||
export function stripShebang(items: Items): Items {
|
||||
if ((items.length > 0) &&
|
||||
|
@ -52,22 +45,15 @@ export interface CompilerOutput {
|
|||
sourceToTargetMap: SpanIndex<number>;
|
||||
}
|
||||
|
||||
function receiverFor(s: FacetAction): Substitution {
|
||||
return (s.implicitFacet) ? 'thisFacet.' : '.';
|
||||
}
|
||||
|
||||
export interface ActivationRecord {
|
||||
activation: ActivationImport;
|
||||
activationScriptId: Identifier;
|
||||
function receiverFor(s: TurnAction): Substitution {
|
||||
return (s.implicitTurn) ? 'thisTurn.' : '.';
|
||||
}
|
||||
|
||||
export class ExpansionContext {
|
||||
readonly parser: SyndicateParser;
|
||||
readonly moduleType: ModuleType;
|
||||
readonly activationRecords: Array<ActivationRecord> = [];
|
||||
hasBootProc: boolean = false;
|
||||
hasBootableBootProc: boolean = false;
|
||||
readonly typescript: boolean;
|
||||
_collectedFields: FacetFields | null = null;
|
||||
nextIdNumber = 0;
|
||||
|
||||
constructor(moduleType: ModuleType,
|
||||
|
@ -82,28 +68,12 @@ export class ExpansionContext {
|
|||
return '__SYNDICATE__id_' + (this.nextIdNumber++);
|
||||
}
|
||||
|
||||
get collectedFields(): FacetFields {
|
||||
// Allocates a transient array for collected fields in
|
||||
// contexts lacking a surrounding collector - that is, for errors.
|
||||
return this._collectedFields ?? [];
|
||||
argDecl(t: TemplateFunction, name: Substitution, type: Substitution): Items {
|
||||
return (this.typescript) ? t`${name}: ${type}` : t`name`;
|
||||
}
|
||||
|
||||
collectField(f: Binder) {
|
||||
this.collectedFields.push(f);
|
||||
}
|
||||
|
||||
withCollectedFields<T>(fs: FacetFields, f: () => T): T {
|
||||
const oldCollectedFields = this._collectedFields;
|
||||
try {
|
||||
this._collectedFields = fs;
|
||||
return f();
|
||||
} finally {
|
||||
this._collectedFields = oldCollectedFields;
|
||||
}
|
||||
}
|
||||
|
||||
argDecl(t: TemplateFunction, name: Substitution, type: Substitution): Substitution {
|
||||
return (this.typescript) ? t`${name}: ${type}` : name;
|
||||
turnDecl(t: TemplateFunction): Items {
|
||||
return this.argDecl(t, 'thisTurn', '__SYNDICATE__.Turn');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,18 +81,6 @@ function stringifyId(i: Identifier): Items {
|
|||
return [ { ... i, type: TokenType.STRING, text: JSON.stringify(i.text) } ];
|
||||
}
|
||||
|
||||
function facetFieldObjectType(
|
||||
t: TemplateFunction,
|
||||
fs: FacetFields,
|
||||
defaultType?: Substitution): Substitution
|
||||
{
|
||||
function formatBinder(binder: Binder) {
|
||||
const hasType = ((binder.type ?? defaultType) !== void 0);
|
||||
return t`${[binder.id]}${hasType ? ': ': ''}${binder.type ?? defaultType ?? ''}`;
|
||||
}
|
||||
return t`{${commaJoin(fs.map(formatBinder))}}`;
|
||||
}
|
||||
|
||||
function binderTypeGuard(t: TemplateFunction): (binder: Binder, index: number) => Items {
|
||||
return (binder, index) => {
|
||||
if (binder.id.text[0] === '_') {
|
||||
|
@ -156,7 +114,7 @@ export function expand(tree: Items, ctx: ExpansionContext): Items {
|
|||
|
||||
function terminalWrap(t: TemplateFunction, isTerminal: boolean, body: Statement): Statement {
|
||||
if (isTerminal) {
|
||||
return t`thisFacet._stop(function (thisFacet) {${body}})`
|
||||
return t`thisTurn._stop(thisTurn.activeFacet, (${ctx.turnDecl(t)}) => {${body}})`
|
||||
} else {
|
||||
return body;
|
||||
}
|
||||
|
@ -166,7 +124,7 @@ export function expand(tree: Items, ctx: ExpansionContext): Items {
|
|||
tree = replace(tree, p, (v, start) => f(v, macro.template(fixPos(start))));
|
||||
}
|
||||
|
||||
function xf<T extends FacetAction>(p: Pattern<T>, f: (v: T, t: TemplateFunction) => Items) {
|
||||
function xf<T extends TurnAction>(p: Pattern<T>, f: (v: T, t: TemplateFunction) => Items) {
|
||||
x(p, (v, t) => t`${receiverFor(v)}${f(v, t)}`);
|
||||
}
|
||||
|
||||
|
@ -174,175 +132,156 @@ export function expand(tree: Items, ctx: ExpansionContext): Items {
|
|||
const maybeWalk = (tree?: Items) : Items | undefined => (tree === void 0) ? tree : walk(tree);
|
||||
|
||||
xf(ctx.parser.duringStatement, (s, t) => {
|
||||
let spawn0 = match(ctx.parser.spawn, s.body, null);
|
||||
if (spawn0 !== null) {
|
||||
const spawn = spawn0;
|
||||
const id = ctx.quasiRandomId();
|
||||
const instantiated = patternText(instantiatePatternToPattern(s.pattern));
|
||||
return t`on asserted ${patternText(s.pattern)} => {
|
||||
const ${id} = __SYNDICATE__.genUuid();
|
||||
const ${id}_inst = __SYNDICATE__.Instance(${id});
|
||||
react {
|
||||
stop on asserted ${id}_inst => react {
|
||||
stop on retracted ${id}_inst;
|
||||
stop on retracted :snapshot ${instantiated};
|
||||
}
|
||||
stop on retracted :snapshot ${instantiated} => react {
|
||||
stop on asserted ${id}_inst;
|
||||
}
|
||||
}
|
||||
spawn
|
||||
${spawn.isDataspace ? 'dataspace' : []}
|
||||
${spawn.name === void 0 ? [] : t`named ${spawn.name}`}
|
||||
:asserting ${id}_inst
|
||||
${joinItems(spawn.initialAssertions.map(e => t`:asserting ${e}`), ' ')}
|
||||
${joinItems(spawn.parentBinders.map((b, i) => {
|
||||
const init = spawn.parentInits[i];
|
||||
return t`:let ${[b.id]}${b.type === void 0 ? [] : t`: ${b.type}`} = ${init}`;
|
||||
}), ' ')}
|
||||
{
|
||||
assert ${id}_inst;
|
||||
stop on retracted __SYNDICATE__.Observe(${id}_inst);
|
||||
${spawn.body}
|
||||
}
|
||||
}`;
|
||||
} else {
|
||||
// TODO: untyped template
|
||||
const sa = compilePattern(s.pattern);
|
||||
return t`withSelfDo(function (thisFacet) {
|
||||
const _Facets = new __SYNDICATE__.Dictionary<any, __SYNDICATE__.Facet<any>>();
|
||||
on asserted ${patternText(s.pattern)} => react {
|
||||
_Facets.set([${commaJoin(sa.captureBinders.map(t=>[t.id]))}], thisFacet);
|
||||
dataflow { } // TODO: horrible hack to keep the facet alive if no other endpoints
|
||||
${s.body}
|
||||
}
|
||||
on retracted ${patternText(s.pattern)} => {
|
||||
const ${ctx.argDecl(t, '_Key', '__SYNDICATE__.Value<any>[]')} =
|
||||
[${commaJoin(sa.captureBinders.map(t=>[t.id]))}];
|
||||
_Facets.get(_Key)?._stop();
|
||||
_Facets.delete(_Key);
|
||||
}
|
||||
});`;
|
||||
}
|
||||
// TODO: untyped template
|
||||
const sa = compilePattern(s.pattern);
|
||||
return t`assertDataflow((${ctx.turnDecl(t)}) => ({
|
||||
target: currentSyndicateTarget,
|
||||
assertion: __SYNDICATE__.fromObserve(__SYNDICATE__.Observe({
|
||||
pattern: ${sa.skeleton},
|
||||
observer: thisTurn.ref(__SYNDICATE__.assertionFacetObserver(
|
||||
(${ctx.turnDecl(t)}, ${ctx.argDecl(t, '__vs', '__SYNDICATE__.AnyValue')}) => {
|
||||
if (Array.isArray(__vs)) {
|
||||
${joinItems(sa.captureBinders.map(binderTypeGuard(t)), '\n')}
|
||||
${walk(s.body)}
|
||||
}
|
||||
}
|
||||
))
|
||||
})),
|
||||
}));`;
|
||||
});
|
||||
|
||||
xf(ctx.parser.spawn, (s, t) => {
|
||||
// TODO: parentBinders, parentInits
|
||||
let body = ctx.withCollectedFields(s.facetFields, () => walk(s.body));
|
||||
let proc = t`function (thisFacet) {${body}}`;
|
||||
if (s.isDataspace) proc = t`__SYNDICATE__.inNestedDataspace(${proc})`;
|
||||
let body = walk(s.body);
|
||||
/*
|
||||
let assertions = (s.initialAssertions.length > 0)
|
||||
? t`, new __SYNDICATE__.Set([${commaJoin(s.initialAssertions.map(walk))}])`
|
||||
: ``;
|
||||
let fieldTypeParam = ctx.typescript ? t`<${facetFieldObjectType(t, s.facetFields)}>` : '';
|
||||
return t`_spawn${fieldTypeParam}(${maybeWalk(s.name) ?? 'null'}, ${proc}${assertions});`;
|
||||
*/
|
||||
const n = s.name === void 0 ? '' : t` thisTurn.activeFacet.actor.name = ${walk(s.name)};`;
|
||||
return t`_spawn${s.isLink ? 'Link': ''}((${ctx.turnDecl(t)}) => {${n} ${body} });`;
|
||||
});
|
||||
|
||||
xf(ctx.parser.fieldDeclarationStatement, (s, t) => {
|
||||
ctx.collectField(s.property);
|
||||
return t`declareField(this, ${stringifyId(s.property.id)}, ${maybeWalk(s.init) ?? 'void 0'});`;
|
||||
x(ctx.parser.fieldDeclarationStatement, (s, t) => {
|
||||
const ft = ctx.typescript ? t`<${s.field.type ?? '__SYNDICATE__.AnyValue'}>` : '';
|
||||
return t`const ${[s.field.id]} = thisTurn.field${ft}(${maybeWalk(s.init) ?? 'void 0'}, ${stringifyId(s.field.id)});`;
|
||||
});
|
||||
|
||||
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`thisTurn.ref(${walk(s.entity)})`;
|
||||
});
|
||||
|
||||
xf(ctx.parser.assertionEndpointStatement, (s, t) => {
|
||||
if (s.test == void 0) {
|
||||
return t`addEndpoint(thisFacet => ({ assertion: ${walk(s.template)}, analysis: null }));`;
|
||||
if (s.isDynamic) {
|
||||
if (s.test == void 0) {
|
||||
return t`assertDataflow((${ctx.turnDecl(t)}) => ({ target: currentSyndicateTarget, assertion: ${walk(s.template)} }));`;
|
||||
} else {
|
||||
return t`assertDataflow((${ctx.turnDecl(t)}) => (${walk(s.test)})
|
||||
? ({ target: currentSyndicateTarget, assertion: ${walk(s.template)} })
|
||||
: ({ target: void 0, assertion: void 0 }));`;
|
||||
}
|
||||
} else {
|
||||
return t`addEndpoint(thisFacet => (${walk(s.test)})
|
||||
? ({ assertion: ${walk(s.template)}, analysis: null })
|
||||
: ({ assertion: void 0, analysis: null }), ${''+s.isDynamic});`;
|
||||
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);`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
xf(ctx.parser.dataflowStatement, (s, t) =>
|
||||
t`addDataflow(function (thisFacet) {${walk(s.body)}});`);
|
||||
t`_dataflow((${ctx.turnDecl(t)}) => {${walk(s.body)}});`);
|
||||
|
||||
xf(ctx.parser.eventHandlerEndpointStatement, (s, t) => {
|
||||
switch (s.triggerType) {
|
||||
case 'dataflow':
|
||||
return t`withSelfDo(function (thisFacet) { dataflow { if (${walk(s.predicate)}) { ${terminalWrap(t, s.terminal, walk(s.body))} } } });`;
|
||||
if (s.triggerType === 'dataflow') {
|
||||
return t`withSelfDo((${ctx.turnDecl(t)}) => { dataflow { if (${walk(s.predicate)}) { ${terminalWrap(t, s.terminal, walk(s.body))} } } });`;
|
||||
}
|
||||
|
||||
case 'start':
|
||||
case 'stop': {
|
||||
const m = s.triggerType === 'start' ? 'addStartScript' : 'addStopScript';
|
||||
return t`${m}(function (thisFacet) {${walk(s.body)}});`;
|
||||
}
|
||||
if (s.triggerType === 'stop') {
|
||||
return t`activeFacet.onStop((${ctx.turnDecl(t)}) => {${walk(s.body)}});`;
|
||||
}
|
||||
|
||||
case 'asserted':
|
||||
case 'retracted':
|
||||
case 'message': {
|
||||
const sa = compilePattern(s.pattern);
|
||||
const expectedEvt = ({
|
||||
'asserted': 'ADDED',
|
||||
'retracted': 'REMOVED',
|
||||
'message': 'MESSAGE',
|
||||
})[s.triggerType];
|
||||
return t`addEndpoint(thisFacet => ({
|
||||
assertion: __SYNDICATE__.Observe(${walk(sa.assertion)}),
|
||||
analysis: {
|
||||
skeleton: ${walk(sa.skeleton)},
|
||||
constPaths: ${JSON.stringify(sa.constPaths)},
|
||||
constVals: [${commaJoin(sa.constVals.map(walk))}],
|
||||
capturePaths: ${JSON.stringify(sa.capturePaths)},
|
||||
callback: thisFacet.wrap((thisFacet, __Evt, ${ctx.argDecl(t, '__vs', 'Array<__SYNDICATE__.Value<any>>')}) => {
|
||||
if (__Evt === __SYNDICATE__.Skeleton.EventType.${expectedEvt}) {
|
||||
const sa = compilePattern(s.pattern);
|
||||
const guardBody = (body: Statement) => t`if (Array.isArray(__vs)) {
|
||||
${joinItems(sa.captureBinders.map(binderTypeGuard(t)), '\n')}
|
||||
thisFacet.scheduleScript(() => {${terminalWrap(t, s.terminal, walk(s.body))}});
|
||||
}
|
||||
})
|
||||
}
|
||||
}), ${'' + s.isDynamic});`;
|
||||
}
|
||||
${body}
|
||||
}`;
|
||||
|
||||
let entity: Items;
|
||||
switch (s.triggerType) {
|
||||
case 'asserted':
|
||||
entity = t`{
|
||||
assert(${ctx.turnDecl(t)}, ${ctx.argDecl(t, '__vs', '__SYNDICATE__.AnyValue')}, __handle: __SYNDICATE__.Handle) {
|
||||
${guardBody(terminalWrap(t, s.terminal, walk(s.body)))}
|
||||
}
|
||||
}`;
|
||||
break;
|
||||
case 'retracted':
|
||||
entity = t`__SYNDICATE__.assertionObserver((${ctx.turnDecl(t)}, ${ctx.argDecl(t, '__vs', '__SYNDICATE__.AnyValue')}) => {
|
||||
${guardBody(t`return (${ctx.turnDecl(t)}) => { ${terminalWrap(t, s.terminal, walk(s.body))} };`)}
|
||||
})`;
|
||||
break;
|
||||
case 'message':
|
||||
entity = t`{
|
||||
message(${ctx.turnDecl(t)}, ${ctx.argDecl(t, '__vs', '__SYNDICATE__.AnyValue')}) {
|
||||
${guardBody(terminalWrap(t, s.terminal, walk(s.body)))}
|
||||
}
|
||||
}`;
|
||||
break;
|
||||
}
|
||||
|
||||
const assertion = t`__SYNDICATE__.fromObserve(__SYNDICATE__.Observe({
|
||||
pattern: ${sa.skeleton},
|
||||
observer: thisTurn.ref(${entity}),
|
||||
}))`;
|
||||
|
||||
if (s.isDynamic) {
|
||||
return t`assertDataflow((${ctx.turnDecl(t)}) => ({
|
||||
target: currentSyndicateTarget,
|
||||
assertion: ${assertion},
|
||||
}));`;
|
||||
} else {
|
||||
return t`replace(currentSyndicateTarget, void 0, ${assertion});`;
|
||||
}
|
||||
});
|
||||
|
||||
x(ctx.parser.typeDefinitionStatement, (s, t) => {
|
||||
const l = `Symbol.for(${JSON.stringify(s.label.text)})`;
|
||||
const fns = JSON.stringify(s.fields.map(f => f.id.text));
|
||||
const formatBinder = (b: Binder) => t`${[b.id]}: ${b.type ?? '__SYNDICATE__.AnyValue'}`;
|
||||
const fs = ctx.typescript
|
||||
? t`<${facetFieldObjectType(t, s.fields, t`__SYNDICATE__.Value<any>`)}, any>`
|
||||
? t`<{${commaJoin(s.fields.map(formatBinder))}}, __SYNDICATE__.Ref>`
|
||||
: '';
|
||||
return t`const ${[s.label]} = __SYNDICATE__.Record.makeConstructor${fs}()(${maybeWalk(s.wireName) ?? l}, ${fns});`;
|
||||
});
|
||||
|
||||
xf(ctx.parser.messageSendStatement, (s, t) => t`_send(${walk(s.expr)});`);
|
||||
xf(ctx.parser.messageSendStatement, (s, t) => t`message(currentSyndicateTarget, ${walk(s.expr)});`);
|
||||
|
||||
xf(ctx.parser.reactStatement, (s, t) => {
|
||||
const body = ctx.withCollectedFields(s.facetFields, () => walk(s.body));
|
||||
const fieldTypeParam = ctx.typescript
|
||||
? t`<${facetFieldObjectType(t, s.facetFields)}>`
|
||||
: '';
|
||||
return t`addChildFacet${fieldTypeParam}(function (thisFacet) {${body}});`;
|
||||
return t`facet((${ctx.turnDecl(t)}) => {${s.body}});`;
|
||||
});
|
||||
|
||||
x(ctx.parser.activationImport, (s) => {
|
||||
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(ctx.parser.bootStatement, (s, t) => {
|
||||
ctx.hasBootProc = true;
|
||||
const activationStatements = ctx.activationRecords.map(({ activationScriptId: id }) =>
|
||||
t`thisFacet.activate(${[id]}); `);
|
||||
const body = t`${joinItems(activationStatements)}${walk(s)}`;
|
||||
const facetDecl = ctx.typescript ? 'thisFacet: __SYNDICATE__.Facet<{}>' : 'thisFacet';
|
||||
ctx.hasBootableBootProc = s.formals.length == 0;
|
||||
const body = t`{${walk(s.body)}}`;
|
||||
const bootFormals = commaJoin([ctx.turnDecl(t), ... s.formals.map(
|
||||
b => ctx.argDecl(t, [b.id], b.type ?? '__SYNDICATE__.AnyValue'))]);
|
||||
switch (ctx.moduleType) {
|
||||
case 'es6':
|
||||
return t`export function ${BootProc}(${facetDecl}) {${body}}`;
|
||||
return t`export function boot(${bootFormals}) ${body}`;
|
||||
case 'require':
|
||||
return t`module.exports.${BootProc} = function (${facetDecl}) {${body}};`;
|
||||
return t`module.exports.boot = (${bootFormals}) => ${body};`;
|
||||
case 'global':
|
||||
return t`function ${BootProc}(${facetDecl}) {${body}}`;
|
||||
return t`function boot(${bootFormals}) ${body}`;
|
||||
}
|
||||
});
|
||||
|
||||
xf(ctx.parser.stopStatement, (s, t) =>
|
||||
t`_stop(function (thisFacet) {${walk(s.body)}});`)
|
||||
t`withSelfDo((${ctx.turnDecl(t)}) => thisTurn._stop(thisTurn.activeFacet, (${ctx.turnDecl(t)}) => {${walk(s.body)}});`)
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
@ -369,32 +308,20 @@ export function compile(options: CompileOptions): CompilerOutput {
|
|||
const ts = macro.template(fixPos(start));
|
||||
const te = macro.template(fixPos(end));
|
||||
|
||||
if (ctx.hasBootProc) {
|
||||
if (ctx.hasBootableBootProc) {
|
||||
let bp;
|
||||
switch (moduleType) {
|
||||
case 'es6':
|
||||
case 'global':
|
||||
bp = BootProc;
|
||||
bp = te`boot`;
|
||||
break;
|
||||
case 'require':
|
||||
bp = te`module.exports.${BootProc}`;
|
||||
bp = te`module.exports.boot`;
|
||||
break;
|
||||
}
|
||||
tree = te`${tree}\nif (typeof module !== 'undefined' && ((typeof require === 'undefined' ? {main: void 0} : require).main === module)) __SYNDICATE__.bootModule(${bp});`;
|
||||
tree = te`${tree}\nif (typeof module !== 'undefined' && ((typeof require === 'undefined' ? {main: void 0} : require).main === module)) __SYNDICATE__.Actor.boot(${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';
|
||||
switch (moduleType) {
|
||||
|
@ -413,7 +340,6 @@ export function compile(options: CompileOptions): CompilerOutput {
|
|||
const cw = new CodeWriter(inputFilename);
|
||||
cw.emit(tree);
|
||||
|
||||
|
||||
const text = cw.text;
|
||||
|
||||
return {
|
||||
|
|
|
@ -2,17 +2,15 @@
|
|||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
import {
|
||||
TokenType, Token, Items,
|
||||
Token, Items,
|
||||
Pattern,
|
||||
foldItems, match, anonymousTemplate as template, commaJoin,
|
||||
startPos,
|
||||
|
||||
scope, bind, seq, alt, upTo, atom, atomString, group, exec,
|
||||
scope, bind, seq, alt, upTo, atom, atomString, group,
|
||||
repeat, option, withoutSpace, map, mapm, rest, discard,
|
||||
value, succeed, fail, separatedBy, anything, not, follows,
|
||||
value, succeed, fail, separatedBy, anything, not
|
||||
} from '../syntax/index.js';
|
||||
import * as Matcher from '../syntax/matcher.js';
|
||||
import { Path, Skeleton } from './internals.js';
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// AST types
|
||||
|
@ -23,41 +21,37 @@ export type Identifier = Token;
|
|||
export type Type = Items;
|
||||
export type Binder = { id: Identifier, type?: Type };
|
||||
|
||||
export interface FacetAction {
|
||||
implicitFacet: boolean;
|
||||
export interface TurnAction {
|
||||
implicitTurn: boolean;
|
||||
}
|
||||
|
||||
export type FacetFields = Binder[];
|
||||
|
||||
export interface FacetProducingAction extends FacetAction {
|
||||
export interface FacetSetupAction extends TurnAction {
|
||||
body: Statement;
|
||||
facetFields: FacetFields;
|
||||
}
|
||||
|
||||
export interface SpawnStatement extends FacetProducingAction {
|
||||
isDataspace: boolean;
|
||||
export interface SpawnStatement extends FacetSetupAction {
|
||||
name?: Expr;
|
||||
initialAssertions: Expr[];
|
||||
isLink: boolean;
|
||||
parentBinders: Binder[];
|
||||
parentInits: Expr[];
|
||||
}
|
||||
|
||||
export interface FieldDeclarationStatement extends FacetAction {
|
||||
property: Binder;
|
||||
export interface FieldDeclarationStatement extends TurnAction {
|
||||
field: Binder;
|
||||
init?: Expr;
|
||||
}
|
||||
|
||||
export interface AssertionEndpointStatement extends FacetAction {
|
||||
export interface AssertionEndpointStatement extends TurnAction {
|
||||
isDynamic: boolean,
|
||||
template: Expr,
|
||||
test?: Expr,
|
||||
}
|
||||
|
||||
export interface StatementFacetAction extends FacetAction {
|
||||
export interface StatementTurnAction extends TurnAction {
|
||||
body: Statement;
|
||||
}
|
||||
|
||||
export interface GenericEventEndpointStatement extends StatementFacetAction {
|
||||
export interface GenericEventEndpointStatement extends StatementTurnAction {
|
||||
terminal: boolean;
|
||||
isDynamic: boolean;
|
||||
}
|
||||
|
@ -68,7 +62,7 @@ export interface DataflowEndpointStatement extends GenericEventEndpointStatement
|
|||
}
|
||||
|
||||
export interface PseudoEventEndpointStatement extends GenericEventEndpointStatement {
|
||||
triggerType: 'start' | 'stop';
|
||||
triggerType: 'stop';
|
||||
}
|
||||
|
||||
export interface AssertionEventEndpointStatement extends GenericEventEndpointStatement {
|
||||
|
@ -86,20 +80,29 @@ export interface TypeDefinitionStatement {
|
|||
wireName?: Expr;
|
||||
}
|
||||
|
||||
export interface MessageSendStatement extends FacetAction {
|
||||
export interface MessageSendStatement extends TurnAction {
|
||||
expr: Expr;
|
||||
}
|
||||
|
||||
export interface DuringStatement extends FacetProducingAction {
|
||||
export interface DuringStatement extends FacetSetupAction {
|
||||
pattern: ValuePattern;
|
||||
}
|
||||
|
||||
export interface ReactStatement extends FacetProducingAction {
|
||||
export interface ReactStatement extends FacetSetupAction {
|
||||
}
|
||||
|
||||
export interface ActivationImport {
|
||||
activationKeyword: Identifier;
|
||||
target: { type: 'import', moduleName: Token } | { type: 'expr', moduleExpr: Expr };
|
||||
export interface BootStatement {
|
||||
formals: Binder[];
|
||||
body: Statement;
|
||||
}
|
||||
|
||||
export interface AtStatement {
|
||||
target: Expr;
|
||||
body: Statement;
|
||||
}
|
||||
|
||||
export interface CreateExpression {
|
||||
entity: Expr;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -115,23 +118,28 @@ export interface PDiscard {
|
|||
type: 'PDiscard',
|
||||
}
|
||||
|
||||
export interface PConstructor {
|
||||
type: 'PConstructor',
|
||||
ctor: Expr,
|
||||
arguments: ValuePattern[],
|
||||
}
|
||||
|
||||
export interface PConstant {
|
||||
type: 'PConstant',
|
||||
value: Expr,
|
||||
}
|
||||
|
||||
export interface PRecord {
|
||||
type: 'PRecord',
|
||||
ctor: Expr,
|
||||
arguments: ValuePattern[],
|
||||
}
|
||||
|
||||
export interface PArray {
|
||||
type: 'PArray',
|
||||
elements: ValuePattern[],
|
||||
}
|
||||
|
||||
export type ValuePattern = PCapture | PDiscard | PConstructor | PConstant | PArray;
|
||||
export interface PDict {
|
||||
type: 'PDict',
|
||||
elements: [Expr, ValuePattern][],
|
||||
}
|
||||
|
||||
export type ValuePattern = PCapture | PDiscard | PRecord | PConstant | PArray | PDict;
|
||||
|
||||
interface RawCall {
|
||||
items: Items;
|
||||
|
@ -140,12 +148,8 @@ interface RawCall {
|
|||
}
|
||||
|
||||
export interface StaticAnalysis {
|
||||
skeleton: Expr;
|
||||
constPaths: Path[];
|
||||
constVals: Expr[];
|
||||
capturePaths: Path[];
|
||||
skeleton: Expr; // constructs a P.Pattern
|
||||
captureBinders: Binder[];
|
||||
assertion: Expr;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -180,11 +184,11 @@ export class SyndicateParser {
|
|||
i => i ? acc.push(i) : void 0))));
|
||||
}
|
||||
|
||||
facetAction<T extends FacetAction>(pattern: (scope: T) => Pattern<any>): Pattern<T> {
|
||||
turnAction<T extends TurnAction>(pattern: (scope: T) => Pattern<any>): Pattern<T> {
|
||||
return i => {
|
||||
const scope = Object.create(null);
|
||||
scope.implicitFacet = true;
|
||||
const p = seq(option(map(atom('.'), _ => scope.implicitFacet = false)), pattern(scope));
|
||||
scope.implicitTurn = true;
|
||||
const p = seq(option(map(atom('.'), _ => scope.implicitTurn = false)), pattern(scope));
|
||||
const r = p(i);
|
||||
if (r === null) return null;
|
||||
return [scope, r[1]];
|
||||
|
@ -193,45 +197,43 @@ export class SyndicateParser {
|
|||
|
||||
readonly headerExpr = this.expr(kw('asserting'), kw('let'));
|
||||
|
||||
// Principal: Facet
|
||||
// Principal: Turn
|
||||
readonly spawn: Pattern<SpawnStatement> =
|
||||
this.facetAction(o => {
|
||||
o.isDataspace = false;
|
||||
o.initialAssertions = [];
|
||||
this.turnAction(o => {
|
||||
o.isLink = false;
|
||||
o.parentBinders = [];
|
||||
o.parentInits = [];
|
||||
o.body = [];
|
||||
o.facetFields = [];
|
||||
return seq(atom('spawn'),
|
||||
option(seq(atom('dataspace'), exec(() => o.isDataspace = true))),
|
||||
option(seq(atom('named'),
|
||||
bind(o, 'name', this.headerExpr))),
|
||||
repeat(alt(seq(kw('asserting'),
|
||||
map(this.headerExpr, e => o.initialAssertions.push(e))),
|
||||
map(scope((l: { b: Binder, init: Expr }) =>
|
||||
seq(kw('let'),
|
||||
bind(l, 'b', this.binder),
|
||||
atom('='),
|
||||
bind(l, 'init', this.headerExpr))),
|
||||
l => {
|
||||
o.parentBinders.push(l.b);
|
||||
o.parentInits.push(l.init);
|
||||
}))),
|
||||
option(map(atom('linked'), _ => o.isLink = true)),
|
||||
option(seq(atom('named'), bind(o, 'name', this.headerExpr))),
|
||||
repeat(alt(
|
||||
/* seq(kw('asserting'), map(this.headerExpr, e => o.initialAssertions.push(e))), */
|
||||
map(scope(
|
||||
(l: { b: Binder, init: Expr }) =>
|
||||
seq(kw('let'),
|
||||
bind(l, 'b', this.binder),
|
||||
atom('='),
|
||||
bind(l, 'init', this.headerExpr))),
|
||||
l => {
|
||||
o.parentBinders.push(l.b);
|
||||
o.parentInits.push(l.init);
|
||||
}))),
|
||||
this.block(o.body));
|
||||
});
|
||||
|
||||
// Principal: Dataspace, but only for implementation reasons, so really Facet
|
||||
// Principal: Turn
|
||||
readonly fieldDeclarationStatement: Pattern<FieldDeclarationStatement> =
|
||||
this.facetAction(o => {
|
||||
this.turnAction(o => {
|
||||
return seq(atom('field'),
|
||||
bind(o, 'property', this.binder),
|
||||
bind(o, 'field', this.binder),
|
||||
option(seq(atom('='), bind(o, 'init', this.expr()))),
|
||||
this.statementBoundary);
|
||||
});
|
||||
|
||||
// Principal: Facet
|
||||
// Principal: Turn
|
||||
readonly assertionEndpointStatement: Pattern<AssertionEndpointStatement> =
|
||||
this.facetAction(o => {
|
||||
this.turnAction(o => {
|
||||
o.isDynamic = true;
|
||||
return seq(atom('assert'),
|
||||
option(map(kw('snapshot'), _ => o.isDynamic = false)),
|
||||
|
@ -240,15 +242,15 @@ export class SyndicateParser {
|
|||
this.statementBoundary);
|
||||
});
|
||||
|
||||
blockFacetAction(kw: Pattern<any>): Pattern<StatementFacetAction> {
|
||||
return this.facetAction(o => {
|
||||
blockTurnAction(kw: Pattern<any>): Pattern<StatementTurnAction> {
|
||||
return this.turnAction(o => {
|
||||
o.body = [];
|
||||
return seq(kw, this.block(o.body));
|
||||
});
|
||||
}
|
||||
|
||||
// Principal: Facet
|
||||
readonly dataflowStatement = this.blockFacetAction(atom('dataflow'));
|
||||
// Principal: Turn
|
||||
readonly dataflowStatement = this.blockTurnAction(atom('dataflow'));
|
||||
|
||||
mandatoryIfNotTerminal(o: GenericEventEndpointStatement, p: Pattern<any>): Pattern<any> {
|
||||
return i => {
|
||||
|
@ -256,9 +258,9 @@ export class SyndicateParser {
|
|||
};
|
||||
}
|
||||
|
||||
// Principal: Facet
|
||||
// Principal: Turn
|
||||
readonly eventHandlerEndpointStatement: Pattern<EventHandlerEndpointStatement> =
|
||||
this.facetAction(o => {
|
||||
this.turnAction(o => {
|
||||
o.terminal = false;
|
||||
o.isDynamic = true;
|
||||
o.body = [];
|
||||
|
@ -268,8 +270,7 @@ export class SyndicateParser {
|
|||
this.expr())),
|
||||
_ => o.triggerType = 'dataflow'),
|
||||
this.mandatoryIfNotTerminal(o, this.statement(o.body))),
|
||||
mapm(seq(bind(o, 'triggerType',
|
||||
alt(atomString('start'), atomString('stop'))),
|
||||
mapm(seq(bind(o, 'triggerType', atomString('stop')),
|
||||
option(this.statement(o.body))),
|
||||
v => o.terminal ? fail : succeed(v)),
|
||||
seq(bind(o, 'triggerType',
|
||||
|
@ -288,67 +289,77 @@ export class SyndicateParser {
|
|||
scope(o => seq(bind(o, 'expectedUse', alt(atomString('message'), atomString('assertion'))),
|
||||
atom('type'),
|
||||
bind(o, 'label', this.identifier),
|
||||
group('(', bind(o, 'fields',
|
||||
repeat(this.binder, { separator: atom(',') }))),
|
||||
group('(', bind(o, 'fields', repeat(this.binder, { separator: atom(',') }))),
|
||||
option(seq(atom('='),
|
||||
bind(o, 'wireName', withoutSpace(upTo(this.statementBoundary))))),
|
||||
this.statementBoundary));
|
||||
|
||||
// Principal: Facet
|
||||
// Principal: Turn
|
||||
readonly messageSendStatement: Pattern<MessageSendStatement> =
|
||||
this.facetAction(o => seq(atom('send'),
|
||||
this.turnAction(o => seq(atom('send'),
|
||||
atom('message'),
|
||||
not(this.statementBoundary),
|
||||
bind(o, 'expr', withoutSpace(upTo(this.statementBoundary))),
|
||||
this.statementBoundary));
|
||||
|
||||
// Principal: Facet
|
||||
// Principal: Turn
|
||||
readonly duringStatement: Pattern<DuringStatement> =
|
||||
this.facetAction(o => {
|
||||
this.turnAction(o => {
|
||||
o.body = [];
|
||||
o.facetFields = [];
|
||||
return seq(atom('during'),
|
||||
bind(o, 'pattern', this.valuePattern(atom('=>'))),
|
||||
seq(atom('=>'), this.statement(o.body)));
|
||||
});
|
||||
|
||||
// Principal: Facet
|
||||
// Principal: Turn
|
||||
readonly reactStatement: Pattern<ReactStatement> =
|
||||
this.facetAction(o => {
|
||||
this.turnAction(o => {
|
||||
o.body = [];
|
||||
o.facetFields = [];
|
||||
return seq(atom('react'), this.block(o.body));
|
||||
});
|
||||
|
||||
// Principal: none
|
||||
readonly bootStatement: Pattern<Statement> =
|
||||
value(o => {
|
||||
o.value = [];
|
||||
return seq(atom('boot'), this.block(o.value));
|
||||
readonly bootStatement: Pattern<BootStatement> =
|
||||
scope(o => {
|
||||
o.body = [];
|
||||
return seq(
|
||||
atom('boot'),
|
||||
group('(', bind(o, 'formals', repeat(this.binder, { separator: atom(',') }))),
|
||||
this.block(o.body));
|
||||
});
|
||||
|
||||
// Principal: Facet
|
||||
readonly stopStatement = this.blockFacetAction(atom('stop'));
|
||||
// Principal: Turn
|
||||
readonly stopStatement = this.blockTurnAction(atom('stop'));
|
||||
|
||||
// Principal: none
|
||||
readonly 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
|
||||
}),
|
||||
this.statementBoundary))),
|
||||
map(this.expr(), e => o.target = {
|
||||
type: 'expr',
|
||||
moduleExpr: e
|
||||
})))));
|
||||
readonly atStatement: Pattern<AtStatement> =
|
||||
scope(o => {
|
||||
o.body = [];
|
||||
return seq(atom('at'),
|
||||
bind(o, 'target', this.expr()),
|
||||
this.statement(o.body));
|
||||
});
|
||||
|
||||
// Principal: none
|
||||
readonly createExpression: Pattern<CreateExpression> =
|
||||
scope(o => seq(atom('create'), bind(o, 'entity', this.expr())));
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Syntax of patterns over Value, used in endpoints
|
||||
|
||||
// $id - capture of discard
|
||||
// _ - discard
|
||||
//
|
||||
// expr(pat, ...) - record ctor
|
||||
// $id(pat) - nested capture
|
||||
// [pat, ...] - array pat
|
||||
// {expr: pat, ...} - dict pat
|
||||
//
|
||||
// expr(expr, ...) - constant
|
||||
// [expr, ...] - constant
|
||||
// {expr: expr, ...} - constant
|
||||
// other - constant
|
||||
|
||||
readonly pCaptureBinder: Pattern<Binder> =
|
||||
mapm(this.binder, i => {
|
||||
return i.id.text.startsWith('$')
|
||||
|
@ -366,16 +377,28 @@ export class SyndicateParser {
|
|||
bs => bs.some(b => b));
|
||||
}
|
||||
|
||||
// $id - capture of discard
|
||||
// _ - discard
|
||||
//
|
||||
// expr(pat, ...) - record ctor
|
||||
// $id(pat) - nested capture
|
||||
// [pat, ...] - array pat
|
||||
//
|
||||
// expr(expr, ...) - constant
|
||||
// [expr, ...] - constant
|
||||
// other - constant
|
||||
pArray: Pattern<PArray> =
|
||||
scope(o => {
|
||||
o.type = 'PArray';
|
||||
return group(
|
||||
'[', mapm(bind(o, 'elements', separatedBy(this.valuePattern(), atom(','))),
|
||||
v => (o.elements.every(p => p.type === 'PConstant') ? fail : succeed(v))));
|
||||
});
|
||||
|
||||
pDict: Pattern<PDict> =
|
||||
scope(o => {
|
||||
o.type = 'PDict';
|
||||
return group(
|
||||
'{', mapm(bind(o,
|
||||
'elements',
|
||||
separatedBy(
|
||||
scope<[Expr, ValuePattern]>(e =>
|
||||
seq(bind(e, '0', this.expr(atom(':'))),
|
||||
atom(':'),
|
||||
bind(e, '1', this.valuePattern()))),
|
||||
atom(','))),
|
||||
v => (o.elements.every(e => e[1].type === 'PConstant') ? fail : succeed(v))));
|
||||
});
|
||||
|
||||
pRawCall(... extraStops: Pattern<any>[]): Pattern<RawCall> {
|
||||
return scope((o: RawCall) =>
|
||||
|
@ -401,6 +424,8 @@ export class SyndicateParser {
|
|||
return bind(o, 'binder', this.pCaptureBinder);
|
||||
}),
|
||||
scope(o => map(this.pDiscard, _ => o.type = 'PDiscard')),
|
||||
this.pArray,
|
||||
this.pDict,
|
||||
mapm<RawCall, ValuePattern>(
|
||||
this.pRawCall(... extraStops),
|
||||
o => {
|
||||
|
@ -424,7 +449,7 @@ export class SyndicateParser {
|
|||
const argPats = o.arguments.map(a => match(this.valuePattern(), a, null));
|
||||
if (argPats.some(p => p === null)) return fail;
|
||||
return succeed({
|
||||
type: 'PConstructor',
|
||||
type: 'PRecord',
|
||||
ctor: o.callee,
|
||||
arguments: argPats as ValuePattern[]
|
||||
});
|
||||
|
@ -446,125 +471,43 @@ export class SyndicateTypedParser extends SyndicateParser {
|
|||
//---------------------------------------------------------------------------
|
||||
// Value pattern utilities
|
||||
|
||||
export function patternText(p: ValuePattern): Items {
|
||||
switch (p.type) {
|
||||
case 'PDiscard': return template`_`;
|
||||
case 'PConstant': return p.value;
|
||||
case 'PCapture':
|
||||
{
|
||||
const binderId = { ... p.binder.id, text: '$' + p.binder.id.text };
|
||||
const affix =
|
||||
(p.inner.type === 'PDiscard') ? [] : template`(${patternText(p.inner)})`;
|
||||
if (p.binder.type !== void 0) {
|
||||
return template`${[binderId]}:${p.binder.type}${affix}`;
|
||||
} else {
|
||||
return template`${[binderId]}${affix}`;
|
||||
}
|
||||
}
|
||||
case 'PArray': return template`[${commaJoin(p.elements.map(patternText))}]`;
|
||||
case 'PConstructor': return template`${p.ctor}(${commaJoin(p.arguments.map(patternText))})`;
|
||||
}
|
||||
}
|
||||
|
||||
export function instantiatePatternToPattern(p: ValuePattern): ValuePattern {
|
||||
switch (p.type) {
|
||||
case 'PDiscard': return p;
|
||||
case 'PConstant': return p;
|
||||
case 'PCapture': return { type: 'PConstant', value: [p.binder.id] };
|
||||
case 'PArray':
|
||||
return {
|
||||
type: 'PArray',
|
||||
elements: p.elements.map(instantiatePatternToPattern),
|
||||
};
|
||||
case 'PConstructor':
|
||||
return {
|
||||
type: 'PConstructor',
|
||||
ctor: p.ctor,
|
||||
arguments: p.arguments.map(instantiatePatternToPattern),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const eDiscard: Expr = template`(__SYNDICATE__.Discard._instance)`;
|
||||
const eCapture = (e: Expr): Expr => template`(__SYNDICATE__.Capture(${e}))`;
|
||||
const eDiscard: Expr = template`(__SYNDICATE__.Pattern._)`;
|
||||
const eBind = (e: Expr): Expr => template`(__SYNDICATE__.Pattern.bind(${e}))`;
|
||||
const eLit = (e : Expr): Expr => template`(__SYNDICATE__.Pattern.lit(${e}))`;
|
||||
|
||||
export function compilePattern(pattern: ValuePattern): StaticAnalysis {
|
||||
const constPaths: Path[] = [];
|
||||
const constVals: Expr[] = [];
|
||||
const capturePaths: Path[] = [];
|
||||
const captureBinders: Binder[] = [];
|
||||
|
||||
const currentPath: Path = [];
|
||||
|
||||
function walk(pattern: ValuePattern): [Skeleton<Expr>, Expr] {
|
||||
function walk(pattern: ValuePattern): Expr {
|
||||
switch (pattern.type) {
|
||||
case 'PDiscard':
|
||||
return [null, eDiscard];
|
||||
return eDiscard;
|
||||
case 'PCapture': {
|
||||
capturePaths.push(currentPath.slice());
|
||||
captureBinders.push(pattern.binder);
|
||||
const [s, a] = walk(pattern.inner);
|
||||
return [s, eCapture(a)];
|
||||
return eBind(walk(pattern.inner));
|
||||
}
|
||||
case 'PConstant':
|
||||
constPaths.push(currentPath.slice());
|
||||
constVals.push(pattern.value);
|
||||
return [null, pattern.value];
|
||||
case 'PConstructor': {
|
||||
const skel: Skeleton<Expr> = {
|
||||
shape: template`__SYNDICATE__.Skeleton.constructorInfoSignature((${pattern.ctor}).constructorInfo)`,
|
||||
members: [],
|
||||
};
|
||||
const assertionArgs: Expr[] = [];
|
||||
pattern.arguments.forEach((argPat, i) => {
|
||||
currentPath.push(i);
|
||||
const [s, a] = walk(argPat);
|
||||
skel.members.push(s);
|
||||
assertionArgs.push(a);
|
||||
currentPath.pop();
|
||||
});
|
||||
return [skel, template`(${pattern.ctor}(${commaJoin(assertionArgs)}))`];
|
||||
return eLit(pattern.value);
|
||||
case 'PRecord': {
|
||||
const pieces = [template`(${pattern.ctor}).constructorInfo.label`,
|
||||
... pattern.arguments.map(walk)];
|
||||
return template`(__SYNDICATE__.Pattern.rec(${commaJoin(pieces)}))`;
|
||||
}
|
||||
case 'PArray': {
|
||||
const skel: Skeleton<Expr> = {
|
||||
shape: [ {
|
||||
start: startPos(null),
|
||||
end: startPos(null),
|
||||
type: TokenType.STRING,
|
||||
text: JSON.stringify(pattern.elements.length.toString()),
|
||||
} ],
|
||||
members: []
|
||||
};
|
||||
const elements: Expr[] = [];
|
||||
pattern.elements.forEach((elemPat, i) => {
|
||||
currentPath.push(i);
|
||||
const [s, a] = walk(elemPat);
|
||||
skel.members.push(s);
|
||||
elements.push(a);
|
||||
currentPath.pop();
|
||||
});
|
||||
return [skel, template`[${commaJoin(elements)}]`];
|
||||
const pieces = pattern.elements.map(walk);
|
||||
return template`(__SYNDICATE__.Pattern.arr(${commaJoin(pieces)}))`;
|
||||
}
|
||||
case 'PDict': {
|
||||
const pieces = pattern.elements.map(([k, v]) => template`[${k}, ${walk(v)}]`);
|
||||
return template`(__SYNDICATE__.Pattern.dict(${commaJoin(pieces)}))`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [skeletonStructure, assertion] = walk(pattern);
|
||||
const skeleton = renderSkeleton(skeletonStructure);
|
||||
const skeleton = walk(pattern);
|
||||
|
||||
return {
|
||||
skeleton,
|
||||
constPaths,
|
||||
constVals,
|
||||
capturePaths,
|
||||
captureBinders,
|
||||
assertion,
|
||||
};
|
||||
}
|
||||
|
||||
function renderSkeleton(skel: Skeleton<Expr>): Expr {
|
||||
if (skel === null) {
|
||||
return template`null`;
|
||||
} else {
|
||||
return template`({shape:${skel.shape}, members: [${commaJoin(skel.members.map(renderSkeleton))}]})`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,5 @@
|
|||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
export * as Grammar from './grammar.js';
|
||||
export * as Internals from './internals.js';
|
||||
export * as Codegen from './codegen.js';
|
||||
export { compile, CompileOptions } from './codegen.js';
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
export const BootProc = '__SYNDICATE__bootProc';
|
||||
|
||||
// Keep these definitions in sync with api.ts from the core package
|
||||
//
|
||||
export type NonEmptySkeleton<Shape> = { shape: Shape, members: Skeleton<Shape>[] };
|
||||
export type Skeleton<Shape> = null | NonEmptySkeleton<Shape>;
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
import {
|
||||
Pattern as P,
|
||||
Observe, fromObserve,
|
||||
assertObserve,
|
||||
Record,
|
||||
Actor, Dataspace,
|
||||
} from '..';
|
||||
|
@ -41,38 +41,29 @@ Actor.boot(t => {
|
|||
}
|
||||
});
|
||||
|
||||
t.assert(ds, fromObserve(Observe({
|
||||
pattern: P.rec(SetBox.constructorInfo.label, P.bind()),
|
||||
observer: t.ref({
|
||||
message(_t, [v]) {
|
||||
boxValue.value = v;
|
||||
// console.log('box updated value', v);
|
||||
}
|
||||
})
|
||||
})));
|
||||
assertObserve(t, ds, P.rec(SetBox.constructorInfo.label, P.bind()), {
|
||||
message(_t, [v]) {
|
||||
boxValue.value = v;
|
||||
// console.log('box updated value', v);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
t.spawn(t => {
|
||||
t.activeFacet.actor.name = 'client';
|
||||
|
||||
t.assert(ds, fromObserve(Observe({
|
||||
pattern: P.rec(BoxState.constructorInfo.label, P.bind()),
|
||||
observer: t.ref({
|
||||
assert(t, [v], _handle) {
|
||||
// console.log('client sending SetBox', v + 1);
|
||||
t.message(ds, SetBox(v + 1));
|
||||
}
|
||||
})
|
||||
})));
|
||||
assertObserve(t, ds, P.rec(BoxState.constructorInfo.label, P.bind()), {
|
||||
assert(t, [v], _handle) {
|
||||
// console.log('client sending SetBox', v + 1);
|
||||
t.message(ds, SetBox(v + 1));
|
||||
}
|
||||
});
|
||||
|
||||
t.assert(ds, fromObserve(Observe({
|
||||
pattern: P.rec(BoxState.constructorInfo.label, P._),
|
||||
observer: t.ref({
|
||||
retract(_t) {
|
||||
console.log('box gone');
|
||||
console.timeEnd('box-and-client-' + N.toString());
|
||||
}
|
||||
})
|
||||
})));
|
||||
assertObserve(t, ds, P.rec(BoxState.constructorInfo.label, P._), {
|
||||
retract(_t) {
|
||||
console.log('box gone');
|
||||
console.timeEnd('box-and-client-' + N.toString());
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
import {
|
||||
Pattern as P,
|
||||
Observe, fromObserve,
|
||||
assertObserve,
|
||||
Record,
|
||||
Actor, Dataspace,
|
||||
} from '..';
|
||||
|
@ -41,38 +41,29 @@ Actor.boot(t => {
|
|||
}
|
||||
});
|
||||
|
||||
t.assert(ds, fromObserve(Observe({
|
||||
pattern: P.rec(SetBox.constructorInfo.label, P.bind()),
|
||||
observer: t.ref({
|
||||
message(_t, [v]: [number]) {
|
||||
boxValue.value = v;
|
||||
// console.log('box updated value', v);
|
||||
}
|
||||
})
|
||||
})));
|
||||
assertObserve(t, ds, P.rec(SetBox.constructorInfo.label, P.bind()), {
|
||||
message(_t, [v]: [number]) {
|
||||
boxValue.value = v;
|
||||
// console.log('box updated value', v);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
t.spawn(t => {
|
||||
t.activeFacet.actor.name = 'client';
|
||||
|
||||
t.assert(ds, fromObserve(Observe({
|
||||
pattern: P.rec(BoxState.constructorInfo.label, P.bind()),
|
||||
observer: t.ref({
|
||||
assert(t, [v]: [number], _handle) {
|
||||
// console.log('client sending SetBox', v + 1);
|
||||
t.message(ds, SetBox(v + 1));
|
||||
}
|
||||
})
|
||||
})));
|
||||
assertObserve(t, ds, P.rec(BoxState.constructorInfo.label, P.bind()), {
|
||||
assert(t, [v]: [number], _handle) {
|
||||
// console.log('client sending SetBox', v + 1);
|
||||
t.message(ds, SetBox(v + 1));
|
||||
}
|
||||
});
|
||||
|
||||
t.assert(ds, fromObserve(Observe({
|
||||
pattern: P.rec(BoxState.constructorInfo.label, P._),
|
||||
observer: t.ref({
|
||||
retract(_t) {
|
||||
console.log('box gone');
|
||||
console.timeEnd('box-and-client-' + N.toString());
|
||||
}
|
||||
})
|
||||
})));
|
||||
assertObserve(t, ds, P.rec(BoxState.constructorInfo.label, P._), {
|
||||
retract(_t) {
|
||||
console.log('box gone');
|
||||
console.timeEnd('box-and-client-' + N.toString());
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -129,6 +129,10 @@ export class Facet {
|
|||
this.outbound = initialAssertions;
|
||||
}
|
||||
|
||||
turn(a: LocalAction) {
|
||||
Turn.for(this, a);
|
||||
}
|
||||
|
||||
onStop(a: LocalAction): void {
|
||||
this.shutdownActions.push(a);
|
||||
}
|
||||
|
@ -251,6 +255,11 @@ export class Turn {
|
|||
return newFacet;
|
||||
}
|
||||
|
||||
// Alias for syndicatec code generator to use
|
||||
_stop(facet: Facet = this.activeFacet, continuation?: LocalAction) {
|
||||
this.stop(facet, continuation);
|
||||
}
|
||||
|
||||
stop(facet: Facet = this.activeFacet, continuation?: LocalAction) {
|
||||
this.enqueue(facet.parent!, t => {
|
||||
facet._terminate(t, true);
|
||||
|
@ -258,11 +267,16 @@ export class Turn {
|
|||
});
|
||||
}
|
||||
|
||||
spawn(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): void {
|
||||
this._spawn(bootProc, initialAssertions);
|
||||
// Alias for syndicatec code generator to use
|
||||
_spawn(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): void {
|
||||
this.spawn(bootProc, initialAssertions);
|
||||
}
|
||||
|
||||
_spawn(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): Actor {
|
||||
spawn(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): void {
|
||||
this.__spawn(bootProc, initialAssertions);
|
||||
}
|
||||
|
||||
__spawn(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): Actor {
|
||||
const newOutbound: OutboundMap = new Map();
|
||||
initialAssertions.forEach(key => newOutbound.set(key, this.activeFacet.outbound.get(key)!));
|
||||
// ^ we trust initialAssertions, so can use `!` safely
|
||||
|
@ -275,9 +289,14 @@ export class Turn {
|
|||
return newActor;
|
||||
}
|
||||
|
||||
// Alias for syndicatec code generator to use
|
||||
_spawnLink(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): void {
|
||||
this.spawnLink(bootProc, initialAssertions);
|
||||
}
|
||||
|
||||
spawnLink(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): void {
|
||||
if (!this.activeFacet.isLive) return;
|
||||
const newActor = this._spawn(bootProc, initialAssertions);
|
||||
const newActor = this.__spawn(bootProc, initialAssertions);
|
||||
this.activeFacet._halfLink(newActor.root);
|
||||
newActor.root._halfLink(this.activeFacet);
|
||||
}
|
||||
|
@ -294,6 +313,11 @@ export class Turn {
|
|||
return new Field(this.activeFacet.actor.dataflowGraph, initial, name);
|
||||
}
|
||||
|
||||
// Alias for syndicatec code generator to use
|
||||
_dataflow(a: LocalAction) {
|
||||
this.dataflow(a);
|
||||
}
|
||||
|
||||
dataflow(a: LocalAction) {
|
||||
const f = this.activeFacet;
|
||||
const b = (t: Turn) => f.isLive && t._inFacet(f, a);
|
||||
|
@ -385,6 +409,10 @@ export class Turn {
|
|||
queueTask(() => Turn.for(actor.root, t => q.forEach(f => f(t)))));
|
||||
this.queues = null;
|
||||
}
|
||||
|
||||
withSelfDo(a: LocalAction) {
|
||||
a(this);
|
||||
}
|
||||
}
|
||||
|
||||
function stopIfInertAfter(a: LocalAction): LocalAction {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
// Property-based "dataflow"
|
||||
|
||||
import { FlexSet, FlexMap, Canonicalizer, Value, is } from '@preserves/core';
|
||||
import { Ref } from 'index.js';
|
||||
import { Ref } from './actor.js';
|
||||
import * as MapSet from './mapset.js';
|
||||
|
||||
export interface ObservingGraph<ObjectId> {
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
|
||||
import { IdentityMap } from '@preserves/core';
|
||||
import { Index } from './skeleton.js';
|
||||
import { Assertion, Entity, Handle, Turn } from './actor.js';
|
||||
import { Observe, toObserve } from '../gen/dataspace.js';
|
||||
import { Assertion, Entity, Facet, Handle, LocalAction, Ref, Turn } from './actor.js';
|
||||
import { fromObserve, Observe, toObserve } from '../gen/dataspace.js';
|
||||
import * as P from '../gen/dataspacePatterns.js';
|
||||
|
||||
export class Dataspace implements Partial<Entity> {
|
||||
readonly index = new Index();
|
||||
|
@ -33,3 +34,40 @@ export class Dataspace implements Partial<Entity> {
|
|||
this.index.deliverMessage(turn, v);
|
||||
}
|
||||
}
|
||||
|
||||
export function assertionObserver(f: (t: Turn, a: Assertion) => LocalAction | undefined): Partial<Entity> {
|
||||
const assertionMap = new IdentityMap<Handle, LocalAction>();
|
||||
return {
|
||||
assert(t: Turn, a: Assertion, h: Handle): void {
|
||||
const g = f(t, a) ?? null;
|
||||
if (g !== null) {
|
||||
assertionMap.set(h, g);
|
||||
}
|
||||
},
|
||||
retract(t: Turn, h: Handle): void {
|
||||
assertionMap.get(h)?.(t);
|
||||
assertionMap.delete(h);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function assertionFacetObserver(f: (t: Turn, a: Assertion) => void, inertOk: boolean = true): Partial<Entity> {
|
||||
const facetMap = new IdentityMap<Handle, Facet>();
|
||||
return {
|
||||
assert(t: Turn, a: Assertion, h: Handle): void {
|
||||
facetMap.set(h, t.facet(t => {
|
||||
if (inertOk) t.activeFacet.preventInertCheck();
|
||||
f(t, a);
|
||||
}));
|
||||
},
|
||||
retract(t: Turn, h: Handle): void {
|
||||
const facet = facetMap.get(h);
|
||||
if (facet) t.stop(facet);
|
||||
facetMap.delete(h);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function assertObserve(t: Turn, ds: Ref, pattern: P.Pattern, e: Partial<Entity>): Handle {
|
||||
return t.assert(ds, fromObserve(Observe({ pattern, observer: t.ref(e) })));
|
||||
}
|
||||
|
|
|
@ -8,6 +8,6 @@
|
|||
<main id="main">
|
||||
</main>
|
||||
<script>
|
||||
Syndicate.bootModule(Main.__SYNDICATE__bootProc);
|
||||
Main.boot();
|
||||
</script>
|
||||
</html>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@syndicate-lang/syndicatec": "^0.2.0",
|
||||
"rollup": "^2.37.0",
|
||||
"rollup-plugin-sourcemaps": "^0.6.3"
|
||||
"rollup": "^2.60",
|
||||
"rollup-plugin-sourcemaps": "^0.6"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,15 @@
|
|||
|
||||
import { BoxState, SetBox, N } from './protocol.js';
|
||||
|
||||
boot {
|
||||
boot(ds) {
|
||||
spawn named 'box' {
|
||||
field value = 0;
|
||||
assert BoxState(this.value);
|
||||
stop on (this.value === N)
|
||||
field boxValue = 0;
|
||||
at ds {
|
||||
assert BoxState(boxValue.value);
|
||||
on message SetBox($v) => boxValue.value = v;
|
||||
}
|
||||
stop on (boxValue.value === N) {
|
||||
console.log('terminated box root facet');
|
||||
on message SetBox($v) => this.value = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,14 @@
|
|||
|
||||
import { BoxState, SetBox } from './protocol.js';
|
||||
|
||||
boot {
|
||||
boot(ds, doneCallback) {
|
||||
spawn named 'client' {
|
||||
on asserted BoxState($v) => send message SetBox(v + 1);
|
||||
on retracted BoxState(_) => console.log('box gone');
|
||||
at ds {
|
||||
on asserted BoxState($v) => send message SetBox(v + 1);
|
||||
on retracted BoxState(_) => {
|
||||
console.log('box gone');
|
||||
doneCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,14 @@
|
|||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
import { N } from './protocol.js';
|
||||
activate import './box.js';
|
||||
activate import './client.js';
|
||||
import * as Box from './box.js';
|
||||
import * as Client from './client.js';
|
||||
import { Dataspace } from '@syndicate-lang/core';
|
||||
|
||||
console.time('box-and-client-' + N.toString());
|
||||
boot {
|
||||
thisFacet.actor.dataspace.ground().addStopHandler(() =>
|
||||
console.timeEnd('box-and-client-' + N.toString()));
|
||||
boot() {
|
||||
thisTurn.activeFacet.preventInertCheck();
|
||||
const ds = create new Dataspace();
|
||||
Box.boot(ds);
|
||||
Client.boot(ds, () => console.timeEnd('box-and-client-' + N.toString()));
|
||||
}
|
||||
|
|
|
@ -8,6 +8,6 @@
|
|||
<main id="main">
|
||||
</main>
|
||||
<script>
|
||||
Syndicate.bootModule(Main.__SYNDICATE__bootProc);
|
||||
Syndicate.Actor.boot(Main.boot);
|
||||
</script>
|
||||
</html>
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@syndicate-lang/syndicatec": "^0.2.0",
|
||||
"rollup": "^2.37.0",
|
||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||
"typescript": "^4.1.3"
|
||||
"rollup": "^2.60",
|
||||
"rollup-plugin-sourcemaps": "^0.6",
|
||||
"typescript": "^4.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,17 @@
|
|||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
import { BoxState, SetBox, N } from './protocol.js';
|
||||
import { Ref } from '@syndicate-lang/core';
|
||||
|
||||
boot {
|
||||
boot(ds: Ref) {
|
||||
spawn named 'box' {
|
||||
field value: number = 0;
|
||||
assert BoxState(this.value);
|
||||
stop on (this.value === N)
|
||||
field boxValue: number = 0;
|
||||
at ds {
|
||||
assert BoxState(boxValue.value);
|
||||
on message SetBox($v: number) => boxValue.value = v;
|
||||
}
|
||||
stop on (boxValue.value === N) {
|
||||
console.log('terminated box root facet');
|
||||
on message SetBox($v: number) => this.value = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,16 @@
|
|||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
import { BoxState, SetBox } from './protocol.js';
|
||||
import { Ref } from '@syndicate-lang/core';
|
||||
|
||||
boot {
|
||||
boot(ds: Ref, doneCallback: () => void) {
|
||||
spawn named 'client' {
|
||||
on asserted BoxState($v: number) => send message SetBox(v + 1);
|
||||
on retracted BoxState(_) => console.log('box gone');
|
||||
at ds {
|
||||
on asserted BoxState($v: number) => send message SetBox(v + 1);
|
||||
on retracted BoxState(_) => {
|
||||
console.log('box gone');
|
||||
doneCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,14 @@
|
|||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
import { N } from './protocol.js';
|
||||
activate import './box.js';
|
||||
activate import './client.js';
|
||||
import * as Box from './box.js';
|
||||
import * as Client from './client.js';
|
||||
import { Dataspace } from '@syndicate-lang/core';
|
||||
|
||||
console.time('box-and-client-' + N.toString());
|
||||
boot {
|
||||
thisFacet.actor.dataspace.ground().addStopHandler(() =>
|
||||
console.timeEnd('box-and-client-' + N.toString()));
|
||||
boot() {
|
||||
thisTurn.activeFacet.preventInertCheck();
|
||||
const ds = create new Dataspace();
|
||||
Box.boot(thisTurn, ds);
|
||||
Client.boot(thisTurn, ds, () => console.timeEnd('box-and-client-' + N.toString()));
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
export assertion type BoxState(value);
|
||||
export message type SetBox(newValue);
|
||||
export assertion type BoxState(value: number);
|
||||
export message type SetBox(newValue: number);
|
||||
|
||||
export const N = 100000;
|
||||
|
|
Loading…
Reference in New Issue