about summary refs log tree commit diff
path: root/src/libexpr/primops.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/libexpr/primops.cc')
-rw-r--r--src/libexpr/primops.cc529
1 files changed, 370 insertions, 159 deletions
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 355b81adf76d..d7245fca52e1 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -1,15 +1,16 @@
+#include "archive.hh"
+#include "derivations.hh"
+#include "download.hh"
+#include "eval-inline.hh"
 #include "eval.hh"
-#include "misc.hh"
 #include "globals.hh"
+#include "json-to-value.hh"
+#include "names.hh"
 #include "store-api.hh"
 #include "util.hh"
-#include "archive.hh"
-#include "value-to-xml.hh"
 #include "value-to-json.hh"
-#include "json-to-value.hh"
-#include "names.hh"
-#include "eval-inline.hh"
-#include "download.hh"
+#include "value-to-xml.hh"
+#include "primops.hh"
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -43,7 +44,7 @@ std::pair<string, string> decodeContext(const string & s)
 InvalidPathError::InvalidPathError(const Path & path) :
     EvalError(format("path ‘%1%’ is not valid") % path), path(path) {}
 
-void realiseContext(const PathSet & context)
+void EvalState::realiseContext(const PathSet & context)
 {
     PathSet drvs;
     for (auto & i : context) {
@@ -52,16 +53,14 @@ void realiseContext(const PathSet & context)
         assert(isStorePath(ctx));
         if (!store->isValidPath(ctx))
             throw InvalidPathError(ctx);
-        if (!decoded.second.empty() && isDerivation(ctx))
+        if (!decoded.second.empty() && nix::isDerivation(ctx))
             drvs.insert(decoded.first + "!" + decoded.second);
     }
     if (!drvs.empty()) {
         /* For performance, prefetch all substitute info. */
         PathSet willBuild, willSubstitute, unknown;
         unsigned long long downloadSize, narSize;
-        queryMissing(*store, drvs,
-            willBuild, willSubstitute, unknown, downloadSize, narSize);
-
+        store->queryMissing(drvs, willBuild, willSubstitute, unknown, downloadSize, narSize);
         store->buildPaths(drvs);
     }
 }
