Compare commits
2 Commits
4cb239c6a0
...
f23f6f6393
Author | SHA1 | Date |
---|---|---|
Sven Slootweg | f23f6f6393 | 2 years ago |
Sven Slootweg | 52f39570a7 | 2 years ago |
@ -0,0 +1,45 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const measureTime = require("./astformer/util/measure-time");
|
||||||
|
const transpile = require("./transpile");
|
||||||
|
|
||||||
|
module.exports = function evaluate(nixCode) {
|
||||||
|
let transpiled = transpile(nixCode);
|
||||||
|
|
||||||
|
const api = {
|
||||||
|
builtins: {},
|
||||||
|
$$jsNix$memoize: function (func) {
|
||||||
|
let isCalled = false;
|
||||||
|
let storedResult;
|
||||||
|
|
||||||
|
return function (arg) {
|
||||||
|
if (isCalled === false) {
|
||||||
|
storedResult = func(arg);
|
||||||
|
isCalled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return storedResult;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
};
|
@ -0,0 +1,42 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const assert = require("assert");
|
||||||
|
|
||||||
|
const acceptableFirstCharacters = /^[a-zA-Z_]$/;
|
||||||
|
const acceptableSubsequentCharacters = /^[0-9a-zA-Z_]$/;
|
||||||
|
|
||||||
|
// This function deterministically and statelessly translates a name such that it is guaranteed to be valid in all identifier positions in JS.
|
||||||
|
// TODO: This can probably be made more performant...
|
||||||
|
|
||||||
|
function mangleCharacter(character) {
|
||||||
|
return (character === "$")
|
||||||
|
? "$$"
|
||||||
|
: `$${character.codePointAt(0)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function mangleName(name) {
|
||||||
|
assert(name.length > 0);
|
||||||
|
|
||||||
|
// FIXME: Tag an identifier of this type with an internal property instead
|
||||||
|
if (name.startsWith("$$jsNix$")) {
|
||||||
|
return name;
|
||||||
|
} else {
|
||||||
|
let completedFirstCharacter = false;
|
||||||
|
|
||||||
|
return Array.from(name)
|
||||||
|
.map((character) => {
|
||||||
|
if (!completedFirstCharacter) {
|
||||||
|
completedFirstCharacter = true;
|
||||||
|
|
||||||
|
return (acceptableFirstCharacters.test(character))
|
||||||
|
? character
|
||||||
|
: mangleCharacter(character);
|
||||||
|
} else {
|
||||||
|
return (acceptableSubsequentCharacters.test(character))
|
||||||
|
? character
|
||||||
|
: mangleCharacter(character);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,62 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const splitFilter = require("split-filter");
|
||||||
|
const assert = require("assert");
|
||||||
|
|
||||||
|
const NoChange = require("../astformer/actions/no-change");
|
||||||
|
const nixTypes = require("./_nix-types");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: "desugar-inherits",
|
||||||
|
visitors: {
|
||||||
|
NixAttributeSet: (node) => {
|
||||||
|
let [ inherits, regularBindings ] = splitFilter(node.bind, (binding) => binding.type === "NixInherit");
|
||||||
|
|
||||||
|
if (inherits.length === 0) {
|
||||||
|
return NoChange;
|
||||||
|
} else {
|
||||||
|
let tempCounter = 0;
|
||||||
|
let inherits_ = inherits.map((inherit) => {
|
||||||
|
return {
|
||||||
|
tempName: `$$jsNix$temp$${tempCounter++}`,
|
||||||
|
sourceExpression: inherit.expression,
|
||||||
|
names: inherit.attrs.attr.map((attribute) => {
|
||||||
|
assert(attribute.type === "NixAttributeIdentifier");
|
||||||
|
return attribute.name;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let letBindings = inherits_.map((inherit) => {
|
||||||
|
return nixTypes.NixBinding(
|
||||||
|
[ nixTypes.NixAttributeIdentifier(inherit.tempName) ],
|
||||||
|
inherit.sourceExpression
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let body = {
|
||||||
|
... node,
|
||||||
|
bind: [
|
||||||
|
... regularBindings,
|
||||||
|
... inherits_.flatMap((inherit) => {
|
||||||
|
return inherit.names.map((name) => {
|
||||||
|
return nixTypes.NixBinding(
|
||||||
|
[ nixTypes.NixAttributeIdentifier(name) ],
|
||||||
|
nixTypes.NixAttributeSelection(
|
||||||
|
nixTypes.NixIdentifier(inherit.tempName),
|
||||||
|
[ nixTypes.NixAttributeIdentifier(name) ]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
return nixTypes.NixLetIn(
|
||||||
|
letBindings,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const NoChange = require("../astformer/actions/no-change");
|
||||||
|
const mangleName = require("../mangle-name");
|
||||||
|
|
||||||
|
function mangleNode(node) {
|
||||||
|
if (node._isMangled) {
|
||||||
|
return NoChange;
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
... node,
|
||||||
|
name: mangleName(node.name),
|
||||||
|
_isMangled: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: "mangle-identifiers",
|
||||||
|
visitors: {
|
||||||
|
NixAttributeIdentifier: mangleNode,
|
||||||
|
NixIdentifier: mangleNode
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,40 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const tape = require("tape-catch");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const evaluate = require("../src/evaluate");
|
||||||
|
|
||||||
|
const NIX_SOURCE_REPO = process.env.NIX_SOURCE_REPO;
|
||||||
|
|
||||||
|
if (NIX_SOURCE_REPO == null) {
|
||||||
|
throw new Error(`To run the upstream Nix language tests, you must specify a NIX_SOURCE_REPO environment variable, that points at the root of a local checkout of the Git repository for Nix`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const testsPath = path.join(NIX_SOURCE_REPO, "tests/lang");
|
||||||
|
|
||||||
|
let tests = fs.readdirSync(testsPath)
|
||||||
|
.filter((entry) => entry.endsWith(".exp"))
|
||||||
|
.map((entry) => entry.replace(/\.exp$/, ""));
|
||||||
|
|
||||||
|
for (let test of tests) {
|
||||||
|
try {
|
||||||
|
let expression = fs.readFileSync(path.join(testsPath, `${test}.nix`), "utf8");
|
||||||
|
let expectedResult = fs.readFileSync(path.join(testsPath, `${test}.exp`), "utf8").replace(/\n$/, "");
|
||||||
|
|
||||||
|
tape(`Nix upstream language tests - ${test}`, (test) => {
|
||||||
|
test.plan(1);
|
||||||
|
|
||||||
|
let result = evaluate(expression).value.toString();
|
||||||
|
|
||||||
|
test.equals(expectedResult, result);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// FIXME: This would currently cause ENOENTs during evaluation (eg. reading a file from Nix itself) to be ignored
|
||||||
|
if (error.code === "ENOENT") {
|
||||||
|
// skip
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue