"use strict" ;
const assert = require ( "assert" ) ;
const types = require ( "@babel/types" ) ;
const template = require ( "@babel/template" ) . default ;
const splitFilter = require ( "split-filter" ) ;
const unpackExpression = require ( "./util/unpack-expression" ) ;
const NoChange = require ( "../astformer/actions/no-change" ) ;
const printAst = require ( "../print-ast" ) ;
const lazyWrapper = require ( "./templates/lazy-wrapper" ) ;
const callLazyWrapper = require ( "./templates/call-lazy-wrapper" ) ;
// 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
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 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 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 )
} ;
} ) ;
if ( dynamicNodes . length > 0 ) {
printAst ( node ) ;
throw new Error ( ` UNIMPLEMENTED: Dynamic bindings are not supported yet ` ) ;
} else if ( isRecursive ) {
return unpackExpression ( tmplScopeWrapper ( {
bindings : staticBindings . map ( ( { name , expression } ) => {
return tmplRecursiveBinding ( {
name : name ,
expression : expression
} ) ;
} ) ,
object : types . objectExpression ( staticBindings . map ( ( { name } ) => {
return types . objectProperty (
types . stringLiteral ( name ) ,
types . identifier ( name )
) ;
} ) )
} ) ) ;
} else {
let object = types . objectExpression ( staticBindings . 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 ;
}
}
} ) ;
} ,
}
} ;