aboutsummaryrefslogtreecommitdiff
path: root/src/libexpr/primops.cc
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2014-05-26 13:46:11 +0200
committerEelco Dolstra <eelco.dolstra@logicblox.com>2014-05-26 14:26:29 +0200
commitc273c15cb13bb86420dda1e5341a4e19517532b5 (patch)
tree38b670d6fbd64735502fd60525a625c18d8372ad /src/libexpr/primops.cc
parentf0fdbd0897ce63c138ec663ed89a94709a8441a7 (diff)
Add primop ‘scopedImport’
‘scopedImport’ works like ‘import’, except that it takes a set of attributes to be added to the lexical scope of the expression, essentially extending or overriding the builtin variables. For instance, the expression scopedImport { x = 1; } ./foo.nix where foo.nix contains ‘x’, will evaluate to 1. This has a few applications: * It allows getting rid of function argument specifications in package expressions. For instance, a package expression like: { stdenv, fetchurl, libfoo }: stdenv.mkDerivation { ... buildInputs = [ libfoo ]; } can now we written as just stdenv.mkDerivation { ... buildInputs = [ libfoo ]; } and imported in all-packages.nix as: bar = scopedImport pkgs ./bar.nix; So whereas we once had dependencies listed in three places (buildInputs, the function, and the call site), they now only need to appear in one place. * It allows overriding builtin functions. For instance, to trace all calls to ‘map’: let overrides = { map = f: xs: builtins.trace "map called!" (map f xs); # Ensure that our override gets propagated by calls to # import/scopedImport. import = fn: scopedImport overrides fn; scopedImport = attrs: fn: scopedImport (overrides // attrs) fn; # Also update ‘builtins’. builtins = builtins // overrides; }; in scopedImport overrides ./bla.nix * Similarly, it allows extending the set of builtin functions. For instance, during Nixpkgs/NixOS evaluation, the Nixpkgs library functions could be added to the default scope. There is a downside: calls to scopedImport are not memoized, unlike import. So importing a file multiple times leads to multiple parsings / evaluations. It would be possible to construct the AST only once, but that would require careful handling of variables/environments.
Diffstat (limited to 'src/libexpr/primops.cc')
-rw-r--r--src/libexpr/primops.cc25
1 files changed, 25 insertions, 0 deletions
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index f402478dd..533ae3768 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -93,6 +93,30 @@ static void prim_import(EvalState & state, const Pos & pos, Value * * args, Valu
}
+static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+ PathSet context;
+ state.forceAttrs(*args[0]);
+ Path path = resolveExprPath(state.coerceToPath(pos, *args[1], context));
+
+ Env * env = &state.allocEnv(args[0]->attrs->size());
+ env->up = &state.baseEnv;
+
+ StaticEnv staticEnv(false, &state.staticBaseEnv);
+
+ unsigned int displ = 0;
+ for (auto & attr : *args[0]->attrs) {
+ staticEnv.vars[attr.name] = displ;
+ env->values[displ++] = attr.value;
+ }
+
+ startNest(nest, lvlTalkative, format("evaluating file `%1%'") % path);
+ Expr * e = state.parseExprFromFile(path, staticEnv);
+
+ e->eval(state, *env, v);
+}
+
+
/* Return a string representing the type of the expression. */
static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
@@ -1247,6 +1271,7 @@ void EvalState::createBaseEnv()
// Miscellaneous
addPrimOp("import", 1, prim_import);
+ addPrimOp("scopedImport", 2, prim_scopedImport);
addPrimOp("__typeOf", 1, prim_typeOf);
addPrimOp("isNull", 1, prim_isNull);
addPrimOp("__isFunction", 1, prim_isFunction);