You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
139 lines
3.5 KiB
JavaScript
139 lines
3.5 KiB
JavaScript
"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;
|
|
};
|