First stab at cross-dataspace relaying; does not yet handle adhoc assertions
This commit is contained in:
parent
066920b771
commit
72e1653aa6
|
@ -23,6 +23,7 @@ const RandomID = require('./randomid.js');
|
|||
const Dataspace = require('./dataspace.js');
|
||||
const Ground = require('./ground.js');
|
||||
const Assertions = require('./assertions.js');
|
||||
const Relay = require('./relay.js');
|
||||
|
||||
module.exports.Immutable = require('immutable');
|
||||
// ^ for use by import machinery in syntactic extensions
|
||||
|
@ -46,6 +47,10 @@ module.exports.Inbound = Assertions.Inbound;
|
|||
module.exports.Outbound = Assertions.Outbound;
|
||||
module.exports.Instance = Assertions.Instance;
|
||||
|
||||
module.exports.$QuitDataspace = Relay.$QuitDataspace;
|
||||
module.exports.NestedDataspace = Relay.NestedDataspace;
|
||||
module.exports.inNestedDataspace = Relay.inNestedDataspace;
|
||||
|
||||
module.exports.bootModule = Ground.bootModule;
|
||||
|
||||
// These aren't so much "Universal" as they are "VM-wide-unique".
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
"use strict";
|
||||
//---------------------------------------------------------------------------
|
||||
// @syndicate-lang/core, an implementation of Syndicate dataspaces for JS.
|
||||
// Copyright (C) 2016-2018 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
const $Special = require('./special.js');
|
||||
|
||||
const _Dataspace = require('./dataspace.js');
|
||||
const Dataspace = _Dataspace.Dataspace;
|
||||
|
||||
const Assertions = require('./assertions.js');
|
||||
const Observe = Assertions.Observe;
|
||||
const Inbound = Assertions.Inbound;
|
||||
const Outbound = Assertions.Outbound;
|
||||
|
||||
const $QuitDataspace = new $Special("quit-dataspace");
|
||||
|
||||
// TODO: container --> metaContainer == ground
|
||||
// TODO: parent links
|
||||
// so there's a path up the tree at all times, and also an easy way to get to ground
|
||||
|
||||
function NestedDataspace(outerFacet, container, bootProc) {
|
||||
Dataspace.call(this, container, bootProc);
|
||||
this.outerFacet = outerFacet;
|
||||
}
|
||||
NestedDataspace.prototype = new Dataspace(null, null);
|
||||
|
||||
NestedDataspace.prototype.sendMessage = function (m) {
|
||||
Dataspace.prototype.sendMessage.call(this, m);
|
||||
if (m === $QuitDataspace) {
|
||||
this.outerFacet.stop();
|
||||
}
|
||||
};
|
||||
|
||||
NestedDataspace.prototype.endpointHook = function (facet, ep) {
|
||||
Dataspace.prototype.endpointHook.call(this, facet, ep);
|
||||
if (Observe.isClassOf(ep.assertion) && Inbound.isClassOf(ep.assertion[0])) {
|
||||
this.installInboundRelay(facet, ep);
|
||||
} else if (Outbound.isClassOf(ep.assertion)) {
|
||||
this.installOutboundRelay(facet, ep);
|
||||
}
|
||||
};
|
||||
|
||||
NestedDataspace.prototype.installInboundRelay = function (facet, innerEp) {
|
||||
// We know that innerEp.assertion is an Observe(Inbound(...)).
|
||||
// Also, if innerEp.handler exists, it will be consonant with innerEp.assertion.
|
||||
this.hookEndpointLifecycle(innerEp, this.outerFacet.addEndpoint(() => {
|
||||
return [Observe(innerEp.assertion[0][0]),
|
||||
innerEp.handler && {
|
||||
skeleton: innerEp.handler.skeleton[1],
|
||||
constPaths: innerEp.handler.constPaths.map((p) => p.shift()),
|
||||
constVals: innerEp.handler.constVals,
|
||||
capturePaths: innerEp.handler.capturePaths.map((p) => p.shift()),
|
||||
callback: innerEp.handler.callback
|
||||
}];
|
||||
}, false));
|
||||
};
|
||||
|
||||
NestedDataspace.prototype.installOutboundRelay = function (facet, innerEp) {
|
||||
// We know that innerEp.assertion is an Outbound(...).
|
||||
// We may also then conclude that there is no point in installing a handler.
|
||||
this.hookEndpointLifecycle(innerEp, this.outerFacet.addEndpoint(() => {
|
||||
return [innerEp.assertion[0], null];
|
||||
}, false));
|
||||
};
|
||||
|
||||
NestedDataspace.prototype.hookEndpointLifecycle = function (innerEp, outerEp) {
|
||||
const outerFacet = this.outerFacet;
|
||||
|
||||
const _refresh = innerEp.refresh;
|
||||
innerEp.refresh = function (ds, ac, facet) {
|
||||
_refresh.call(this, ds, ac, facet);
|
||||
outerEp.refresh(outerFacet.actor.dataspace, outerFacet.actor, outerFacet);
|
||||
};
|
||||
|
||||
const _destroy = innerEp.destroy;
|
||||
innerEp.destroy = function (ds, ac, facet, emitPatches) {
|
||||
_destroy.call(this, ds, ac, facet, emitPatches);
|
||||
outerEp.destroy(outerFacet.actor.dataspace, outerFacet.actor, outerFacet, true);
|
||||
};
|
||||
};
|
||||
|
||||
NestedDataspace.prototype.start = function () {
|
||||
this.outerFacet.actor.pushScript(() => {
|
||||
Dataspace.withCurrentFacet(this.outerFacet, () => {
|
||||
if (this.outerFacet.isLive) {
|
||||
Dataspace.deferTurn(() => {
|
||||
const stillBusy = this.runScripts();
|
||||
if (stillBusy) this.start();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function inNestedDataspace(bootProc) {
|
||||
return () => {
|
||||
const outerFacet = Dataspace.currentFacet();
|
||||
outerFacet.addDataflow(function () {});
|
||||
// ^ eww! Dummy endpoint to keep the root facet of the relay alive.
|
||||
const innerDs = new NestedDataspace(outerFacet, outerFacet.actor.dataspace.container, bootProc);
|
||||
outerFacet.actor.scheduleScript(() => innerDs.start());
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.$QuitDataspace = $QuitDataspace;
|
||||
module.exports.NestedDataspace = NestedDataspace;
|
||||
module.exports.inNestedDataspace = inNestedDataspace;
|
|
@ -0,0 +1,29 @@
|
|||
//---------------------------------------------------------------------------
|
||||
// @syndicate-lang/syntax-test, a demo of Syndicate extensions to JS.
|
||||
// Copyright (C) 2016-2018 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
import { Dataspace, inNestedDataspace, Outbound, Inbound } from '@syndicate-lang/core';
|
||||
|
||||
assertion type Greeting(text);
|
||||
|
||||
spawn named 'A' assert Greeting('Hi from outer space!');
|
||||
spawn named 'B' on asserted Greeting($t) console.log('Outer dataspace:', t);
|
||||
|
||||
spawn dataspace named 'C' {
|
||||
spawn named 'D' assert Outbound(Greeting('Hi from inner!'));
|
||||
spawn named 'E' on asserted Inbound(Greeting($t)) console.log('Inner dataspace:', t);
|
||||
}
|
|
@ -20,6 +20,10 @@ import * as t from "@babel/types";
|
|||
|
||||
export function SpawnStatement(node) {
|
||||
this.word("spawn");
|
||||
if (node.isDataspace) {
|
||||
this.space();
|
||||
this.word("dataspace");
|
||||
}
|
||||
if (node.name) {
|
||||
this.space();
|
||||
this.word("named");
|
||||
|
|
|
@ -195,8 +195,13 @@ export default class SyndicateParser extends _original_Parser {
|
|||
}
|
||||
|
||||
parseSpawnStatement() {
|
||||
let isDataspace = false;
|
||||
this.next();
|
||||
const node = this.startNode();
|
||||
if (this.isContextual("dataspace")) {
|
||||
this.next();
|
||||
isDataspace = true;
|
||||
}
|
||||
if (this.isContextual("named")) {
|
||||
this.next();
|
||||
node.name = this.parseExpression();
|
||||
|
@ -206,22 +211,29 @@ export default class SyndicateParser extends _original_Parser {
|
|||
node.parentInits = [];
|
||||
while (this.match(tt.colon)) {
|
||||
this.next();
|
||||
if (this.isContextual("asserting")) {
|
||||
this.next();
|
||||
node.initialAssertions.push(this.parseExpression());
|
||||
} else if (this.state.type === tt._let) {
|
||||
this.next();
|
||||
const id = this.parseBindingAtom();
|
||||
this.checkLVal(id, true, undefined, "spawn :let declaration");
|
||||
this.expect(tt.eq);
|
||||
const init = this.parseMaybeAssign(false);
|
||||
node.parentIds.push(id);
|
||||
node.parentInits.push(init);
|
||||
if (!isDataspace) {
|
||||
if (this.isContextual("asserting")) {
|
||||
this.next();
|
||||
node.initialAssertions.push(this.parseExpression());
|
||||
continue;
|
||||
}
|
||||
if (this.state.type === tt._let) {
|
||||
this.next();
|
||||
const id = this.parseBindingAtom();
|
||||
this.checkLVal(id, true, undefined, "spawn :let declaration");
|
||||
this.expect(tt.eq);
|
||||
const init = this.parseMaybeAssign(false);
|
||||
node.parentIds.push(id);
|
||||
node.parentInits.push(init);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
this.unexpected();
|
||||
// No optional keywordish things supported for spawned dataspaces at present.
|
||||
}
|
||||
this.unexpected();
|
||||
}
|
||||
node.bootProc = this.parseSyntheticFunctionStatement();
|
||||
node.isDataspace = isDataspace;
|
||||
return this.finishNode(node, "SpawnStatement");
|
||||
}
|
||||
|
||||
|
|
|
@ -341,7 +341,11 @@ export default declare((api, options) => {
|
|||
path.replaceWith(template(`DATASPACE.spawn(NAME, PROC, ASSERTIONS)`)({
|
||||
DATASPACE: state.DataspaceID,
|
||||
NAME: node.name || t.nullLiteral(),
|
||||
PROC: node.bootProc,
|
||||
PROC: !node.isDataspace ? node.bootProc : template.expression(
|
||||
`SYNDICATE.inNestedDataspace(PROC)`)({
|
||||
SYNDICATE: state.SyndicateID,
|
||||
PROC: node.bootProc
|
||||
}),
|
||||
ASSERTIONS: node.initialAssertions.length === 0 ? null :
|
||||
template.expression(`IMMUTABLE.Set(SEQ)`)({
|
||||
IMMUTABLE: state.ImmutableID,
|
||||
|
|
|
@ -25,7 +25,7 @@ import defineType, {
|
|||
} from "@babel/types/lib/definitions/utils";
|
||||
|
||||
defineType("SpawnStatement", {
|
||||
builder: ["name", "initialAssertions", "parentIds", "parentInits", "bootProc"],
|
||||
builder: ["name", "initialAssertions", "parentIds", "parentInits", "bootProc", "isDataspace"],
|
||||
visitor: ["name", "initialAssertions", "parentIds", "parentInits", "bootProc"],
|
||||
aliases: ["Statement", "Scopable"],
|
||||
fields: {
|
||||
|
@ -48,6 +48,9 @@ defineType("SpawnStatement", {
|
|||
bootProc: {
|
||||
validate: assertNodeType("FunctionExpression"),
|
||||
},
|
||||
isDataspace: {
|
||||
validate: assertOneOf(true, false),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue