"use strict" ;
const measureTime = require ( "astformer/util/measure-time" ) ; // FIXME
const transpile = require ( "./transpile" ) ;
module . exports = function evaluate ( nixCode ) {
let transpiled = transpile ( nixCode ) ;
function lazyWrap ( func ) {
return ( ) => func ;
}
const builtins = {
seq : lazyWrap ( ( $a ) => ( $b ) => {
// First evaluate the first argument...
$a ( ) ;
// ... then evaluate and return the second argument.
return $b ( ) ;
} ) ,
splitVersion : lazyWrap ( ( $version ) => {
let version = $version ( ) ;
// FIXME: assert string
let parts = [ ] ;
let currentPart = "" ;
let isNumber = null ;
function finalizePart ( ) {
if ( currentPart !== "" ) {
// NOTE: Numbers get added to the list as strings anyway. This is really weird considering `nix-env -u`s comparison logic, but it's how upstream Nix works too.
parts . push ( currentPart ) ;
currentPart = "" ;
isNumber = null ;
}
}
// FIXME: Is it correct to assume that only the ASCII character set is supported here?
// TODO: Replace this with a proper parser some day
for ( let i = 0 ; i < version . length ; i ++ ) {
let code = version . charCodeAt ( i ) ;
if ( code >= 48 && code <= 57 ) {
// Digit
if ( isNumber !== true ) {
finalizePart ( ) ;
isNumber = true ;
}
currentPart += version [ i ] ;
} else if ( ( code >= 65 && code <= 90 ) || ( code >= 97 && code <= 122 ) ) {
// Letter (uppercase and lowercase respectively)
if ( isNumber !== false ) {
finalizePart ( ) ;
isNumber = false ;
}
currentPart += version [ i ] ;
} else {
finalizePart ( ) ;
}
}
finalizePart ( ) ;
return parts ;
} )
} ;
const api = {
builtins : ( ) => builtins ,
$memoize : function ( func ) {
let isCalled = false ;
let storedResult ;
return function ( arg ) {
if ( isCalled === false ) {
storedResult = func ( arg ) ;
isCalled = true ;
}
return storedResult ;
} ;
} ,
$handleArgument : function ( name , _arg , defaultValue ) {
// We need to evaluate the lazy wrapper for `arg`, as it is passed in as an attribute set; and since that is a value, it will have been wrapped.
const arg = _arg ( ) ;
// FIXME: Improve this check, check for a (translated) attrset specifically
if ( typeof arg === "object" ) {
// NOTE: We do *not* evaluate the actual attributes, nor the default value; them merely being present is enough for our case.
const value = arg [ name ] ;
if ( value !== undefined ) {
return value ;
} else if ( defaultValue !== undefined ) {
return defaultValue ;
} else {
throw new Error ( ` Missing required argument ' ${ name } ' ` ) ;
}
} else {
// FIXME: Improve error
throw new Error ( ` Must pass an attribute set to function that expects one ` ) ;
}
} ,
$assertUniqueKeys : function ( keys ) {
let seen = new Set ( ) ;
for ( let key of keys ) {
if ( seen . has ( key ) ) {
throw new Error ( ` Attempted to define duplicate attribute ' ${ key } ' ` ) ;
} else {
seen . add ( key ) ;
}
}
}
} ;
// TODO: Switch to Node `vm` API instead, and check whether there's a polyfill for it for non-Node environments, build a custom one if not
const context = { module : { } , exports : { } } ;
context . module . exports = exports ;
new Function ( "module" , transpiled ) ( context . module ) ;
// Warm-up for hot VM performance testing
// for (let i = 0; i < 10000; i++) {
// context.module.exports(api);
// }
let result = measureTime ( ( ) => context . module . exports ( api ) ) ;
if ( process . env . DEBUG _NIX ) {
console . log ( "-- EVALUATION RESULT:" ) ;
console . log ( result ) ;
}
return result ;
} ;