"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 tmplConditionalExpression = templateExpression ( `
( ( ) => {
if ( % % condition % % ) {
return % % consequence % % ;
} else {
return % % alternative % % ;
}
} ) ( )
` );
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;
let safeName = 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 } ) ;
}
} ,
NixUnaryOperation : ( _node , { defer } ) => {
return defer ( ( node ) => {
if ( node . operator === "-" || node . operator === "!" ) {
return types . unaryExpression ( node . operator , node . argument , true ) ;
} else {
throw new Error ( ` Unsupported operator: ${ node . operator } ` ) ;
}
} ) ;
} ,
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
if ( node . operator === "&&" || node . operator === "||" ) {
return types . logicalExpression ( node . operator , node . left , node . right ) ;
} if ( node . operator === "//" ) {
return types . objectExpression ( [
types . spreadElement ( node . left ) ,
types . spreadElement ( node . right )
] ) ;
} else if ( node . operator === "++" ) {
return types . arrayExpression ( [
types . spreadElement ( node . left ) ,
types . spreadElement ( node . right )
] ) ;
} else {
return types . binaryExpression ( node . operator , node . left , node . right ) ;
}
} ) ;
} ,
NixWithExpression : ( node , { defer , setContext , getContextOptional } ) => {
// TODO: Can we optimize for the fast case (no nested `with`) by referencing the source attrset directly?
let parentContext = getContextOptional ( "implicitContext" ) ;
let hasParent = ( parentContext != null ) ;
let contextName = ` $ implicit ${ implicitContextCounter ++ } ` ;
setContext ( null , "implicitContext" , contextName ) ;
return defer ( ( node ) => {
let setupCode = ( hasParent )
? tmplImplicitContextNested ( { environment : node . environment , parent : parentContext } )
: tmplImplicitContextTop ( { environment : node . environment } ) ;
return tmplWithWrapper ( {
contextName : contextName ,
setupCode : setupCode ,
body : node . body
} ) ;
} ) ;
} ,
NixConditional : ( _node , { defer } ) => {
// condition, consequence, alternative
return defer ( ( node ) => {
return tmplConditionalExpression ( {
condition : node . condition ,
consequence : node . consequence ,
alternative : node . alternative
} ) ;
} ) ;
}
}
} ;
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 ,
] ;