Update compiler

This commit is contained in:
Tony Garnock-Jones 2021-12-03 00:55:42 +01:00
parent f9d1e694e0
commit dd14c8471d
21 changed files with 448 additions and 515 deletions

View File

@ -1,3 +1,5 @@
__ignored__ := $(shell ./setup.sh)
LERNA=./node_modules/.bin/lerna LERNA=./node_modules/.bin/lerna
bootstrap: node_modules/lerna bootstrap: node_modules/lerna

View File

@ -3,7 +3,7 @@
import { import {
isToken, isTokenType, replace, commaJoin, startPos, fixPos, joinItems, isToken, isTokenType, replace, commaJoin, startPos, fixPos, joinItems,
laxRead, itemText, match, laxRead, itemText,
Items, Pattern, Templates, Substitution, TokenType, Items, Pattern, Templates, Substitution, TokenType,
SourceMap, CodeWriter, TemplateFunction, Token, SpanIndex, SourceMap, CodeWriter, TemplateFunction, Token, SpanIndex,
@ -11,19 +11,12 @@ import {
import { import {
SyndicateParser, SyndicateTypedParser, SyndicateParser, SyndicateTypedParser,
Identifier, Identifier,
FacetAction, TurnAction,
Statement, Statement,
ActivationImport,
FacetFields,
Binder, Binder,
compilePattern, compilePattern,
patternText,
instantiatePatternToPattern,
} from './grammar.js'; } from './grammar.js';
import {
BootProc,
} from './internals.js';
export function stripShebang(items: Items): Items { export function stripShebang(items: Items): Items {
if ((items.length > 0) && if ((items.length > 0) &&
@ -52,22 +45,15 @@ export interface CompilerOutput {
sourceToTargetMap: SpanIndex<number>; sourceToTargetMap: SpanIndex<number>;
} }
function receiverFor(s: FacetAction): Substitution { function receiverFor(s: TurnAction): Substitution {
return (s.implicitFacet) ? 'thisFacet.' : '.'; return (s.implicitTurn) ? 'thisTurn.' : '.';
}
export interface ActivationRecord {
activation: ActivationImport;
activationScriptId: Identifier;
} }
export class ExpansionContext { export class ExpansionContext {
readonly parser: SyndicateParser; readonly parser: SyndicateParser;
readonly moduleType: ModuleType; readonly moduleType: ModuleType;
readonly activationRecords: Array<ActivationRecord> = []; hasBootableBootProc: boolean = false;
hasBootProc: boolean = false;
readonly typescript: boolean; readonly typescript: boolean;
_collectedFields: FacetFields | null = null;
nextIdNumber = 0; nextIdNumber = 0;
constructor(moduleType: ModuleType, constructor(moduleType: ModuleType,
@ -82,28 +68,12 @@ export class ExpansionContext {
return '__SYNDICATE__id_' + (this.nextIdNumber++); return '__SYNDICATE__id_' + (this.nextIdNumber++);
} }
get collectedFields(): FacetFields { argDecl(t: TemplateFunction, name: Substitution, type: Substitution): Items {
// Allocates a transient array for collected fields in return (this.typescript) ? t`${name}: ${type}` : t`name`;
// contexts lacking a surrounding collector - that is, for errors.
return this._collectedFields ?? [];
} }
collectField(f: Binder) { turnDecl(t: TemplateFunction): Items {
this.collectedFields.push(f); return this.argDecl(t, 'thisTurn', '__SYNDICATE__.Turn');
}
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;
} }
} }
@ -111,18 +81,6 @@ function stringifyId(i: Identifier): Items {
return [ { ... i, type: TokenType.STRING, text: JSON.stringify(i.text) } ]; 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 { function binderTypeGuard(t: TemplateFunction): (binder: Binder, index: number) => Items {
return (binder, index) => { return (binder, index) => {
if (binder.id.text[0] === '_') { 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 { function terminalWrap(t: TemplateFunction, isTerminal: boolean, body: Statement): Statement {
if (isTerminal) { if (isTerminal) {
return t`thisFacet._stop(function (thisFacet) {${body}})` return t`thisTurn._stop(thisTurn.activeFacet, (${ctx.turnDecl(t)}) => {${body}})`
} else { } else {
return body; 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)))); 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)}`); 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); const maybeWalk = (tree?: Items) : Items | undefined => (tree === void 0) ? tree : walk(tree);
xf(ctx.parser.duringStatement, (s, t) => { xf(ctx.parser.duringStatement, (s, t) => {
let spawn0 = match(ctx.parser.spawn, s.body, null); // TODO: untyped template
if (spawn0 !== null) { const sa = compilePattern(s.pattern);
const spawn = spawn0; return t`assertDataflow((${ctx.turnDecl(t)}) => ({
const id = ctx.quasiRandomId(); target: currentSyndicateTarget,
const instantiated = patternText(instantiatePatternToPattern(s.pattern)); assertion: __SYNDICATE__.fromObserve(__SYNDICATE__.Observe({
return t`on asserted ${patternText(s.pattern)} => { pattern: ${sa.skeleton},
const ${id} = __SYNDICATE__.genUuid(); observer: thisTurn.ref(__SYNDICATE__.assertionFacetObserver(
const ${id}_inst = __SYNDICATE__.Instance(${id}); (${ctx.turnDecl(t)}, ${ctx.argDecl(t, '__vs', '__SYNDICATE__.AnyValue')}) => {
react { if (Array.isArray(__vs)) {
stop on asserted ${id}_inst => react { ${joinItems(sa.captureBinders.map(binderTypeGuard(t)), '\n')}
stop on retracted ${id}_inst; ${walk(s.body)}
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);
}
});`;
}
}); });
xf(ctx.parser.spawn, (s, t) => { xf(ctx.parser.spawn, (s, t) => {
// TODO: parentBinders, parentInits // TODO: parentBinders, parentInits
let body = ctx.withCollectedFields(s.facetFields, () => walk(s.body)); let body = walk(s.body);
let proc = t`function (thisFacet) {${body}}`; /*
if (s.isDataspace) proc = t`__SYNDICATE__.inNestedDataspace(${proc})`;
let assertions = (s.initialAssertions.length > 0) let assertions = (s.initialAssertions.length > 0)
? t`, new __SYNDICATE__.Set([${commaJoin(s.initialAssertions.map(walk))}])` ? 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) => { x(ctx.parser.fieldDeclarationStatement, (s, t) => {
ctx.collectField(s.property); const ft = ctx.typescript ? t`<${s.field.type ?? '__SYNDICATE__.AnyValue'}>` : '';
return t`declareField(this, ${stringifyId(s.property.id)}, ${maybeWalk(s.init) ?? 'void 0'});`; 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) => { xf(ctx.parser.assertionEndpointStatement, (s, t) => {
if (s.test == void 0) { if (s.isDynamic) {
return t`addEndpoint(thisFacet => ({ assertion: ${walk(s.template)}, analysis: null }));`; 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 { } else {
return t`addEndpoint(thisFacet => (${walk(s.test)}) if (s.test == void 0) {
? ({ assertion: ${walk(s.template)}, analysis: null }) return t`assert(currentSyndicateTarget, ${walk(s.template)});`;
: ({ assertion: void 0, analysis: null }), ${''+s.isDynamic});`; } else {
return t`replace(currentSyndicateTarget, void 0, (${walk(s.test)}) ? (${walk(s.template)}) : void 0);`;
}
} }
}); });
xf(ctx.parser.dataflowStatement, (s, t) => 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) => { xf(ctx.parser.eventHandlerEndpointStatement, (s, t) => {
switch (s.triggerType) { if (s.triggerType === 'dataflow') {
case 'dataflow': return t`withSelfDo((${ctx.turnDecl(t)}) => { dataflow { if (${walk(s.predicate)}) { ${terminalWrap(t, s.terminal, walk(s.body))} } } });`;
return t`withSelfDo(function (thisFacet) { dataflow { if (${walk(s.predicate)}) { ${terminalWrap(t, s.terminal, walk(s.body))} } } });`; }
case 'start': if (s.triggerType === 'stop') {
case 'stop': { return t`activeFacet.onStop((${ctx.turnDecl(t)}) => {${walk(s.body)}});`;
const m = s.triggerType === 'start' ? 'addStartScript' : 'addStopScript'; }
return t`${m}(function (thisFacet) {${walk(s.body)}});`;
}
case 'asserted': const sa = compilePattern(s.pattern);
case 'retracted': const guardBody = (body: Statement) => t`if (Array.isArray(__vs)) {
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}) {
${joinItems(sa.captureBinders.map(binderTypeGuard(t)), '\n')} ${joinItems(sa.captureBinders.map(binderTypeGuard(t)), '\n')}
thisFacet.scheduleScript(() => {${terminalWrap(t, s.terminal, walk(s.body))}}); ${body}
} }`;
})
} let entity: Items;
}), ${'' + s.isDynamic});`; 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) => { x(ctx.parser.typeDefinitionStatement, (s, t) => {
const l = `Symbol.for(${JSON.stringify(s.label.text)})`; const l = `Symbol.for(${JSON.stringify(s.label.text)})`;
const fns = JSON.stringify(s.fields.map(f => f.id.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 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});`; 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) => { xf(ctx.parser.reactStatement, (s, t) => {
const body = ctx.withCollectedFields(s.facetFields, () => walk(s.body)); return t`facet((${ctx.turnDecl(t)}) => {${s.body}});`;
const fieldTypeParam = ctx.typescript
? t`<${facetFieldObjectType(t, s.facetFields)}>`
: '';
return t`addChildFacet${fieldTypeParam}(function (thisFacet) {${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) => { x(ctx.parser.bootStatement, (s, t) => {
ctx.hasBootProc = true; ctx.hasBootableBootProc = s.formals.length == 0;
const activationStatements = ctx.activationRecords.map(({ activationScriptId: id }) => const body = t`{${walk(s.body)}}`;
t`thisFacet.activate(${[id]}); `); const bootFormals = commaJoin([ctx.turnDecl(t), ... s.formals.map(
const body = t`${joinItems(activationStatements)}${walk(s)}`; b => ctx.argDecl(t, [b.id], b.type ?? '__SYNDICATE__.AnyValue'))]);
const facetDecl = ctx.typescript ? 'thisFacet: __SYNDICATE__.Facet<{}>' : 'thisFacet';
switch (ctx.moduleType) { switch (ctx.moduleType) {
case 'es6': case 'es6':
return t`export function ${BootProc}(${facetDecl}) {${body}}`; return t`export function boot(${bootFormals}) ${body}`;
case 'require': case 'require':
return t`module.exports.${BootProc} = function (${facetDecl}) {${body}};`; return t`module.exports.boot = (${bootFormals}) => ${body};`;
case 'global': case 'global':
return t`function ${BootProc}(${facetDecl}) {${body}}`; return t`function boot(${bootFormals}) ${body}`;
} }
}); });
xf(ctx.parser.stopStatement, (s, t) => 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; return tree;
} }
@ -369,32 +308,20 @@ export function compile(options: CompileOptions): CompilerOutput {
const ts = macro.template(fixPos(start)); const ts = macro.template(fixPos(start));
const te = macro.template(fixPos(end)); const te = macro.template(fixPos(end));
if (ctx.hasBootProc) { if (ctx.hasBootableBootProc) {
let bp; let bp;
switch (moduleType) { switch (moduleType) {
case 'es6': case 'es6':
case 'global': case 'global':
bp = BootProc; bp = te`boot`;
break; break;
case 'require': case 'require':
bp = te`module.exports.${BootProc}`; bp = te`module.exports.boot`;
break; 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'; const runtime = options.runtime ?? '@syndicate-lang/core';
switch (moduleType) { switch (moduleType) {
@ -413,7 +340,6 @@ export function compile(options: CompileOptions): CompilerOutput {
const cw = new CodeWriter(inputFilename); const cw = new CodeWriter(inputFilename);
cw.emit(tree); cw.emit(tree);
const text = cw.text; const text = cw.text;
return { return {

View File

@ -2,17 +2,15 @@
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com> /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { import {
TokenType, Token, Items, Token, Items,
Pattern, Pattern,
foldItems, match, anonymousTemplate as template, commaJoin, 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, repeat, option, withoutSpace, map, mapm, rest, discard,
value, succeed, fail, separatedBy, anything, not, follows, value, succeed, fail, separatedBy, anything, not
} from '../syntax/index.js'; } from '../syntax/index.js';
import * as Matcher from '../syntax/matcher.js'; import * as Matcher from '../syntax/matcher.js';
import { Path, Skeleton } from './internals.js';
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// AST types // AST types
@ -23,41 +21,37 @@ export type Identifier = Token;
export type Type = Items; export type Type = Items;
export type Binder = { id: Identifier, type?: Type }; export type Binder = { id: Identifier, type?: Type };
export interface FacetAction { export interface TurnAction {
implicitFacet: boolean; implicitTurn: boolean;
} }
export type FacetFields = Binder[]; export interface FacetSetupAction extends TurnAction {
export interface FacetProducingAction extends FacetAction {
body: Statement; body: Statement;
facetFields: FacetFields;
} }
export interface SpawnStatement extends FacetProducingAction { export interface SpawnStatement extends FacetSetupAction {
isDataspace: boolean;
name?: Expr; name?: Expr;
initialAssertions: Expr[]; isLink: boolean;
parentBinders: Binder[]; parentBinders: Binder[];
parentInits: Expr[]; parentInits: Expr[];
} }
export interface FieldDeclarationStatement extends FacetAction { export interface FieldDeclarationStatement extends TurnAction {
property: Binder; field: Binder;
init?: Expr; init?: Expr;
} }
export interface AssertionEndpointStatement extends FacetAction { export interface AssertionEndpointStatement extends TurnAction {
isDynamic: boolean, isDynamic: boolean,
template: Expr, template: Expr,
test?: Expr, test?: Expr,
} }
export interface StatementFacetAction extends FacetAction { export interface StatementTurnAction extends TurnAction {
body: Statement; body: Statement;
} }
export interface GenericEventEndpointStatement extends StatementFacetAction { export interface GenericEventEndpointStatement extends StatementTurnAction {
terminal: boolean; terminal: boolean;
isDynamic: boolean; isDynamic: boolean;
} }
@ -68,7 +62,7 @@ export interface DataflowEndpointStatement extends GenericEventEndpointStatement
} }
export interface PseudoEventEndpointStatement extends GenericEventEndpointStatement { export interface PseudoEventEndpointStatement extends GenericEventEndpointStatement {
triggerType: 'start' | 'stop'; triggerType: 'stop';
} }
export interface AssertionEventEndpointStatement extends GenericEventEndpointStatement { export interface AssertionEventEndpointStatement extends GenericEventEndpointStatement {
@ -86,20 +80,29 @@ export interface TypeDefinitionStatement {
wireName?: Expr; wireName?: Expr;
} }
export interface MessageSendStatement extends FacetAction { export interface MessageSendStatement extends TurnAction {
expr: Expr; expr: Expr;
} }
export interface DuringStatement extends FacetProducingAction { export interface DuringStatement extends FacetSetupAction {
pattern: ValuePattern; pattern: ValuePattern;
} }
export interface ReactStatement extends FacetProducingAction { export interface ReactStatement extends FacetSetupAction {
} }
export interface ActivationImport { export interface BootStatement {
activationKeyword: Identifier; formals: Binder[];
target: { type: 'import', moduleName: Token } | { type: 'expr', moduleExpr: Expr }; body: Statement;
}
export interface AtStatement {
target: Expr;
body: Statement;
}
export interface CreateExpression {
entity: Expr;
} }
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -115,23 +118,28 @@ export interface PDiscard {
type: 'PDiscard', type: 'PDiscard',
} }
export interface PConstructor {
type: 'PConstructor',
ctor: Expr,
arguments: ValuePattern[],
}
export interface PConstant { export interface PConstant {
type: 'PConstant', type: 'PConstant',
value: Expr, value: Expr,
} }
export interface PRecord {
type: 'PRecord',
ctor: Expr,
arguments: ValuePattern[],
}
export interface PArray { export interface PArray {
type: 'PArray', type: 'PArray',
elements: ValuePattern[], 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 { interface RawCall {
items: Items; items: Items;
@ -140,12 +148,8 @@ interface RawCall {
} }
export interface StaticAnalysis { export interface StaticAnalysis {
skeleton: Expr; skeleton: Expr; // constructs a P.Pattern
constPaths: Path[];
constVals: Expr[];
capturePaths: Path[];
captureBinders: Binder[]; captureBinders: Binder[];
assertion: Expr;
} }
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -180,11 +184,11 @@ export class SyndicateParser {
i => i ? acc.push(i) : void 0)))); 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 => { return i => {
const scope = Object.create(null); const scope = Object.create(null);
scope.implicitFacet = true; scope.implicitTurn = true;
const p = seq(option(map(atom('.'), _ => scope.implicitFacet = false)), pattern(scope)); const p = seq(option(map(atom('.'), _ => scope.implicitTurn = false)), pattern(scope));
const r = p(i); const r = p(i);
if (r === null) return null; if (r === null) return null;
return [scope, r[1]]; return [scope, r[1]];
@ -193,45 +197,43 @@ export class SyndicateParser {
readonly headerExpr = this.expr(kw('asserting'), kw('let')); readonly headerExpr = this.expr(kw('asserting'), kw('let'));
// Principal: Facet // Principal: Turn
readonly spawn: Pattern<SpawnStatement> = readonly spawn: Pattern<SpawnStatement> =
this.facetAction(o => { this.turnAction(o => {
o.isDataspace = false; o.isLink = false;
o.initialAssertions = [];
o.parentBinders = []; o.parentBinders = [];
o.parentInits = []; o.parentInits = [];
o.body = []; o.body = [];
o.facetFields = [];
return seq(atom('spawn'), return seq(atom('spawn'),
option(seq(atom('dataspace'), exec(() => o.isDataspace = true))), option(map(atom('linked'), _ => o.isLink = true)),
option(seq(atom('named'), option(seq(atom('named'), bind(o, 'name', this.headerExpr))),
bind(o, 'name', this.headerExpr))), repeat(alt(
repeat(alt(seq(kw('asserting'), /* seq(kw('asserting'), map(this.headerExpr, e => o.initialAssertions.push(e))), */
map(this.headerExpr, e => o.initialAssertions.push(e))), map(scope(
map(scope((l: { b: Binder, init: Expr }) => (l: { b: Binder, init: Expr }) =>
seq(kw('let'), seq(kw('let'),
bind(l, 'b', this.binder), bind(l, 'b', this.binder),
atom('='), atom('='),
bind(l, 'init', this.headerExpr))), bind(l, 'init', this.headerExpr))),
l => { l => {
o.parentBinders.push(l.b); o.parentBinders.push(l.b);
o.parentInits.push(l.init); o.parentInits.push(l.init);
}))), }))),
this.block(o.body)); this.block(o.body));
}); });
// Principal: Dataspace, but only for implementation reasons, so really Facet // Principal: Turn
readonly fieldDeclarationStatement: Pattern<FieldDeclarationStatement> = readonly fieldDeclarationStatement: Pattern<FieldDeclarationStatement> =
this.facetAction(o => { this.turnAction(o => {
return seq(atom('field'), return seq(atom('field'),
bind(o, 'property', this.binder), bind(o, 'field', this.binder),
option(seq(atom('='), bind(o, 'init', this.expr()))), option(seq(atom('='), bind(o, 'init', this.expr()))),
this.statementBoundary); this.statementBoundary);
}); });
// Principal: Facet // Principal: Turn
readonly assertionEndpointStatement: Pattern<AssertionEndpointStatement> = readonly assertionEndpointStatement: Pattern<AssertionEndpointStatement> =
this.facetAction(o => { this.turnAction(o => {
o.isDynamic = true; o.isDynamic = true;
return seq(atom('assert'), return seq(atom('assert'),
option(map(kw('snapshot'), _ => o.isDynamic = false)), option(map(kw('snapshot'), _ => o.isDynamic = false)),
@ -240,15 +242,15 @@ export class SyndicateParser {
this.statementBoundary); this.statementBoundary);
}); });
blockFacetAction(kw: Pattern<any>): Pattern<StatementFacetAction> { blockTurnAction(kw: Pattern<any>): Pattern<StatementTurnAction> {
return this.facetAction(o => { return this.turnAction(o => {
o.body = []; o.body = [];
return seq(kw, this.block(o.body)); return seq(kw, this.block(o.body));
}); });
} }
// Principal: Facet // Principal: Turn
readonly dataflowStatement = this.blockFacetAction(atom('dataflow')); readonly dataflowStatement = this.blockTurnAction(atom('dataflow'));
mandatoryIfNotTerminal(o: GenericEventEndpointStatement, p: Pattern<any>): Pattern<any> { mandatoryIfNotTerminal(o: GenericEventEndpointStatement, p: Pattern<any>): Pattern<any> {
return i => { return i => {
@ -256,9 +258,9 @@ export class SyndicateParser {
}; };
} }
// Principal: Facet // Principal: Turn
readonly eventHandlerEndpointStatement: Pattern<EventHandlerEndpointStatement> = readonly eventHandlerEndpointStatement: Pattern<EventHandlerEndpointStatement> =
this.facetAction(o => { this.turnAction(o => {
o.terminal = false; o.terminal = false;
o.isDynamic = true; o.isDynamic = true;
o.body = []; o.body = [];
@ -268,8 +270,7 @@ export class SyndicateParser {
this.expr())), this.expr())),
_ => o.triggerType = 'dataflow'), _ => o.triggerType = 'dataflow'),
this.mandatoryIfNotTerminal(o, this.statement(o.body))), this.mandatoryIfNotTerminal(o, this.statement(o.body))),
mapm(seq(bind(o, 'triggerType', mapm(seq(bind(o, 'triggerType', atomString('stop')),
alt(atomString('start'), atomString('stop'))),
option(this.statement(o.body))), option(this.statement(o.body))),
v => o.terminal ? fail : succeed(v)), v => o.terminal ? fail : succeed(v)),
seq(bind(o, 'triggerType', seq(bind(o, 'triggerType',
@ -288,67 +289,77 @@ export class SyndicateParser {
scope(o => seq(bind(o, 'expectedUse', alt(atomString('message'), atomString('assertion'))), scope(o => seq(bind(o, 'expectedUse', alt(atomString('message'), atomString('assertion'))),
atom('type'), atom('type'),
bind(o, 'label', this.identifier), bind(o, 'label', this.identifier),
group('(', bind(o, 'fields', group('(', bind(o, 'fields', repeat(this.binder, { separator: atom(',') }))),
repeat(this.binder, { separator: atom(',') }))),
option(seq(atom('='), option(seq(atom('='),
bind(o, 'wireName', withoutSpace(upTo(this.statementBoundary))))), bind(o, 'wireName', withoutSpace(upTo(this.statementBoundary))))),
this.statementBoundary)); this.statementBoundary));
// Principal: Facet // Principal: Turn
readonly messageSendStatement: Pattern<MessageSendStatement> = readonly messageSendStatement: Pattern<MessageSendStatement> =
this.facetAction(o => seq(atom('send'), this.turnAction(o => seq(atom('send'),
atom('message'), atom('message'),
not(this.statementBoundary), not(this.statementBoundary),
bind(o, 'expr', withoutSpace(upTo(this.statementBoundary))), bind(o, 'expr', withoutSpace(upTo(this.statementBoundary))),
this.statementBoundary)); this.statementBoundary));
// Principal: Facet // Principal: Turn
readonly duringStatement: Pattern<DuringStatement> = readonly duringStatement: Pattern<DuringStatement> =
this.facetAction(o => { this.turnAction(o => {
o.body = []; o.body = [];
o.facetFields = [];
return seq(atom('during'), return seq(atom('during'),
bind(o, 'pattern', this.valuePattern(atom('=>'))), bind(o, 'pattern', this.valuePattern(atom('=>'))),
seq(atom('=>'), this.statement(o.body))); seq(atom('=>'), this.statement(o.body)));
}); });
// Principal: Facet // Principal: Turn
readonly reactStatement: Pattern<ReactStatement> = readonly reactStatement: Pattern<ReactStatement> =
this.facetAction(o => { this.turnAction(o => {
o.body = []; o.body = [];
o.facetFields = [];
return seq(atom('react'), this.block(o.body)); return seq(atom('react'), this.block(o.body));
}); });
// Principal: none // Principal: none
readonly bootStatement: Pattern<Statement> = readonly bootStatement: Pattern<BootStatement> =
value(o => { scope(o => {
o.value = []; o.body = [];
return seq(atom('boot'), this.block(o.value)); return seq(
atom('boot'),
group('(', bind(o, 'formals', repeat(this.binder, { separator: atom(',') }))),
this.block(o.body));
}); });
// Principal: Facet // Principal: Turn
readonly stopStatement = this.blockFacetAction(atom('stop')); readonly stopStatement = this.blockTurnAction(atom('stop'));
// Principal: none // Principal: none
readonly activationImport: Pattern<ActivationImport> = readonly atStatement: Pattern<AtStatement> =
scope(o => seq(bind(o, 'activationKeyword', atom('activate')), scope(o => {
follows(alt<any>(seq(atom('import'), o.body = [];
upTo(seq( return seq(atom('at'),
map(atom(void 0, { tokenType: TokenType.STRING }), bind(o, 'target', this.expr()),
n => o.target = { this.statement(o.body));
type: 'import', });
moduleName: n
}), // Principal: none
this.statementBoundary))), readonly createExpression: Pattern<CreateExpression> =
map(this.expr(), e => o.target = { scope(o => seq(atom('create'), bind(o, 'entity', this.expr())));
type: 'expr',
moduleExpr: e
})))));
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// Syntax of patterns over Value, used in endpoints // 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> = readonly pCaptureBinder: Pattern<Binder> =
mapm(this.binder, i => { mapm(this.binder, i => {
return i.id.text.startsWith('$') return i.id.text.startsWith('$')
@ -366,16 +377,28 @@ export class SyndicateParser {
bs => bs.some(b => b)); bs => bs.some(b => b));
} }
// $id - capture of discard pArray: Pattern<PArray> =
// _ - discard scope(o => {
// o.type = 'PArray';
// expr(pat, ...) - record ctor return group(
// $id(pat) - nested capture '[', mapm(bind(o, 'elements', separatedBy(this.valuePattern(), atom(','))),
// [pat, ...] - array pat v => (o.elements.every(p => p.type === 'PConstant') ? fail : succeed(v))));
// });
// expr(expr, ...) - constant
// [expr, ...] - constant pDict: Pattern<PDict> =
// other - constant 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> { pRawCall(... extraStops: Pattern<any>[]): Pattern<RawCall> {
return scope((o: RawCall) => return scope((o: RawCall) =>
@ -401,6 +424,8 @@ export class SyndicateParser {
return bind(o, 'binder', this.pCaptureBinder); return bind(o, 'binder', this.pCaptureBinder);
}), }),
scope(o => map(this.pDiscard, _ => o.type = 'PDiscard')), scope(o => map(this.pDiscard, _ => o.type = 'PDiscard')),
this.pArray,
this.pDict,
mapm<RawCall, ValuePattern>( mapm<RawCall, ValuePattern>(
this.pRawCall(... extraStops), this.pRawCall(... extraStops),
o => { o => {
@ -424,7 +449,7 @@ export class SyndicateParser {
const argPats = o.arguments.map(a => match(this.valuePattern(), a, null)); const argPats = o.arguments.map(a => match(this.valuePattern(), a, null));
if (argPats.some(p => p === null)) return fail; if (argPats.some(p => p === null)) return fail;
return succeed({ return succeed({
type: 'PConstructor', type: 'PRecord',
ctor: o.callee, ctor: o.callee,
arguments: argPats as ValuePattern[] arguments: argPats as ValuePattern[]
}); });
@ -446,125 +471,43 @@ export class SyndicateTypedParser extends SyndicateParser {
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// Value pattern utilities // Value pattern utilities
export function patternText(p: ValuePattern): Items { const eDiscard: Expr = template`(__SYNDICATE__.Pattern._)`;
switch (p.type) { const eBind = (e: Expr): Expr => template`(__SYNDICATE__.Pattern.bind(${e}))`;
case 'PDiscard': return template`_`; const eLit = (e : Expr): Expr => template`(__SYNDICATE__.Pattern.lit(${e}))`;
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}))`;
export function compilePattern(pattern: ValuePattern): StaticAnalysis { export function compilePattern(pattern: ValuePattern): StaticAnalysis {
const constPaths: Path[] = [];
const constVals: Expr[] = [];
const capturePaths: Path[] = [];
const captureBinders: Binder[] = []; const captureBinders: Binder[] = [];
const currentPath: Path = []; function walk(pattern: ValuePattern): Expr {
function walk(pattern: ValuePattern): [Skeleton<Expr>, Expr] {
switch (pattern.type) { switch (pattern.type) {
case 'PDiscard': case 'PDiscard':
return [null, eDiscard]; return eDiscard;
case 'PCapture': { case 'PCapture': {
capturePaths.push(currentPath.slice());
captureBinders.push(pattern.binder); captureBinders.push(pattern.binder);
const [s, a] = walk(pattern.inner); return eBind(walk(pattern.inner));
return [s, eCapture(a)];
} }
case 'PConstant': case 'PConstant':
constPaths.push(currentPath.slice()); return eLit(pattern.value);
constVals.push(pattern.value); case 'PRecord': {
return [null, pattern.value]; const pieces = [template`(${pattern.ctor}).constructorInfo.label`,
case 'PConstructor': { ... pattern.arguments.map(walk)];
const skel: Skeleton<Expr> = { return template`(__SYNDICATE__.Pattern.rec(${commaJoin(pieces)}))`;
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)}))`];
} }
case 'PArray': { case 'PArray': {
const skel: Skeleton<Expr> = { const pieces = pattern.elements.map(walk);
shape: [ { return template`(__SYNDICATE__.Pattern.arr(${commaJoin(pieces)}))`;
start: startPos(null), }
end: startPos(null), case 'PDict': {
type: TokenType.STRING, const pieces = pattern.elements.map(([k, v]) => template`[${k}, ${walk(v)}]`);
text: JSON.stringify(pattern.elements.length.toString()), return template`(__SYNDICATE__.Pattern.dict(${commaJoin(pieces)}))`;
} ],
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 [skeletonStructure, assertion] = walk(pattern); const skeleton = walk(pattern);
const skeleton = renderSkeleton(skeletonStructure);
return { return {
skeleton, skeleton,
constPaths,
constVals,
capturePaths,
captureBinders, 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))}]})`;
}
}

View File

@ -2,6 +2,5 @@
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com> /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
export * as Grammar from './grammar.js'; export * as Grammar from './grammar.js';
export * as Internals from './internals.js';
export * as Codegen from './codegen.js'; export * as Codegen from './codegen.js';
export { compile, CompileOptions } from './codegen.js'; export { compile, CompileOptions } from './codegen.js';

View File

@ -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>;

View File

@ -4,7 +4,7 @@
import { import {
Pattern as P, Pattern as P,
Observe, fromObserve, assertObserve,
Record, Record,
Actor, Dataspace, Actor, Dataspace,
} from '..'; } from '..';
@ -41,38 +41,29 @@ Actor.boot(t => {
} }
}); });
t.assert(ds, fromObserve(Observe({ assertObserve(t, ds, P.rec(SetBox.constructorInfo.label, P.bind()), {
pattern: P.rec(SetBox.constructorInfo.label, P.bind()), message(_t, [v]) {
observer: t.ref({ boxValue.value = v;
message(_t, [v]) { // console.log('box updated value', v);
boxValue.value = v; }
// console.log('box updated value', v); });
}
})
})));
}); });
t.spawn(t => { t.spawn(t => {
t.activeFacet.actor.name = 'client'; t.activeFacet.actor.name = 'client';
t.assert(ds, fromObserve(Observe({ assertObserve(t, ds, P.rec(BoxState.constructorInfo.label, P.bind()), {
pattern: P.rec(BoxState.constructorInfo.label, P.bind()), assert(t, [v], _handle) {
observer: t.ref({ // console.log('client sending SetBox', v + 1);
assert(t, [v], _handle) { t.message(ds, SetBox(v + 1));
// console.log('client sending SetBox', v + 1); }
t.message(ds, SetBox(v + 1)); });
}
})
})));
t.assert(ds, fromObserve(Observe({ assertObserve(t, ds, P.rec(BoxState.constructorInfo.label, P._), {
pattern: P.rec(BoxState.constructorInfo.label, P._), retract(_t) {
observer: t.ref({ console.log('box gone');
retract(_t) { console.timeEnd('box-and-client-' + N.toString());
console.log('box gone'); }
console.timeEnd('box-and-client-' + N.toString()); });
}
})
})));
}); });
}); });

View File

@ -4,7 +4,7 @@
import { import {
Pattern as P, Pattern as P,
Observe, fromObserve, assertObserve,
Record, Record,
Actor, Dataspace, Actor, Dataspace,
} from '..'; } from '..';
@ -41,38 +41,29 @@ Actor.boot(t => {
} }
}); });
t.assert(ds, fromObserve(Observe({ assertObserve(t, ds, P.rec(SetBox.constructorInfo.label, P.bind()), {
pattern: P.rec(SetBox.constructorInfo.label, P.bind()), message(_t, [v]: [number]) {
observer: t.ref({ boxValue.value = v;
message(_t, [v]: [number]) { // console.log('box updated value', v);
boxValue.value = v; }
// console.log('box updated value', v); });
}
})
})));
}); });
t.spawn(t => { t.spawn(t => {
t.activeFacet.actor.name = 'client'; t.activeFacet.actor.name = 'client';
t.assert(ds, fromObserve(Observe({ assertObserve(t, ds, P.rec(BoxState.constructorInfo.label, P.bind()), {
pattern: P.rec(BoxState.constructorInfo.label, P.bind()), assert(t, [v]: [number], _handle) {
observer: t.ref({ // console.log('client sending SetBox', v + 1);
assert(t, [v]: [number], _handle) { t.message(ds, SetBox(v + 1));
// console.log('client sending SetBox', v + 1); }
t.message(ds, SetBox(v + 1)); });
}
})
})));
t.assert(ds, fromObserve(Observe({ assertObserve(t, ds, P.rec(BoxState.constructorInfo.label, P._), {
pattern: P.rec(BoxState.constructorInfo.label, P._), retract(_t) {
observer: t.ref({ console.log('box gone');
retract(_t) { console.timeEnd('box-and-client-' + N.toString());
console.log('box gone'); }
console.timeEnd('box-and-client-' + N.toString()); });
}
})
})));
}); });
}); });

View File

@ -129,6 +129,10 @@ export class Facet {
this.outbound = initialAssertions; this.outbound = initialAssertions;
} }
turn(a: LocalAction) {
Turn.for(this, a);
}
onStop(a: LocalAction): void { onStop(a: LocalAction): void {
this.shutdownActions.push(a); this.shutdownActions.push(a);
} }
@ -251,6 +255,11 @@ export class Turn {
return newFacet; 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) { stop(facet: Facet = this.activeFacet, continuation?: LocalAction) {
this.enqueue(facet.parent!, t => { this.enqueue(facet.parent!, t => {
facet._terminate(t, true); facet._terminate(t, true);
@ -258,11 +267,16 @@ export class Turn {
}); });
} }
spawn(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): void { // Alias for syndicatec code generator to use
this._spawn(bootProc, initialAssertions); _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(); const newOutbound: OutboundMap = new Map();
initialAssertions.forEach(key => newOutbound.set(key, this.activeFacet.outbound.get(key)!)); initialAssertions.forEach(key => newOutbound.set(key, this.activeFacet.outbound.get(key)!));
// ^ we trust initialAssertions, so can use `!` safely // ^ we trust initialAssertions, so can use `!` safely
@ -275,9 +289,14 @@ export class Turn {
return newActor; 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 { spawnLink(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): void {
if (!this.activeFacet.isLive) return; if (!this.activeFacet.isLive) return;
const newActor = this._spawn(bootProc, initialAssertions); const newActor = this.__spawn(bootProc, initialAssertions);
this.activeFacet._halfLink(newActor.root); this.activeFacet._halfLink(newActor.root);
newActor.root._halfLink(this.activeFacet); newActor.root._halfLink(this.activeFacet);
} }
@ -294,6 +313,11 @@ export class Turn {
return new Field(this.activeFacet.actor.dataflowGraph, initial, name); 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) { dataflow(a: LocalAction) {
const f = this.activeFacet; const f = this.activeFacet;
const b = (t: Turn) => f.isLive && t._inFacet(f, a); 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))))); queueTask(() => Turn.for(actor.root, t => q.forEach(f => f(t)))));
this.queues = null; this.queues = null;
} }
withSelfDo(a: LocalAction) {
a(this);
}
} }
function stopIfInertAfter(a: LocalAction): LocalAction { function stopIfInertAfter(a: LocalAction): LocalAction {

View File

@ -4,7 +4,7 @@
// Property-based "dataflow" // Property-based "dataflow"
import { FlexSet, FlexMap, Canonicalizer, Value, is } from '@preserves/core'; 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'; import * as MapSet from './mapset.js';
export interface ObservingGraph<ObjectId> { export interface ObservingGraph<ObjectId> {

View File

@ -3,8 +3,9 @@
import { IdentityMap } from '@preserves/core'; import { IdentityMap } from '@preserves/core';
import { Index } from './skeleton.js'; import { Index } from './skeleton.js';
import { Assertion, Entity, Handle, Turn } from './actor.js'; import { Assertion, Entity, Facet, Handle, LocalAction, Ref, Turn } from './actor.js';
import { Observe, toObserve } from '../gen/dataspace.js'; import { fromObserve, Observe, toObserve } from '../gen/dataspace.js';
import * as P from '../gen/dataspacePatterns.js';
export class Dataspace implements Partial<Entity> { export class Dataspace implements Partial<Entity> {
readonly index = new Index(); readonly index = new Index();
@ -33,3 +34,40 @@ export class Dataspace implements Partial<Entity> {
this.index.deliverMessage(turn, v); 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) })));
}

View File

@ -8,6 +8,6 @@
<main id="main"> <main id="main">
</main> </main>
<script> <script>
Syndicate.bootModule(Main.__SYNDICATE__bootProc); Main.boot();
</script> </script>
</html> </html>

View File

@ -16,7 +16,7 @@
}, },
"devDependencies": { "devDependencies": {
"@syndicate-lang/syndicatec": "^0.2.0", "@syndicate-lang/syndicatec": "^0.2.0",
"rollup": "^2.37.0", "rollup": "^2.60",
"rollup-plugin-sourcemaps": "^0.6.3" "rollup-plugin-sourcemaps": "^0.6"
} }
} }

View File

@ -3,12 +3,15 @@
import { BoxState, SetBox, N } from './protocol.js'; import { BoxState, SetBox, N } from './protocol.js';
boot { boot(ds) {
spawn named 'box' { spawn named 'box' {
field value = 0; field boxValue = 0;
assert BoxState(this.value); at ds {
stop on (this.value === N) assert BoxState(boxValue.value);
on message SetBox($v) => boxValue.value = v;
}
stop on (boxValue.value === N) {
console.log('terminated box root facet'); console.log('terminated box root facet');
on message SetBox($v) => this.value = v; }
} }
} }

View File

@ -3,9 +3,14 @@
import { BoxState, SetBox } from './protocol.js'; import { BoxState, SetBox } from './protocol.js';
boot { boot(ds, doneCallback) {
spawn named 'client' { spawn named 'client' {
on asserted BoxState($v) => send message SetBox(v + 1); at ds {
on retracted BoxState(_) => console.log('box gone'); on asserted BoxState($v) => send message SetBox(v + 1);
on retracted BoxState(_) => {
console.log('box gone');
doneCallback();
}
}
} }
} }

View File

@ -2,11 +2,14 @@
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com> /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { N } from './protocol.js'; import { N } from './protocol.js';
activate import './box.js'; import * as Box from './box.js';
activate import './client.js'; import * as Client from './client.js';
import { Dataspace } from '@syndicate-lang/core';
console.time('box-and-client-' + N.toString()); console.time('box-and-client-' + N.toString());
boot { boot() {
thisFacet.actor.dataspace.ground().addStopHandler(() => thisTurn.activeFacet.preventInertCheck();
console.timeEnd('box-and-client-' + N.toString())); const ds = create new Dataspace();
Box.boot(ds);
Client.boot(ds, () => console.timeEnd('box-and-client-' + N.toString()));
} }

View File

@ -8,6 +8,6 @@
<main id="main"> <main id="main">
</main> </main>
<script> <script>
Syndicate.bootModule(Main.__SYNDICATE__bootProc); Syndicate.Actor.boot(Main.boot);
</script> </script>
</html> </html>

View File

@ -16,8 +16,8 @@
}, },
"devDependencies": { "devDependencies": {
"@syndicate-lang/syndicatec": "^0.2.0", "@syndicate-lang/syndicatec": "^0.2.0",
"rollup": "^2.37.0", "rollup": "^2.60",
"rollup-plugin-sourcemaps": "^0.6.3", "rollup-plugin-sourcemaps": "^0.6",
"typescript": "^4.1.3" "typescript": "^4.5"
} }
} }

View File

@ -2,13 +2,17 @@
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com> /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { BoxState, SetBox, N } from './protocol.js'; import { BoxState, SetBox, N } from './protocol.js';
import { Ref } from '@syndicate-lang/core';
boot { boot(ds: Ref) {
spawn named 'box' { spawn named 'box' {
field value: number = 0; field boxValue: number = 0;
assert BoxState(this.value); at ds {
stop on (this.value === N) assert BoxState(boxValue.value);
on message SetBox($v: number) => boxValue.value = v;
}
stop on (boxValue.value === N) {
console.log('terminated box root facet'); console.log('terminated box root facet');
on message SetBox($v: number) => this.value = v; }
} }
} }

View File

@ -2,10 +2,16 @@
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com> /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { BoxState, SetBox } from './protocol.js'; import { BoxState, SetBox } from './protocol.js';
import { Ref } from '@syndicate-lang/core';
boot { boot(ds: Ref, doneCallback: () => void) {
spawn named 'client' { spawn named 'client' {
on asserted BoxState($v: number) => send message SetBox(v + 1); at ds {
on retracted BoxState(_) => console.log('box gone'); on asserted BoxState($v: number) => send message SetBox(v + 1);
on retracted BoxState(_) => {
console.log('box gone');
doneCallback();
}
}
} }
} }

View File

@ -2,11 +2,14 @@
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com> /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { N } from './protocol.js'; import { N } from './protocol.js';
activate import './box.js'; import * as Box from './box.js';
activate import './client.js'; import * as Client from './client.js';
import { Dataspace } from '@syndicate-lang/core';
console.time('box-and-client-' + N.toString()); console.time('box-and-client-' + N.toString());
boot { boot() {
thisFacet.actor.dataspace.ground().addStopHandler(() => thisTurn.activeFacet.preventInertCheck();
console.timeEnd('box-and-client-' + N.toString())); const ds = create new Dataspace();
Box.boot(thisTurn, ds);
Client.boot(thisTurn, ds, () => console.timeEnd('box-and-client-' + N.toString()));
} }

View File

@ -1,7 +1,7 @@
/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com> /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
export assertion type BoxState(value); export assertion type BoxState(value: number);
export message type SetBox(newValue); export message type SetBox(newValue: number);
export const N = 100000; export const N = 100000;