syndicate-js/packages/syntax/src/index.js

144 lines
5.8 KiB
JavaScript

"use strict";
//---------------------------------------------------------------------------
// @syndicate-lang/syntax, a translator 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/>.
//---------------------------------------------------------------------------
// Monkeypatch Babel in an utterly horrible way to support Syndicate
// syntactic extensions to JS.
//---------------------------------------------------------------------------
// (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.
//
// This allows our later extensions to be picked up correctly.
//
const Validators = require("@babel/types/lib/validators/generated");
const shallowEqual = require("@babel/types/lib/utils/shallowEqual");
function _isX(X, previous) {
return (node, opts) => {
if (node && Types.FLIPPED_ALIAS_KEYS[X].indexOf(node.type) !== -1) {
return typeof opts === "undefined" || shallowEqual.default(node, opts);
} else {
return previous(node, opts);
}
};
}
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.
//
// We do this by, in the build scripts, COPYING the parser COMPILED
// code, and MODIFYING it to add a couple of new exports. See
// `../babel_parser_suffix.js`.
//
// Here, then, we load the MODIFIED copy of the parser, and then
// PRE-POPULATE THE REQUIRE CACHE with it. Later requires of the
// ACTUAL module will then get our MODIFIED copy.
//
// Vile.
//
const BabelParser = require("./babel_parser");
require.cache[require.resolve("@babel/parser")] = require.cache[require.resolve("./babel_parser")];
//---------------------------------------------------------------------------
// (2) Install the new AST node types required.
//
// 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. We
// also update various properties and mappings to include the new
// types.
//
const Types = require("@babel/types");
require("./types");
//
// Now reset the TYPES array. This code is roughly equivalent to the
// declaration of TYPES in babel-types/src/definitions/index.js:
//
// const TYPES: Array<string> = Object.keys(VISITOR_KEYS)
// .concat(Object.keys(FLIPPED_ALIAS_KEYS))
// .concat(Object.keys(DEPRECATED_KEYS));
//
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"];
Types.getBindingIdentifiers.keys.SpawnStatement = ["parentIds"];
//---------------------------------------------------------------------------
// (3) Install our modified parser in place of the core parser.
//
// This makes use of the modification we installed from
// `../babel_parser_suffix.js` in step (1).
//
// It overrides the parser's notion of its root class -- effectively,
// the Syndicate parser becomes a mixin.
//
BabelParser.__setParser(require("./parser").default);
//---------------------------------------------------------------------------
// (4) Install generators for the new AST node types.
//
// 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 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
// one of our patched modules, we give them the patched version. This
// is ultra disgusting.
(function () {
const Module = require('module');
const _oldLoad = Module._load;
Module._load = function (request, parent) {
if (!parent) return _oldLoad.apply(this, arguments);
switch (request) {
case "@babel/parser": return BabelParser;
case "@babel/types": return Types;
case "@babel/generator": return Generator;
default: return _oldLoad.apply(this, arguments);
}
}
})();
//---------------------------------------------------------------------------
// (6) At this point, we should (?) be able to load and use Babel
// somewhat normally.