From 72e1653aa6c45a744aaeda478f4ea67a8d16883f Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 11 Nov 2018 14:00:02 +0000 Subject: [PATCH] First stab at cross-dataspace relaying; does not yet handle adhoc assertions --- packages/core/src/index.js | 5 + packages/core/src/relay.js | 122 +++++++++++++++++++++++ packages/syntax-playground/src/nested.js | 29 ++++++ packages/syntax/src/generators.js | 4 + packages/syntax/src/parser.js | 36 ++++--- packages/syntax/src/plugin.js | 6 +- packages/syntax/src/types.js | 5 +- 7 files changed, 193 insertions(+), 14 deletions(-) create mode 100644 packages/core/src/relay.js create mode 100644 packages/syntax-playground/src/nested.js diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 1f89534..67c55ae 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -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". diff --git a/packages/core/src/relay.js b/packages/core/src/relay.js new file mode 100644 index 0000000..73abe49 --- /dev/null +++ b/packages/core/src/relay.js @@ -0,0 +1,122 @@ +"use strict"; +//--------------------------------------------------------------------------- +// @syndicate-lang/core, an implementation of Syndicate dataspaces for JS. +// Copyright (C) 2016-2018 Tony Garnock-Jones +// +// 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 . +//--------------------------------------------------------------------------- + +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; diff --git a/packages/syntax-playground/src/nested.js b/packages/syntax-playground/src/nested.js new file mode 100644 index 0000000..740f04f --- /dev/null +++ b/packages/syntax-playground/src/nested.js @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------------- +// @syndicate-lang/syntax-test, a demo of Syndicate extensions to JS. +// Copyright (C) 2016-2018 Tony Garnock-Jones +// +// 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 . +//--------------------------------------------------------------------------- + +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); +} diff --git a/packages/syntax/src/generators.js b/packages/syntax/src/generators.js index c57c71b..d48f48f 100644 --- a/packages/syntax/src/generators.js +++ b/packages/syntax/src/generators.js @@ -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"); diff --git a/packages/syntax/src/parser.js b/packages/syntax/src/parser.js index 8f27274..2aa43f4 100644 --- a/packages/syntax/src/parser.js +++ b/packages/syntax/src/parser.js @@ -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"); } diff --git a/packages/syntax/src/plugin.js b/packages/syntax/src/plugin.js index 16e258c..8cba8f9 100644 --- a/packages/syntax/src/plugin.js +++ b/packages/syntax/src/plugin.js @@ -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, diff --git a/packages/syntax/src/types.js b/packages/syntax/src/types.js index 9ce1cd4..6c3c570 100644 --- a/packages/syntax/src/types.js +++ b/packages/syntax/src/types.js @@ -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), + }, }, });