|
|
|
"use strict";
|
|
|
|
|
|
|
|
const assert = require("assert");
|
|
|
|
const types = require("@babel/types");
|
|
|
|
const template = require("@babel/template").default;
|
|
|
|
const unpackExpression = require("./_unpack-expression");
|
|
|
|
const NoChange = require("../astformer/actions/no-change");
|
|
|
|
const printAst = require("../print-ast");
|
|
|
|
|
|
|
|
// FIXME: Add expression parens!
|
|
|
|
let tmplCallLazy = template(`
|
|
|
|
%%wrapper%%()
|
|
|
|
`);
|
|
|
|
|
|
|
|
let tmplLazyWrapper = template(`(
|
|
|
|
$$jsNix$memoize(() => %%expression%%)
|
|
|
|
)`);
|
|
|
|
|
|
|
|
let tmplLazyWrapperRecursive = template(`(
|
|
|
|
function() { return %%expression%%; }
|
|
|
|
)`);
|
|
|
|
|
|
|
|
let tmplExtend = template(`(
|
|
|
|
$$jsNix$extend(this ?? {}, %%object%%)
|
|
|
|
)`);
|
|
|
|
|
|
|
|
let tmplScopeWrapper = template(`(
|
|
|
|
(() => {
|
|
|
|
%%bindings%%;
|
|
|
|
|
|
|
|
return %%object%%;
|
|
|
|
})()
|
|
|
|
)`);
|
|
|
|
|
|
|
|
// FIXME: Verify that this always works, and that we don't need `var` for hoisting!
|
|
|
|
let tmplRecursiveBinding = template(`
|
|
|
|
const %%name%% = %%expression%%;
|
|
|
|
`);
|
|
|
|
|
|
|
|
function lazyEvaluationWrapper(args) {
|
|
|
|
let { recursive, ... rest } = args;
|
|
|
|
|
|
|
|
// printAst(rest.expression);
|
|
|
|
let wrapper = (recursive)
|
|
|
|
? tmplLazyWrapperRecursive
|
|
|
|
: tmplLazyWrapper;
|
|
|
|
|
|
|
|
return unpackExpression(wrapper(rest));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isDynamicBinding(binding) {
|
|
|
|
return binding.attrpath.attr[0].type !== "NixAttributeIdentifier";
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
name: "attribute-sets",
|
|
|
|
visitors: {
|
|
|
|
Program: (_node, { setContext }) => {
|
|
|
|
setContext(null, "attributeSets_inRecursiveSet", false);
|
|
|
|
return NoChange;
|
|
|
|
},
|
|
|
|
NixAttributeSelection: (_node, { defer }) => {
|
|
|
|
// TODO: Optimize dynamic attribute access by translating builtins.getAttr to dynamic property access at compile time? This requires scope analysis!
|
|
|
|
return defer((node) => {
|
|
|
|
assert(node.attrpath.type === "NixAttributePath");
|
|
|
|
|
|
|
|
return node.attrpath.attr.reduce((last, identifier) => {
|
|
|
|
assert(identifier.type === "NixAttributeIdentifier");
|
|
|
|
|
|
|
|
return unpackExpression(tmplCallLazy({
|
|
|
|
wrapper: types.memberExpression(last, types.identifier(identifier.name))
|
|
|
|
}));
|
|
|
|
}, node.expression);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
NixAttributeSet: (node, { defer, setContext, getContext }) => {
|
|
|
|
if (node.recursive) {
|
|
|
|
setContext(null, "attributeSets_inRecursiveSet", true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return defer((node) => {
|
|
|
|
let isRecursive = node.recursive;
|
|
|
|
let hasDynamicBindings = node.bind.some((binding) => isDynamicBinding(binding));
|
|
|
|
|
|
|
|
let bindings = node.bind.map((binding) => {
|
|
|
|
assert(binding.attrpath.attr.length === 1); // Nested attributes should have been desugared by this point
|
|
|
|
|
|
|
|
return {
|
|
|
|
name: binding.attrpath.attr[0].name,
|
|
|
|
expression: lazyEvaluationWrapper({ recursive: isRecursive, expression: binding.expression })
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
if (hasDynamicBindings) {
|
|
|
|
throw new Error(`UNIMPLEMENTED: Dynamic bindings are not supported yet`);
|
|
|
|
} else if (isRecursive) {
|
|
|
|
return unpackExpression(tmplScopeWrapper({
|
|
|
|
bindings: bindings.map(({ name, expression }) => {
|
|
|
|
return tmplRecursiveBinding({
|
|
|
|
name: name,
|
|
|
|
expression: expression
|
|
|
|
});
|
|
|
|
}),
|
|
|
|
object: types.objectExpression(bindings.map(({ name }) => {
|
|
|
|
return types.objectProperty(
|
|
|
|
types.stringLiteral(name),
|
|
|
|
types.identifier(name)
|
|
|
|
);
|
|
|
|
}))
|
|
|
|
}));
|
|
|
|
} else {
|
|
|
|
let object = types.objectExpression(bindings.map(({ name, expression }) => {
|
|
|
|
return types.objectProperty(
|
|
|
|
types.stringLiteral(name),
|
|
|
|
// TODO: Remove the isRecursive distinction here once heap-of-vars works
|
|
|
|
expression
|
|
|
|
);
|
|
|
|
}));
|
|
|
|
|
|
|
|
// Only do prototypal inheritance for `this` if we're dealing with nested recursive attribute sets
|
|
|
|
if (isRecursive && getContext("attributeSets_inRecursiveSet") === true) {
|
|
|
|
return unpackExpression(tmplExtend({
|
|
|
|
object: object
|
|
|
|
}));
|
|
|
|
} else {
|
|
|
|
return object;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
}
|
|
|
|
};
|