You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

116 lines
3.5 KiB
JavaScript

"use strict";
const assert = require("assert");
const types = require("@babel/types");
const template = require("@babel/template").default;
const templateExpression = require("./util/template-expression");
const NoChange = require("astformer/actions/no-change");
// FIXME: Auto-generate argument list based on exposed API surface?
let tmplModule = template(`
module.exports = function({ builtins, removeAttrs, $memoize, $handleArgument, $assertUniqueKeys }) {
return %%contents%%;
};
`);
let tmplIdentifierReference = templateExpression(`(
%%name%%()
)`);
let tmplWithWrapper = templateExpression(`
(() => {
const %%contextName%% = %%setupCode%%;
return %%body%%;
})()
`);
let tmplImplicitContextNested = templateExpression(`
Object.assign(Object.create(%%parent%%), %%environment%%)
`);
let tmplImplicitContextTop = templateExpression(`
Object.assign({}, %%environment%%)
`);
let implicitContextCounter = 0;
let trivial = {
name: "trivial-transforms",
visitors: {
// Trivial transforms
NixParenthesizedExpression: (node) => node.expression,
Program: (_node, { setContext, defer }) => {
setContext(null, "identifierType", "reference");
return defer((node) => {
assert(node.body.length === 1);
return tmplModule({ contents: node.body[0] });
});
},
NixAttributePath: (_node, { setContext }) => {
// This ensures that attribute paths get excluded from being processed as an identifier, now that the parser no longer distinguishes between attribute identifiers and regular identifiers
setContext([ "attr" ], "identifierType", "attributePath");
return NoChange;
},
NixIdentifier: (node, { getContext }) => {
// FIXME: Mangle reserved keywords like `const`
let safeName = (node.name === "import")
? "$import"
: node.name;
if (getContext("identifierType") === "define") {
return types.identifier(safeName);
} else if (getContext("identifierType") === "attributePath") {
// Do nothing, this will get removed from the tree in attribute set handling
return NoChange;
} else { // reference
return tmplIdentifierReference({ name: safeName });
}
},
NixBinaryOperation: (_node, { defer }) => {
return defer((node) => {
// FIXME: Verify that all the 'operator' values match between Nix and JS!
// FIXME: Need to replace this with utility functions to deal with eg. paths
return types.binaryExpression(node.operator, node.left, node.right);
});
},
NixWithExpression: (node, { defer, setContext, getContext }) => {
// TODO: Can we optimize for the fast case (no nested `with`) by referencing the source attrset directly?
let parentContext = getContext("implicitContext");
let hasParent = (parentContext != null);
let contextName = `$implicit${implicitContextCounter++}`;
setContext(null, "implicitContext", contextName);
let setupCode = (hasParent)
? tmplImplicitContextNested({ environment: node.environment, parent: parentContext })
: tmplImplicitContextTop({ environment: node.environment });
return defer((node) => {
return tmplWithWrapper({
contextName: contextName,
setupCode: setupCode,
body: node.body
});
});
}
}
};
module.exports = [
// FIXME: desugar-search-path
require("./desugar-inherits"),
require("./desugar-attrsets"),
require("./desugar-let-attribute-set"),
require("./desugar-interpolation-expressions"),
require("./mangle-identifiers"),
require("./let-in"),
require("./literals"),
require("./lists"),
require("./functions"),
require("./attribute-sets"),
trivial,
];