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.
180 lines
5.5 KiB
JavaScript
180 lines
5.5 KiB
JavaScript
"use strict";
|
|
|
|
const assert = require("assert");
|
|
const types = require("@babel/types");
|
|
const template = require("@babel/template").default;
|
|
const splitFilter = require("split-filter");
|
|
|
|
const templateExpression = require("./util/template-expression");
|
|
const NoChange = require("astformer/actions/no-change"); // FIXME
|
|
const lazyWrapper = require("./templates/lazy-wrapper");
|
|
const callLazyWrapper = require("./templates/call-lazy-wrapper");
|
|
const objectLiteral = require("./templates/object-literal");
|
|
|
|
// TODO: Optimize lazy evaluation wrappers by only unpacking them selectively when used in an actual expression; in particular, that avoids the "wrapper that just calls another wrapper" overhead when passing attributes as function arguments
|
|
// TODO: Change to a prototype-based scope object chain? This should produce more consistent output regardless of whether a given set of bindings is static vs. dynamic and recursive vs. non-recursive
|
|
|
|
let tmplAssertKeys = template(`
|
|
$assertUniqueKeys( %%keyList%% )
|
|
`);
|
|
|
|
let tmplObjectNormal = templateExpression(`
|
|
(() => {
|
|
%%keyAssertion%%;
|
|
|
|
return %%object%%;
|
|
})()
|
|
`);
|
|
|
|
let tmplScopeWrapper = templateExpression(`(
|
|
(() => {
|
|
%%bindings%%;
|
|
|
|
return %%object%%;
|
|
})()
|
|
)`);
|
|
|
|
let tmplDynamicScopeWrapper = templateExpression(`(
|
|
(() => {
|
|
let $attributes = {};
|
|
|
|
with ($attributes) {
|
|
/* Static and overrides */
|
|
Object.assign($attributes, %%staticBindings%%);
|
|
|
|
%%keyAssertion%%;
|
|
|
|
/* Dynamic bindings */
|
|
Object.assign($attributes, %%dynamicBindings%%);
|
|
}
|
|
|
|
return $attributes;
|
|
})()
|
|
)`, { strictMode: false });
|
|
|
|
// FIXME: Verify that this always works, and that we don't need `var` for hoisting!
|
|
let tmplRecursiveBinding = template(`
|
|
const %%name%% = %%expression%%;
|
|
`);
|
|
|
|
|
|
function isDynamicBinding(binding) {
|
|
return binding.attrpath.attr[0].type !== "NixAttributeIdentifier";
|
|
}
|
|
|
|
function objectNormal(bindings) {
|
|
return tmplObjectNormal({
|
|
object: objectLiteral(bindings.map(({ name, expression }) => {
|
|
return [ name, expression ];
|
|
})),
|
|
keyAssertion: bindings.some((binding) => typeof binding.name !== "string")
|
|
// Only needed when dealing with dynamic keys
|
|
? assertKeys(bindings)
|
|
: null
|
|
});
|
|
}
|
|
|
|
function objectRecursiveStatic(bindings) {
|
|
return tmplScopeWrapper({
|
|
bindings: bindings.map(({ name, expression }) => {
|
|
return tmplRecursiveBinding({
|
|
name: name,
|
|
expression: expression
|
|
});
|
|
}),
|
|
object: objectLiteral(bindings.map(({ name }) => {
|
|
assert(typeof name === "string");
|
|
return [ name, types.identifier(name) ];
|
|
}))
|
|
});
|
|
}
|
|
|
|
function objectRecursiveDynamic(bindings) {
|
|
// NOTE: We assign static bindings first and then in a separate pass the dynamic bindings; that way, the dynamic bindings' keys can refer to values set in the static binding pass. For the same reason, we only check duplicate keys *after* setting up the static bindings.
|
|
let [ staticBindings, dynamicBindings ] = splitFilter(bindings, (binding) => typeof binding.name === "string");
|
|
|
|
return tmplDynamicScopeWrapper({
|
|
keyAssertion: assertKeys(bindings),
|
|
staticBindings: objectLiteral(staticBindings.map(({ name, expression }) => [ name, expression ])),
|
|
dynamicBindings: objectLiteral(dynamicBindings.map(({ name, expression }) => [ name, expression ]))
|
|
});
|
|
}
|
|
|
|
function implicitStringLiteral(node) {
|
|
if (typeof node === "string") {
|
|
return types.stringLiteral(node);
|
|
} else {
|
|
return node;
|
|
}
|
|
}
|
|
|
|
function assertKeys(bindings) {
|
|
let keys = bindings.map(({ name }) => implicitStringLiteral(name));
|
|
return tmplAssertKeys({ keyList: types.arrayExpression(keys) });
|
|
}
|
|
|
|
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 callLazyWrapper(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 [ dynamicNodes, staticNodes ] = splitFilter(node.bind, (binding) => isDynamicBinding(binding));
|
|
|
|
// let staticBindings = staticNodes.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: lazyWrapper(binding.expression)
|
|
// };
|
|
// });
|
|
|
|
let bindings = node.bind.map((binding) => {
|
|
assert(binding.attrpath.attr.length === 1); // Nested attributes should have been desugared by this point
|
|
|
|
return {
|
|
name: (isDynamicBinding(binding))
|
|
? binding.attrpath.attr[0]
|
|
: binding.attrpath.attr[0].name,
|
|
expression: lazyWrapper(binding.expression)
|
|
};
|
|
});
|
|
|
|
let hasDynamicBindings = bindings.some((binding) => typeof binding.name !== "string");
|
|
|
|
if (isRecursive) {
|
|
if (hasDynamicBindings) {
|
|
return objectRecursiveDynamic(bindings);
|
|
} else {
|
|
return objectRecursiveStatic(bindings);
|
|
}
|
|
} else {
|
|
return objectNormal(bindings);
|
|
}
|
|
});
|
|
},
|
|
}
|
|
};
|