"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; };