From fa2ee1915d169a6d92e40298aefbd7d6ca8e96fb Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Mon, 5 Nov 2018 13:57:31 +0000 Subject: [PATCH] Attempt to introduce scopes for 'on ...' and 'during', so that pattern variables are bound. --- packages/syntax/src/index.js | 19 +++++++++++++++--- packages/syntax/src/parser.js | 3 +++ packages/syntax/src/plugin.js | 36 ++++++++++++++++++++++++++++++----- packages/syntax/src/types.js | 4 ++-- 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/packages/syntax/src/index.js b/packages/syntax/src/index.js index efcc907..e069a6a 100644 --- a/packages/syntax/src/index.js +++ b/packages/syntax/src/index.js @@ -21,7 +21,7 @@ // syntactic extensions to JS. //--------------------------------------------------------------------------- -// (0) Replace the `isStatement` and `isExpression` functions with +// (0) Replace `isStatement`, `isExpression`, etc. functions with // non-hard-coded versions that check the contents of // FLIPPED_ALIAS_KEYS at the time of each call. // @@ -42,6 +42,7 @@ function _isX(X, previous) { Validators.isStatement = _isX("Statement", Validators.isStatement); Validators.isExpression = _isX("Expression", Validators.isExpression); +Validators.isScopable = _isX("Scopable", Validators.isScopable); //--------------------------------------------------------------------------- // (1) Load the core parser in modifiable form. @@ -64,7 +65,9 @@ require.cache[require.resolve("@babel/parser")] = require.cache[require.resolve( // // We do this by loading and populating the core TYPES array, and then // loading our extensions, followed by RESETTING the TYPES array to -// include the new extensions as well as the original definitions. +// include the new extensions as well as the original definitions. We +// also update various properties and mappings to include the new +// types. // const Types = require("@babel/types"); require("./types"); @@ -80,6 +83,12 @@ Types.TYPES.splice(0); Array.prototype.push.apply(Types.TYPES, Object.keys(Types.VISITOR_KEYS)); Array.prototype.push.apply(Types.TYPES, Object.keys(Types.FLIPPED_ALIAS_KEYS)); Array.prototype.push.apply(Types.TYPES, Object.keys(Types.DEPRECATED_KEYS)); +// +// Update the means by which scopes discover binding identifiers from +// a node. +// +Types.getBindingIdentifiers.keys.EventHandlerEndpoint = ["captureIds"]; +Types.getBindingIdentifiers.keys.DuringStatement = ["captureIds"]; //--------------------------------------------------------------------------- // (3) Install our modified parser in place of the core parser. @@ -98,12 +107,16 @@ BabelParser.__setParser(require("./parser").default); // This is mostly optional, unless for some reason we want only the // syntax extension but not the transform (e.g. if the plugin omitted // its `visitor`). -const Generator = require("@babel/generator"); // needed for _load override, below const Generators = require("@babel/generator/lib/generators"); const SyndicateGenerators = require("./generators"); Object.keys(SyndicateGenerators).forEach((f) => { Generators[f] = SyndicateGenerators[f]; }); +// +// Load this after updating Generators[...], because it copies the +// keys of Generators onto the Printer class. +// +const Generator = require("@babel/generator"); // needed for _load override, below //--------------------------------------------------------------------------- // (5) Ensure that, no matter where we are when some module `require`s diff --git a/packages/syntax/src/parser.js b/packages/syntax/src/parser.js index 9047e5b..b3b7092 100644 --- a/packages/syntax/src/parser.js +++ b/packages/syntax/src/parser.js @@ -130,6 +130,7 @@ export default class SyndicateParser extends _original_Parser { } else { node.body = this.parseStatement(); } + node.captureIds = 'UNINITIALIZED'; return this.finishNode(node, "DuringStatement"); } @@ -220,6 +221,7 @@ export default class SyndicateParser extends _original_Parser { node.isDynamic = true; node.pattern = this.parseExpression(); node.body = this.parseStatement(); + node.captureIds = 'UNINITIALIZED'; return this.finishNode(node, "EventHandlerEndpoint"); } @@ -248,6 +250,7 @@ export default class SyndicateParser extends _original_Parser { node.terminal = terminal; node.pattern = this.parseExpression(); node.body = this.parseStatement(); + node.captureIds = 'UNINITIALIZED'; return this.finishNode(node, "EventHandlerEndpoint"); default: diff --git a/packages/syntax/src/plugin.js b/packages/syntax/src/plugin.js index 108875c..ff9e6bb 100644 --- a/packages/syntax/src/plugin.js +++ b/packages/syntax/src/plugin.js @@ -90,12 +90,12 @@ function compilePattern(state, patternPath) { let constPaths = []; let constVals = []; let capturePaths = []; - let captureNames = []; + let captureIds = []; let syndicatePath = []; function pushCapture(idNode) { capturePaths.push(syndicatePath.slice()); - captureNames.push(idNode.name.slice(1)); + captureIds.push(t.identifier(idNode.name.slice(1))); } function pushConstant(node) { @@ -186,7 +186,7 @@ function compilePattern(state, patternPath) { constPathsAst: astifySyndicatePath(state, constPaths), constValsAst: listAst(state, t.arrayExpression(constVals)), capturePathsAst: astifySyndicatePath(state, capturePaths), - captureNames: captureNames, + captureIds: captureIds, assertionAst: template.expression(`SYNDICATE.Observe(ASSERTION)`)({ SYNDICATE: state.SyndicateID, ASSERTION: assertion @@ -213,11 +213,35 @@ function instantiatePatternToPattern(state, patternPath) { return patternPath.node; } +const bindingRegistrationVisitor = { + EventHandlerEndpoint(path, state) { + switch (path.node.triggerType) { + case "dataflow": + break; + case "asserted": + case "retracted": + case "message": { + let info = compilePattern(state, path.get('pattern')); + path.node.captureIds = info.captureIds; + path.scope.registerBinding('let', path); + break; + } + } + }, + + DuringStatement(path, state) { + let info = compilePattern(state, path.get('pattern')); + path.node.captureIds = info.captureIds; + path.scope.registerBinding('let', path); + }, +}; + function translateEndpoint(state, path, expectedEvt) { const { node } = path; let info = compilePattern(state, path.get('pattern')); let _evt = path.scope.generateUidIdentifier("evt"); let _vs = path.scope.generateUidIdentifier("vs"); + path.replaceWith(template( `DATASPACE._currentFacet.addEndpoint(function () { let HANDLER = { @@ -245,8 +269,8 @@ function translateEndpoint(state, path, expectedEvt) { EVT: _evt, EXPECTED: expectedEvt, VS: _vs, - INITS: info.captureNames.map((n, i) => template(`let N = VS.get(I);`)({ - N: t.identifier(n), + INITS: info.captureIds.map((n, i) => template(`let N = VS.get(I);`)({ + N: n, VS: _vs, I: t.numericLiteral(i), })), @@ -303,6 +327,8 @@ export default declare((api, options) => { SYNDICATE: state.SyndicateID, SAVEDGLOBALFACET: savedGlobalFacetUid, })); + + path.traverse(bindingRegistrationVisitor, state); }, SpawnStatement(path, state) { diff --git a/packages/syntax/src/types.js b/packages/syntax/src/types.js index e4a68e7..a8789e5 100644 --- a/packages/syntax/src/types.js +++ b/packages/syntax/src/types.js @@ -92,7 +92,7 @@ defineType("DataflowStatement", { defineType("EventHandlerEndpoint", { builder: ["terminal", "triggerType", "isDynamic", "pattern", "body"], visitor: ["terminal", "triggerType", "isDynamic", "pattern", "body"], - aliases: ["Statement"], + aliases: ["Statement", "Scopable"], fields: { terminal: { validate: assertOneOf(true, false), @@ -175,7 +175,7 @@ defineType("ActivationExpression", { defineType("DuringStatement", { builder: ["pattern", "body"], visitor: ["pattern", "body"], - aliases: ["Statement"], + aliases: ["Statement", "Scopable"], fields: { pattern: { validate: assertNodeType("Expression"),