@@ -75,7 +74,7 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
     Path path = state.coerceToPath(pos, *args[1], context);
 
     try {
-        realiseContext(context);
+        state.realiseContext(context);
     } catch (InvalidPathError & e) {
         throw EvalError(format("cannot import ‘%1%’, since path ‘%2%’ is not valid, at %3%")
             % path % e.path % pos);
@@ -83,12 +82,14 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
 
     path = state.checkSourcePath(path);
 
-    if (isStorePath(path) && store->isValidPath(path) && isDerivation(path)) {
+    if (isStorePath(path) && state.store->isValidPath(path) && isDerivation(path)) {
         Derivation drv = readDerivation(path);
         Value & w = *state.allocValue();
-        state.mkAttrs(w, 2 + drv.outputs.size());
+        state.mkAttrs(w, 3 + drv.outputs.size());
         Value * v2 = state.allocAttr(w, state.sDrvPath);
-        mkString(*v2, path, singleton<PathSet>("=" + path));
+        mkString(*v2, path, {"=" + path});
+        v2 = state.allocAttr(w, state.sName);
+        mkString(*v2, drv.env["name"]);
         Value * outputsVal =
             state.allocAttr(w, state.symbols.create("outputs"));
         state.mkList(*outputsVal, drv.outputs.size());
@@ -96,10 +97,9 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
 
         for (const auto & o : drv.outputs) {
             v2 = state.allocAttr(w, state.symbols.create(o.first));
-            mkString(*v2, o.second.path,
-                singleton<PathSet>("!" + o.first + "!" + path));
-            outputsVal->list.elems[outputs_index] = state.allocValue();
-            mkString(*(outputsVal->list.elems[outputs_index++]), o.first);
+            mkString(*v2, o.second.path, {"!" + o.first + "!" + path});
+            outputsVal->listElems()[outputs_index] = state.allocValue();
+            mkString(*(outputsVal->listElems()[outputs_index++]), o.first);
         }
         w.attrs->sort();
         Value fun;
@@ -123,7 +123,7 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
                 env->values[displ++] = attr.value;
             }
 
-            startNest(nest, lvlTalkative, format("evaluating file ‘%1%’") % path);
+            Activity act(*logger, lvlTalkative, format("evaluating file ‘%1%’") % path);
             Expr * e = state.parseExprFromFile(resolveExprPath(path), staticEnv);
 
             e->eval(state, *env, v);
@@ -143,7 +143,7 @@ static void prim_importNative(EvalState & state, const Pos & pos, Value * * args
     Path path = state.coerceToPath(pos, *args[0], context);
 
     try {
-        realiseContext(context);
+        state.realiseContext(context);
     } catch (InvalidPathError & e) {
         throw EvalError(format("cannot import ‘%1%’, since path ‘%2%’ is not valid, at %3%")
             % path % e.path % pos);
@@ -186,7 +186,7 @@ static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Valu
         case tPath: t = "path"; break;
         case tNull: t = "null"; break;
         case tAttrs: t = "set"; break;
-        case tList: t = "list"; break;
+        case tList1: case tList2: case tListN: t = "list"; break;
         case tLambda:
         case tPrimOp:
         case tPrimOpApp:
@@ -195,6 +195,7 @@ static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Valu
         case tExternal:
             t = args[0]->external->typeOf();
             break;
+        case tFloat: t = "float"; break;
         default: abort();
     }
     mkString(v, state.symbols.create(t));
@@ -224,6 +225,12 @@ static void prim_isInt(EvalState & state, const Pos & pos, Value * * args, Value
     mkBool(v, args[0]->type == tInt);
 }
 
+/* Determine whether the argument is a float. */
+static void prim_isFloat(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+    state.forceValue(*args[0]);
+    mkBool(v, args[0]->type == tFloat);
+}
 
 /* Determine whether the argument is a string. */
 static void prim_isString(EvalState & state, const Pos & pos, Value * * args, Value & v)
@@ -245,11 +252,17 @@ struct CompareValues
 {
     bool operator () (const Value * v1, const Value * v2) const
     {
+        if (v1->type == tFloat && v2->type == tInt)
+            return v1->fpoint < v2->integer;
+        if (v1->type == tInt && v2->type == tFloat)
+            return v1->integer < v2->fpoint;
         if (v1->type != v2->type)
-            throw EvalError("cannot compare values of different types");
+            throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) % showType(*v2));
         switch (v1->type) {
             case tInt:
                 return v1->integer < v2->integer;
+            case tFloat:
+                return v1->fpoint < v2->fpoint;
             case tString:
                 return strcmp(v1->string.s, v2->string.s) < 0;
             case tPath:
@@ -270,7 +283,7 @@ typedef list<Value *> ValueList;
 
 static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
-    startNest(nest, lvlDebug, "finding dependencies");
+    Activity act(*logger, lvlDebug, "finding dependencies");
 
     state.forceAttrs(*args[0], pos);
 
@@ -282,8 +295,8 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
     state.forceList(*startSet->value, pos);
 
     ValueList workSet;
-    for (unsigned int n = 0; n < startSet->value->list.length; ++n)
-        workSet.push_back(startSet->value->list.elems[n]);
+    for (unsigned int n = 0; n < startSet->value->listSize(); ++n)
+        workSet.push_back(startSet->value->listElems()[n]);
 
     /* Get the operator. */
     Bindings::iterator op =
@@ -321,17 +334,17 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
         state.forceList(call, pos);
 
         /* Add the values returned by the operator to the work set. */
-        for (unsigned int n = 0; n < call.list.length; ++n) {
-            state.forceValue(*call.list.elems[n]);
-            workSet.push_back(call.list.elems[n]);
+        for (unsigned int n = 0; n < call.listSize(); ++n) {
+            state.forceValue(*call.listElems()[n]);
+            workSet.push_back(call.listElems()[n]);
         }
     }
 
     /* Create the result list. */
     state.mkList(v, res.size());
     unsigned int n = 0;
-    foreach (ValueList::iterator, i, res)
-        v.list.elems[n++] = *i;
+    for (auto & i : res)
+        v.listElems()[n++] = i;
 }
 
 
@@ -443,7 +456,7 @@ void prim_valueSize(EvalState & state, const Pos & pos, Value * * args, Value &
    derivation. */
 static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
-    startNest(nest, lvlVomit, "evaluating derivation");
+    Activity act(*logger, lvlVomit, "evaluating derivation");
 
     state.forceAttrs(*args[0], pos);
 
@@ -477,24 +490,24 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
     StringSet outputs;
     outputs.insert("out");
 
-    foreach (Bindings::iterator, i, *args[0]->attrs) {
-        if (i->name == state.sIgnoreNulls) continue;
-        string key = i->name;
-        startNest(nest, lvlVomit, format("processing attribute ‘%1%’") % key);
+    for (auto & i : *args[0]->attrs) {
+        if (i.name == state.sIgnoreNulls) continue;
+        string key = i.name;
+        Activity act(*logger, lvlVomit, format("processing attribute ‘%1%’") % key);
 
         try {
 
             if (ignoreNulls) {
-                state.forceValue(*i->value);
-                if (i->value->type == tNull) continue;
+                state.forceValue(*i.value);
+                if (i.value->type == tNull) continue;
             }
 
             /* The `args' attribute is special: it supplies the
                command-line arguments to the builder. */
             if (key == "args") {
-                state.forceList(*i->value, pos);
-                for (unsigned int n = 0; n < i->value->list.length; ++n) {
-                    string s = state.coerceToString(posDrvName, *i->value->list.elems[n], context, true);
+                state.forceList(*i.value, pos);
+                for (unsigned int n = 0; n < i.value->listSize(); ++n) {
+                    string s = state.coerceToString(posDrvName, *i.value->listElems()[n], context, true);
                     drv.args.push_back(s);
                 }
             }
@@ -502,11 +515,11 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
             /* All other attributes are passed to the builder through
                the environment. */
             else {
-                string s = state.coerceToString(posDrvName, *i->value, context, true);
+                string s = state.coerceToString(posDrvName, *i.value, context, true);
                 drv.env[key] = s;
                 if (key == "builder") drv.builder = s;
-                else if (i->name == state.sSystem) drv.platform = s;
-                else if (i->name == state.sName) {
+                else if (i.name == state.sSystem) drv.platform = s;
+                else if (i.name == state.sName) {
                     drvName = s;
                     printMsg(lvlVomit, format("derivation name is ‘%1%’") % drvName);
                 }
@@ -520,17 +533,17 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
                 else if (key == "outputs") {
                     Strings tmp = tokenizeString<Strings>(s);
                     outputs.clear();
-                    foreach (Strings::iterator, j, tmp) {
-                        if (outputs.find(*j) != outputs.end())
-                            throw EvalError(format("duplicate derivation output ‘%1%’, at %2%") % *j % posDrvName);
-                        /* !!! Check whether *j is a valid attribute
+                    for (auto & j : tmp) {
+                        if (outputs.find(j) != outputs.end())
+                            throw EvalError(format("duplicate derivation output ‘%1%’, at %2%") % j % posDrvName);
+                        /* !!! Check whether j is a valid attribute
                            name. */
                         /* Derivations cannot be named ‘drv’, because
                            then we'd have an attribute ‘drvPath’ in
                            the resulting set. */
-                        if (*j == "drv")
+                        if (j == "drv")
                             throw EvalError(format("invalid derivation output name ‘drv’, at %1%") % posDrvName);
-                        outputs.insert(*j);
+                        outputs.insert(j);
                     }
                     if (outputs.empty())
                         throw EvalError(format("derivation cannot have an empty set of outputs, at %1%") % posDrvName);
@@ -547,8 +560,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
     /* Everything in the context of the strings in the derivation
        attributes should be added as dependencies of the resulting
        derivation. */
-    foreach (PathSet::iterator, i, context) {
-        Path path = *i;
+    for (auto & path : context) {
 
         /* Paths marked with `=' denote that the path of a derivation
            is explicitly passed to the builder.  Since that allows the
@@ -559,11 +571,12 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
            runs. */
         if (path.at(0) == '=') {
             /* !!! This doesn't work if readOnlyMode is set. */
-            PathSet refs; computeFSClosure(*store, string(path, 1), refs);
-            foreach (PathSet::iterator, j, refs) {
-                drv.inputSrcs.insert(*j);
-                if (isDerivation(*j))
-                    drv.inputDrvs[*j] = store->queryDerivationOutputNames(*j);
+            PathSet refs;
+            state.store->computeFSClosure(string(path, 1), refs);
+            for (auto & j : refs) {
+                drv.inputSrcs.insert(j);
+                if (isDerivation(j))
+                    drv.inputDrvs[j] = state.store->queryDerivationOutputNames(j);
             }
         }
 
@@ -580,7 +593,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
         /* Handle derivation contexts returned by
            ‘builtins.storePath’. */
         else if (isDerivation(path))
-            drv.inputDrvs[path] = store->queryDerivationOutputNames(path);
+            drv.inputDrvs[path] = state.store->queryDerivationOutputNames(path);
 
         /* Otherwise it's a source file. */
         else
@@ -622,25 +635,25 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
            are empty, and the corresponding environment variables have
            an empty value.  This ensures that changes in the set of
            output names do get reflected in the hash. */
-        foreach (StringSet::iterator, i, outputs) {
-            drv.env[*i] = "";
-            drv.outputs[*i] = DerivationOutput("", "", "");
+        for (auto & i : outputs) {
+            drv.env[i] = "";
+            drv.outputs[i] = DerivationOutput("", "", "");
         }
 
         /* Use the masked derivation expression to compute the output
            path. */
-        Hash h = hashDerivationModulo(*store, drv);
+        Hash h = hashDerivationModulo(*state.store, drv);
 
-        foreach (DerivationOutputs::iterator, i, drv.outputs)
-            if (i->second.path == "") {
-                Path outPath = makeOutputPath(i->first, h, drvName);
-                drv.env[i->first] = outPath;
-                i->second.path = outPath;
+        for (auto & i : drv.outputs)
+            if (i.second.path == "") {
+                Path outPath = makeOutputPath(i.first, h, drvName);
+                drv.env[i.first] = outPath;
+                i.second.path = outPath;
             }
     }
 
     /* Write the resulting term into the Nix store directory. */
-    Path drvPath = writeDerivation(*store, drv, drvName, state.repair);
+    Path drvPath = writeDerivation(state.store, drv, drvName, state.repair);
 
     printMsg(lvlChatty, format("instantiated ‘%1%’ -> ‘%2%’")
         % drvName % drvPath);
@@ -648,13 +661,13 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
     /* Optimisation, but required in read-only mode! because in that
        case we don't actually write store derivations, so we can't
        read them later. */
-    drvHashes[drvPath] = hashDerivationModulo(*store, drv);
+    drvHashes[drvPath] = hashDerivationModulo(*state.store, drv);
 
     state.mkAttrs(v, 1 + drv.outputs.size());
-    mkString(*state.allocAttr(v, state.sDrvPath), drvPath, singleton<PathSet>("=" + drvPath));
-    foreach (DerivationOutputs::iterator, i, drv.outputs) {
-        mkString(*state.allocAttr(v, state.symbols.create(i->first)),
-            i->second.path, singleton<PathSet>("!" + i->first + "!" + drvPath));
+    mkString(*state.allocAttr(v, state.sDrvPath), drvPath, {"=" + drvPath});
+    for (auto & i : drv.outputs) {
+        mkString(*state.allocAttr(v, state.symbols.create(i.first)),
+            i.second.path, {"!" + i.first + "!" + drvPath});
     }
     v.attrs->sort();
 }
@@ -694,7 +707,7 @@ static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, V
         throw EvalError(format("path ‘%1%’ is not in the Nix store, at %2%") % path % pos);
     Path path2 = toStorePath(path);
     if (!settings.readOnlyMode)
-        store->ensurePath(path2);
+        state.store->ensurePath(path2);
     context.insert(path2);
     mkString(v, path, context);
 }
@@ -744,7 +757,7 @@ static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Va
     PathSet context;
     Path path = state.coerceToPath(pos, *args[0], context);
     try {
-        realiseContext(context);
+        state.realiseContext(context);
     } catch (InvalidPathError & e) {
         throw EvalError(format("cannot read ‘%1%’, since path ‘%2%’ is not valid, at %3%")
             % path % e.path % pos);
@@ -752,7 +765,7 @@ static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Va
     string s = readFile(state.checkSourcePath(path));
     if (s.find((char) 0) != string::npos)
         throw Error(format("the contents of the file ‘%1%’ cannot be represented as a Nix string") % path);
-    mkString(v, s.c_str());
+    mkString(v, s.c_str(), context);
 }
 
 
@@ -764,9 +777,8 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
 
     SearchPath searchPath;
 
-    PathSet context;
-    for (unsigned int n = 0; n < args[0]->list.length; ++n) {
-        Value & v2(*args[0]->list.elems[n]);
+    for (unsigned int n = 0; n < args[0]->listSize(); ++n) {
+        Value & v2(*args[0]->listElems()[n]);
         state.forceAttrs(v2, pos);
 
         string prefix;
@@ -777,21 +789,23 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
         i = v2.attrs->find(state.symbols.create("path"));
         if (i == v2.attrs->end())
             throw EvalError(format("attribute ‘path’ missing, at %1%") % pos);
-        string path = state.coerceToPath(pos, *i->value, context);
 
-        searchPath.push_back(std::pair<string, Path>(prefix, state.checkSourcePath(path)));
-    }
+        PathSet context;
+        string path = state.coerceToString(pos, *i->value, context, false, false);
 
-    string path = state.forceStringNoCtx(*args[1], pos);
+        try {
+            state.realiseContext(context);
+        } catch (InvalidPathError & e) {
+            throw EvalError(format("cannot find ‘%1%’, since path ‘%2%’ is not valid, at %3%")
+                % path % e.path % pos);
+        }
 
-    try {
-        realiseContext(context);
-    } catch (InvalidPathError & e) {
-        throw EvalError(format("cannot find ‘%1%’, since path ‘%2%’ is not valid, at %3%")
-            % path % e.path % pos);
+        searchPath.emplace_back(prefix, path);
     }
 
-    mkPath(v, state.findFile(searchPath, path, pos).c_str());
+    string path = state.forceStringNoCtx(*args[1], pos);
+
+    mkPath(v, state.checkSourcePath(state.findFile(searchPath, path, pos)).c_str());
 }
 
 /* Read a directory (without . or ..) */
@@ -800,7 +814,7 @@ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Val
     PathSet ctx;
     Path path = state.coerceToPath(pos, *args[0], ctx);
     try {
-        realiseContext(ctx);
+        state.realiseContext(ctx);
     } catch (InvalidPathError & e) {
         throw EvalError(format("cannot read ‘%1%’, since path ‘%2%’ is not valid, at %3%")
             % path % e.path % pos);
@@ -871,23 +885,26 @@ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Valu
 
     PathSet refs;
 
-    foreach (PathSet::iterator, i, context) {
-        Path path = *i;
+    for (auto path : context) {
         if (path.at(0) == '=') path = string(path, 1);
-        if (isDerivation(path))
-            throw EvalError(format("in ‘toFile’: the file ‘%1%’ cannot refer to derivation outputs, at %2%") % name % pos);
+        if (isDerivation(path)) {
+            /* See prim_unsafeDiscardOutputDependency. */
+            if (path.at(0) != '~')
+                throw EvalError(format("in ‘toFile’: the file ‘%1%’ cannot refer to derivation outputs, at %2%") % name % pos);
+            path = string(path, 1);
+        }
         refs.insert(path);
     }
 
     Path storePath = settings.readOnlyMode
         ? computeStorePathForText(name, contents, refs)
-        : store->addTextToStore(name, contents, refs, state.repair);
+        : state.store->addTextToStore(name, contents, refs, state.repair);
 
     /* Note: we don't need to add `context' to the context of the
        result, since `storePath' itself has references to the paths
        used in args[1]. */
 
-    mkString(v, storePath, singleton<PathSet>(storePath));
+    mkString(v, storePath, {storePath});
 }
 
 
@@ -947,9 +964,9 @@ static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args
 
     Path dstPath = settings.readOnlyMode
         ? computeStorePathForPath(path, true, htSHA256, filter).first
-        : store->addToStore(baseNameOf(path), path, true, htSHA256, filter, state.repair);
+        : state.store->addToStore(baseNameOf(path), path, true, htSHA256, filter, state.repair);
 
-    mkString(v, dstPath, singleton<PathSet>(dstPath));
+    mkString(v, dstPath, {dstPath});
 }
 
 
@@ -968,9 +985,9 @@ static void prim_attrNames(EvalState & state, const Pos & pos, Value * * args, V
 
     unsigned int n = 0;
     for (auto & i : *args[0]->attrs)
-        mkString(*(v.list.elems[n++] = state.allocValue()), i.name);
+        mkString(*(v.listElems()[n++] = state.allocValue()), i.name);
 
-    std::sort(v.list.elems, v.list.elems + n,
+    std::sort(v.listElems(), v.listElems() + n,
         [](Value * v1, Value * v2) { return strcmp(v1->string.s, v2->string.s) < 0; });
 }
 
@@ -985,13 +1002,13 @@ static void prim_attrValues(EvalState & state, const Pos & pos, Value * * args,
 
     unsigned int n = 0;
     for (auto & i : *args[0]->attrs)
-        v.list.elems[n++] = (Value *) &i;
+        v.listElems()[n++] = (Value *) &i;
 
-    std::sort(v.list.elems, v.list.elems + n,
+    std::sort(v.listElems(), v.listElems() + n,
         [](Value * v1, Value * v2) { return (string) ((Attr *) v1)->name < (string) ((Attr *) v2)->name; });
 
     for (unsigned int i = 0; i < n; ++i)
-        v.list.elems[i] = ((Attr *) v.list.elems[i])->value;
+        v.listElems()[i] = ((Attr *) v.listElems()[i])->value;
 }
 
 
@@ -1048,18 +1065,18 @@ static void prim_removeAttrs(EvalState & state, const Pos & pos, Value * * args,
 
     /* Get the attribute names to be removed. */
     std::set<Symbol> names;
-    for (unsigned int i = 0; i < args[1]->list.length; ++i) {
-        state.forceStringNoCtx(*args[1]->list.elems[i], pos);
-        names.insert(state.symbols.create(args[1]->list.elems[i]->string.s));
+    for (unsigned int i = 0; i < args[1]->listSize(); ++i) {
+        state.forceStringNoCtx(*args[1]->listElems()[i], pos);
+        names.insert(state.symbols.create(args[1]->listElems()[i]->string.s));
     }
 
     /* Copy all attributes not in that set.  Note that we don't need
        to sort v.attrs because it's a subset of an already sorted
        vector. */
     state.mkAttrs(v, args[0]->attrs->size());
-    foreach (Bindings::iterator, i, *args[0]->attrs) {
-        if (names.find(i->name) == names.end())
-            v.attrs->push_back(*i);
+    for (auto & i : *args[0]->attrs) {
+        if (names.find(i.name) == names.end())
+            v.attrs->push_back(i);
     }
 }
 
@@ -1073,12 +1090,12 @@ static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args,
 {
     state.forceList(*args[0], pos);
 
-    state.mkAttrs(v, args[0]->list.length);
+    state.mkAttrs(v, args[0]->listSize());
 
     std::set<Symbol> seen;
 
-    for (unsigned int i = 0; i < args[0]->list.length; ++i) {
-        Value & v2(*args[0]->list.elems[i]);
+    for (unsigned int i = 0; i < args[0]->listSize(); ++i) {
+        Value & v2(*args[0]->listElems()[i]);
         state.forceAttrs(v2, pos);
 
         Bindings::iterator j = v2.attrs->find(state.sName);
@@ -1111,8 +1128,8 @@ static void prim_intersectAttrs(EvalState & state, const Pos & pos, Value * * ar
 
     state.mkAttrs(v, std::min(args[0]->attrs->size(), args[1]->attrs->size()));
 
-    foreach (Bindings::iterator, i, *args[0]->attrs) {
-        Bindings::iterator j = args[1]->attrs->find(i->name);
+    for (auto & i : *args[0]->attrs) {
+        Bindings::iterator j = args[1]->attrs->find(i.name);
         if (j != args[1]->attrs->end())
             v.attrs->push_back(*j);
     }
@@ -1131,11 +1148,11 @@ static void prim_catAttrs(EvalState & state, const Pos & pos, Value * * args, Va
     Symbol attrName = state.symbols.create(state.forceStringNoCtx(*args[0], pos));
     state.forceList(*args[1], pos);
 
-    Value * res[args[1]->list.length];
+    Value * res[args[1]->listSize()];
     unsigned int found = 0;
 
-    for (unsigned int n = 0; n < args[1]->list.length; ++n) {
-        Value & v2(*args[1]->list.elems[n]);
+    for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
+        Value & v2(*args[1]->listElems()[n]);
         state.forceAttrs(v2, pos);
         Bindings::iterator i = v2.attrs->find(attrName);
         if (i != v2.attrs->end())
@@ -1144,13 +1161,13 @@ static void prim_catAttrs(EvalState & state, const Pos & pos, Value * * args, Va
 
     state.mkList(v, found);
     for (unsigned int n = 0; n < found; ++n)
-        v.list.elems[n] = res[n];
+        v.listElems()[n] = res[n];
 }
 
 
 /* Return a set containing the names of the formal arguments expected
    by the function `f'.  The value of each attribute is a Boolean
-   denoting whether has a default value.  For instance,
+   denoting whether the corresponding argument has a default value.  For instance,
 
       functionArgs ({ x, y ? 123}: ...)
    => { x = false; y = true; }
@@ -1173,9 +1190,9 @@ static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args
     }
 
     state.mkAttrs(v, args[0]->lambda.fun->formals->formals.size());
-    foreach (Formals::Formals_::iterator, i, args[0]->lambda.fun->formals->formals)
+    for (auto & i : args[0]->lambda.fun->formals->formals)
         // !!! should optimise booleans (allocate only once)
-        mkBool(*state.allocAttr(v, i->name), i->def);
+        mkBool(*state.allocAttr(v, i.name), i.def);
     v.attrs->sort();
 }
 
@@ -1189,17 +1206,17 @@ static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args
 static void prim_isList(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
     state.forceValue(*args[0]);
-    mkBool(v, args[0]->type == tList);
+    mkBool(v, args[0]->isList());
 }
 
 
 static void elemAt(EvalState & state, const Pos & pos, Value & list, int n, Value & v)
 {
     state.forceList(list, pos);
-    if (n < 0 || (unsigned int) n >= list.list.length)
+    if (n < 0 || (unsigned int) n >= list.listSize())
         throw Error(format("list index %1% is out of bounds, at %2%") % n % pos);
-    state.forceValue(*list.list.elems[n]);
-    v = *list.list.elems[n];
+    state.forceValue(*list.listElems()[n]);
+    v = *list.listElems()[n];
 }
 
 
@@ -1223,11 +1240,11 @@ static void prim_head(EvalState & state, const Pos & pos, Value * * args, Value
 static void prim_tail(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
     state.forceList(*args[0], pos);
-    if (args[0]->list.length == 0)
+    if (args[0]->listSize() == 0)
         throw Error(format("‘tail’ called on an empty list, at %1%") % pos);
-    state.mkList(v, args[0]->list.length - 1);
-    for (unsigned int n = 0; n < v.list.length; ++n)
-        v.list.elems[n] = args[0]->list.elems[n + 1];
+    state.mkList(v, args[0]->listSize() - 1);
+    for (unsigned int n = 0; n < v.listSize(); ++n)
+        v.listElems()[n] = args[0]->listElems()[n + 1];
 }
 
 
@@ -1237,11 +1254,11 @@ static void prim_map(EvalState & state, const Pos & pos, Value * * args, Value &
     state.forceFunction(*args[0], pos);
     state.forceList(*args[1], pos);
 
-    state.mkList(v, args[1]->list.length);
+    state.mkList(v, args[1]->listSize());
 
-    for (unsigned int n = 0; n < v.list.length; ++n)
-        mkApp(*(v.list.elems[n] = state.allocValue()),
-            *args[0], *args[1]->list.elems[n]);
+    for (unsigned int n = 0; n < v.listSize(); ++n)
+        mkApp(*(v.listElems()[n] = state.allocValue()),
+            *args[0], *args[1]->listElems()[n]);
 }
 
 
@@ -1254,15 +1271,15 @@ static void prim_filter(EvalState & state, const Pos & pos, Value * * args, Valu
     state.forceList(*args[1], pos);
 
     // FIXME: putting this on the stack is risky.
-    Value * vs[args[1]->list.length];
+    Value * vs[args[1]->listSize()];
     unsigned int k = 0;
 
     bool same = true;
-    for (unsigned int n = 0; n < args[1]->list.length; ++n) {
+    for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
         Value res;
-        state.callFunction(*args[0], *args[1]->list.elems[n], res, noPos);
+        state.callFunction(*args[0], *args[1]->listElems()[n], res, noPos);
         if (state.forceBool(res))
-            vs[k++] = args[1]->list.elems[n];
+            vs[k++] = args[1]->listElems()[n];
         else
             same = false;
     }
@@ -1271,7 +1288,7 @@ static void prim_filter(EvalState & state, const Pos & pos, Value * * args, Valu
         v = *args[1];
     else {
         state.mkList(v, k);
-        for (unsigned int n = 0; n < k; ++n) v.list.elems[n] = vs[n];
+        for (unsigned int n = 0; n < k; ++n) v.listElems()[n] = vs[n];
     }
 }
 
@@ -1281,8 +1298,8 @@ static void prim_elem(EvalState & state, const Pos & pos, Value * * args, Value
 {
     bool res = false;
     state.forceList(*args[1], pos);
-    for (unsigned int n = 0; n < args[1]->list.length; ++n)
-        if (state.eqValues(*args[0], *args[1]->list.elems[n])) {
+    for (unsigned int n = 0; n < args[1]->listSize(); ++n)
+        if (state.eqValues(*args[0], *args[1]->listElems()[n])) {
             res = true;
             break;
         }
@@ -1294,7 +1311,7 @@ static void prim_elem(EvalState & state, const Pos & pos, Value * * args, Value
 static void prim_concatLists(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
     state.forceList(*args[0], pos);
-    state.concatLists(v, args[0]->list.length, args[0]->list.elems, pos);
+    state.concatLists(v, args[0]->listSize(), args[0]->listElems(), pos);
 }
 
 
@@ -1302,7 +1319,114 @@ static void prim_concatLists(EvalState & state, const Pos & pos, Value * * args,
 static void prim_length(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
     state.forceList(*args[0], pos);
-    mkInt(v, args[0]->list.length);
+    mkInt(v, args[0]->listSize());
+}
+
+
+/* Reduce a list by applying a binary operator, from left to
+   right. The operator is applied strictly. */
+static void prim_foldlStrict(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+    state.forceFunction(*args[0], pos);
+    state.forceList(*args[2], pos);
+
+    Value * vCur = args[1];
+
+    if (args[2]->listSize())
+        for (unsigned int n = 0; n < args[2]->listSize(); ++n) {
+            Value vTmp;
+            state.callFunction(*args[0], *vCur, vTmp, pos);
+            vCur = n == args[2]->listSize() - 1 ? &v : state.allocValue();
+            state.callFunction(vTmp, *args[2]->listElems()[n], *vCur, pos);
+        }
+    else
+        v = *vCur;
+
+    state.forceValue(v);
+}
+
+
+static void anyOrAll(bool any, EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+    state.forceFunction(*args[0], pos);
+    state.forceList(*args[1], pos);
+
+    Value vTmp;
+    for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
+        state.callFunction(*args[0], *args[1]->listElems()[n], vTmp, pos);
+        bool res = state.forceBool(vTmp);
+        if (res == any) {
+            mkBool(v, any);
+            return;
+        }
+    }
+
+    mkBool(v, !any);
+}
+
+
+static void prim_any(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+    anyOrAll(true, state, pos, args, v);
+}
+
+
+static void prim_all(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+    anyOrAll(false, state, pos, args, v);
+}
+
+
+static void prim_genList(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+    state.forceFunction(*args[0], pos);
+    auto len = state.forceInt(*args[1], pos);
+
+    if (len < 0)
+        throw EvalError(format("cannot create list of size %1%, at %2%") % len % pos);
+
+    state.mkList(v, len);
+
+    for (unsigned int n = 0; n < (unsigned int) len; ++n) {
+        Value * arg = state.allocValue();
+        mkInt(*arg, n);
+        mkApp(*(v.listElems()[n] = state.allocValue()), *args[0], *arg);
+    }
+}
+
+
+static void prim_lessThan(EvalState & state, const Pos & pos, Value * * args, Value & v);
+
+
+static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+    state.forceFunction(*args[0], pos);
+    state.forceList(*args[1], pos);
+
+    auto len = args[1]->listSize();
+    state.mkList(v, len);
+    for (unsigned int n = 0; n < len; ++n) {
+        state.forceValue(*args[1]->listElems()[n]);
+        v.listElems()[n] = args[1]->listElems()[n];
+    }
+
+
+    auto comparator = [&](Value * a, Value * b) {
+        /* Optimization: if the comparator is lessThan, bypass
+           callFunction. */
+        if (args[0]->type == tPrimOp && args[0]->primOp->fun == prim_lessThan)
+            return CompareValues()(a, b);
+
+        Value vTmp1, vTmp2;
+        state.callFunction(*args[0], *a, vTmp1, pos);
+        state.callFunction(vTmp1, *b, vTmp2, pos);
+        return state.forceBool(vTmp2);
+    };
+
+    /* FIXME: std::sort can segfault if the comparator is not a strict
+       weak ordering. What to do? std::stable_sort() seems more
+       resilient, but no guarantees... */
+    std::stable_sort(v.listElems(), v.listElems() + len, comparator);
 }
 
 
@@ -1313,27 +1437,40 @@ static void prim_length(EvalState & state, const Pos & pos, Value * * args, Valu
 
 static void prim_add(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
-    mkInt(v, state.forceInt(*args[0], pos) + state.forceInt(*args[1], pos));
+    if (args[0]->type == tFloat || args[1]->type == tFloat)
+        mkFloat(v, state.forceFloat(*args[0], pos) + state.forceFloat(*args[1], pos));
+    else
+        mkInt(v, state.forceInt(*args[0], pos) + state.forceInt(*args[1], pos));
 }
 
 
 static void prim_sub(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
-    mkInt(v, state.forceInt(*args[0], pos) - state.forceInt(*args[1], pos));
+    if (args[0]->type == tFloat || args[1]->type == tFloat)
+        mkFloat(v, state.forceFloat(*args[0], pos) - state.forceFloat(*args[1], pos));
+    else
+        mkInt(v, state.forceInt(*args[0], pos) - state.forceInt(*args[1], pos));
 }
 
 
 static void prim_mul(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
-    mkInt(v, state.forceInt(*args[0], pos) * state.forceInt(*args[1], pos));
+    if (args[0]->type == tFloat || args[1]->type == tFloat)
+        mkFloat(v, state.forceFloat(*args[0], pos) * state.forceFloat(*args[1], pos));
+    else
+        mkInt(v, state.forceInt(*args[0], pos) * state.forceInt(*args[1], pos));
 }
 
 
 static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
-    NixInt i2 = state.forceInt(*args[1], pos);
-    if (i2 == 0) throw EvalError(format("division by zero, at %1%") % pos);
-    mkInt(v, state.forceInt(*args[0], pos) / i2);
+    NixFloat f2 = state.forceFloat(*args[1], pos);
+    if (f2 == 0) throw EvalError(format("division by zero, at %1%") % pos);
+
+    if (args[0]->type == tFloat || args[1]->type == tFloat)
+        mkFloat(v, state.forceFloat(*args[0], pos) / state.forceFloat(*args[1], pos));
+    else
+        mkInt(v, state.forceInt(*args[0], pos) / state.forceInt(*args[1], pos));
 }
 
 
@@ -1407,11 +1544,8 @@ static void prim_unsafeDiscardOutputDependency(EvalState & state, const Pos & po
     string s = state.coerceToString(pos, *args[0], context);
 
     PathSet context2;
-    foreach (PathSet::iterator, i, context) {
-        Path p = *i;
-        if (p.at(0) == '=') p = "~" + string(p, 1);
-        context2.insert(p);
-    }
+    for (auto & p : context)
+        context2.insert(p.at(0) == '=' ? "~" + string(p, 1) : p);
 
     mkString(v, s, context2);
 }
@@ -1452,13 +1586,68 @@ static void prim_match(EvalState & state, const Pos & pos, Value * * args, Value
     for (unsigned int n = 0; n < len; ++n) {
         auto i = subs.find(n);
         if (i == subs.end())
-            mkNull(*(v.list.elems[n] = state.allocValue()));
+            mkNull(*(v.listElems()[n] = state.allocValue()));
         else
-            mkString(*(v.list.elems[n] = state.allocValue()), i->second);
+            mkString(*(v.listElems()[n] = state.allocValue()), i->second);
     }
 }
 
 
+static void prim_concatStringSep(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+    PathSet context;
+
+    auto sep = state.forceString(*args[0], context, pos);
+    state.forceList(*args[1], pos);
+
+    string res;
+    res.reserve((args[1]->listSize() + 32) * sep.size());
+    bool first = true;
+
+    for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
+        if (first) first = false; else res += sep;
+        res += state.coerceToString(pos, *args[1]->listElems()[n], context);
+    }
+
+    mkString(v, res, context);
+}
+
+
+static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+    state.forceList(*args[0], pos);
+    state.forceList(*args[1], pos);
+    if (args[0]->listSize() != args[1]->listSize())
+        throw EvalError(format("‘from’ and ‘to’ arguments to ‘replaceStrings’ have different lengths, at %1%") % pos);
+
+    Strings from;
+    for (unsigned int n = 0; n < args[0]->listSize(); ++n)
+        from.push_back(state.forceStringNoCtx(*args[0]->listElems()[n], pos));
+
+    Strings to;
+    for (unsigned int n = 0; n < args[1]->listSize(); ++n)
+        to.push_back(state.forceStringNoCtx(*args[1]->listElems()[n], pos));
+
+    PathSet context;
+    auto s = state.forceString(*args[2], context, pos);
+
+    string res;
+    for (size_t p = 0; p < s.size(); ) {
+        bool found = false;
+        for (auto i = from.begin(), j = to.begin(); i != from.end(); ++i, ++j)
+            if (s.compare(p, i->size(), *i) == 0) {
+                found = true;
+                p += i->size();
+                res += *j;
+                break;
+            }
+        if (!found) res += s[p++];
+    }
+
+    mkString(v, res, context);
+}
+
+
 /*************************************************************
  * Versions
  *************************************************************/
@@ -1515,7 +1704,7 @@ void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
     } else
         url = state.forceStringNoCtx(*args[0], pos);
 
-    Path res = downloadFileCached(url, unpack);
+    Path res = makeDownloader()->downloadCached(state.store, url, unpack);
     mkString(v, res, PathSet({res}));
 }
 
@@ -1537,6 +1726,16 @@ static void prim_fetchTarball(EvalState & state, const Pos & pos, Value * * args
  *************************************************************/
 
 
+RegisterPrimOp::PrimOps * RegisterPrimOp::primOps;
+
+
+RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun)
+{
+    if (!primOps) primOps = new PrimOps;
+    primOps->emplace_back(name, arity, fun);
+}
+
+
 void EvalState::createBaseEnv()
 {
     baseEnv.up = 0;
@@ -1573,7 +1772,7 @@ void EvalState::createBaseEnv()
        language feature gets added.  It's not necessary to increase it
        when primops get added, because you can just use `builtins ?
        primOp' to check. */
-    mkInt(v, 3);
+    mkInt(v, 4);
     addConstant("__langVersion", v);
 
     // Miscellaneous
@@ -1590,6 +1789,7 @@ void EvalState::createBaseEnv()
     addPrimOp("__isFunction", 1, prim_isFunction);
     addPrimOp("__isString", 1, prim_isString);
     addPrimOp("__isInt", 1, prim_isInt);
+    addPrimOp("__isFloat", 1, prim_isFloat);
     addPrimOp("__isBool", 1, prim_isBool);
     addPrimOp("__genericClosure", 1, prim_genericClosure);
     addPrimOp("abort", 1, prim_abort);
@@ -1646,6 +1846,11 @@ void EvalState::createBaseEnv()
     addPrimOp("__elem", 2, prim_elem);
     addPrimOp("__concatLists", 1, prim_concatLists);
     addPrimOp("__length", 1, prim_length);
+    addPrimOp("__foldl'", 3, prim_foldlStrict);
+    addPrimOp("__any", 2, prim_any);
+    addPrimOp("__all", 2, prim_all);
+    addPrimOp("__genList", 2, prim_genList);
+    addPrimOp("__sort", 2, prim_sort);
 
     // Integer arithmetic
     addPrimOp("__add", 2, prim_add);
@@ -1662,6 +1867,8 @@ void EvalState::createBaseEnv()
     addPrimOp("__unsafeDiscardOutputDependency", 1, prim_unsafeDiscardOutputDependency);
     addPrimOp("__hashString", 2, prim_hashString);
     addPrimOp("__match", 2, prim_match);
+    addPrimOp("__concatStringsSep", 2, prim_concatStringSep);
+    addPrimOp("__replaceStrings", 3, prim_replaceStrings);
 
     // Versions
     addPrimOp("__parseDrvName", 1, prim_parseDrvName);
@@ -1685,7 +1892,7 @@ void EvalState::createBaseEnv()
     mkList(v, searchPath.size());
     int n = 0;
     for (auto & i : searchPath) {
-        v2 = v.list.elems[n++] = allocValue();
+        v2 = v.listElems()[n++] = allocValue();
         mkAttrs(*v2, 2);
         mkString(*allocAttr(*v2, symbols.create("path")), i.second);
         mkString(*allocAttr(*v2, symbols.create("prefix")), i.first);
@@ -1693,6 +1900,10 @@ void EvalState::createBaseEnv()
     }
     addConstant("__nixPath", v);
 
+    if (RegisterPrimOp::primOps)
+        for (auto & primOp : *RegisterPrimOp::primOps)
+            addPrimOp(std::get<0>(primOp), std::get<1>(primOp), std::get<2>(primOp));
+
     /* Now that we've added all primops, sort the `builtins' set,
        because attribute lookups expect it to be sorted. */
     baseEnv.values[0]->attrs->sort();