"use strict"; const NoChange = require("../astformer/actions/no-change"); const { NixAttributeIdentifier, NixAttributeSet, NixBinding } = require("./_nix-types"); function isAttributeSet(node) { return (node.type === "NixAttributeSet"); } function mergeAttributeSets(a, b) { return NixAttributeSet([ ... a.bind, ... b.bind ]); } function mergeBindings(name, a, b) { let attributes = mergeAttributeSets(unpackBindingValue(a), unpackBindingValue(b)); return NixBinding( [ NixAttributeIdentifier(name) ], attributes ); } function unpackBindingValue(binding) { // binding -> value for top-level attribute of the binding if (binding.attrpath.attr.length > 1) { // Nested attribute path syntax, synthesize a new attribute set with the first attribute snipped off - this new attribute set represents the *value* of the binding return NixAttributeSet([ NixBinding(binding.attrpath.attr.slice(1), binding.expression) ]); } else { return binding.expression; } } module.exports = { name: "desugar-attrsets", visitors: { NixAttributeSet: (node) => { let neededDesugaring = false; let newStaticBindings = {}; let dynamicBindings = []; for (let binding of node.bind) { if (binding.attrpath.attr.length > 1) { neededDesugaring = true; } let firstAttribute = binding.attrpath.attr[0]; let isStaticAttribute = firstAttribute.type === "NixAttributeIdentifier"; if (isStaticAttribute) { let attributeName = firstAttribute.name; let existingBinding = newStaticBindings[attributeName]; if (existingBinding == null) { newStaticBindings[attributeName] = NixBinding( [ firstAttribute ], unpackBindingValue(binding) ); } else { if (isAttributeSet(existingBinding.expression) && isAttributeSet(binding.expression)) { neededDesugaring = true; newStaticBindings[attributeName] = mergeBindings(attributeName, existingBinding, binding); } else { throw new Error(`Key '${attributeName}' was specified twice, but this is only allowed when both values are an attribute set`); } } } else { // FIXME: Needs runtime check, need to *always* construct objects at runtime when dynamic bindings are involved dynamicBindings.push(binding); } } let staticBindingList = Object.entries(newStaticBindings).map(([ key, value ]) => { // console.log([ key, value ]); return value; }); // We only check this here because there are multiple things that could cause a need for desugaring, and this is simpler for now than having a separate 'check first' implementation if (neededDesugaring) { return NixAttributeSet([ ... staticBindingList, ... dynamicBindings ]); } else { return NoChange; } } } };