about summary refs log tree commit diff
path: root/src/libexpr
diff options
context:
space:
mode:
Diffstat (limited to 'src/libexpr')
-rw-r--r--src/libexpr/attr-path.cc11
-rw-r--r--src/libexpr/attr-set.cc63
-rw-r--r--src/libexpr/attr-set.hh82
-rw-r--r--src/libexpr/common-opts.cc2
-rw-r--r--src/libexpr/common-opts.hh2
-rw-r--r--src/libexpr/download.cc236
-rw-r--r--src/libexpr/download.hh22
-rw-r--r--src/libexpr/eval-inline.hh13
-rw-r--r--src/libexpr/eval.cc371
-rw-r--r--src/libexpr/eval.hh126
-rw-r--r--src/libexpr/get-drvs.cc86
-rw-r--r--src/libexpr/get-drvs.hh6
-rw-r--r--src/libexpr/json-to-value.cc26
-rw-r--r--src/libexpr/lexer.l72
-rw-r--r--src/libexpr/local.mk9
-rw-r--r--src/libexpr/names.cc4
-rw-r--r--src/libexpr/nixexpr.cc106
-rw-r--r--src/libexpr/nixexpr.hh10
-rw-r--r--src/libexpr/parser.y122
-rw-r--r--src/libexpr/primops.cc529
-rw-r--r--src/libexpr/primops.hh15
-rw-r--r--src/libexpr/primops/fetchgit.cc82
-rw-r--r--src/libexpr/primops/fetchgit.hh14
-rw-r--r--src/libexpr/value-to-json.cc38
-rw-r--r--src/libexpr/value-to-json.hh13
-rw-r--r--src/libexpr/value-to-xml.cc50
-rw-r--r--src/libexpr/value.hh40
27 files changed, 1273 insertions, 877 deletions
diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc
index fdd61a5fd375..55379f94b189 100644
--- a/src/libexpr/attr-path.cc
+++ b/src/libexpr/attr-path.cc
@@ -42,11 +42,10 @@ Value * findAlongAttrPath(EvalState & state, const string & attrPath,
 
     Value * v = &vIn;
 
-    foreach (Strings::iterator, i, tokens) {
+    for (auto & attr : tokens) {
 
-        /* Is *i an index (integer) or a normal attribute name? */
+        /* Is i an index (integer) or a normal attribute name? */
         enum { apAttr, apIndex } apType = apAttr;
-        string attr = *i;
         unsigned int attrIndex;
         if (string2Int(attr, attrIndex)) apType = apIndex;
 
@@ -77,15 +76,15 @@ Value * findAlongAttrPath(EvalState & state, const string & attrPath,
 
         else if (apType == apIndex) {
 
-            if (v->type != tList)
+            if (!v->isList())
                 throw TypeError(
                     format("the expression selected by the selection path ‘%1%’ should be a list but is %2%")
                     % attrPath % showType(*v));
 
-            if (attrIndex >= v->list.length)
+            if (attrIndex >= v->listSize())
                 throw Error(format("list index %1% in selection path ‘%2%’ is out of range") % attrIndex % attrPath);
 
-            v = v->list.elems[attrIndex];
+            v = v->listElems()[attrIndex];
         }
 
     }
diff --git a/src/libexpr/attr-set.cc b/src/libexpr/attr-set.cc
new file mode 100644
index 000000000000..910428c02686
--- /dev/null
+++ b/src/libexpr/attr-set.cc
@@ -0,0 +1,63 @@
+#include "attr-set.hh"
+#include "eval.hh"
+
+#include <algorithm>
+
+
+namespace nix {
+
+
+static void * allocBytes(size_t n)
+{
+    void * p;
+#if HAVE_BOEHMGC
+    p = GC_malloc(n);
+#else
+    p = malloc(n);
+#endif
+    if (!p) throw std::bad_alloc();
+    return p;
+}
+
+
+/* Allocate a new array of attributes for an attribute set with a specific
+   capacity. The space is implicitly reserved after the Bindings
+   structure. */
+Bindings * EvalState::allocBindings(Bindings::size_t capacity)
+{
+    return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings(capacity);
+}
+
+
+void EvalState::mkAttrs(Value & v, unsigned int capacity)
+{
+    if (capacity == 0) {
+        v = vEmptySet;
+        return;
+    }
+    clearValue(v);
+    v.type = tAttrs;
+    v.attrs = allocBindings(capacity);
+    nrAttrsets++;
+    nrAttrsInAttrsets += capacity;
+}
+
+
+/* Create a new attribute named 'name' on an existing attribute set stored
+   in 'vAttrs' and return the newly allocated Value which is associated with
+   this attribute. */
+Value * EvalState::allocAttr(Value & vAttrs, const Symbol & name)
+{
+    Value * v = allocValue();
+    vAttrs.attrs->push_back(Attr(name, v));
+    return v;
+}
+
+
+void Bindings::sort()
+{
+    std::sort(begin(), end());
+}
+
+
+}
diff --git a/src/libexpr/attr-set.hh b/src/libexpr/attr-set.hh
new file mode 100644
index 000000000000..7cf6a9c58086
--- /dev/null
+++ b/src/libexpr/attr-set.hh
@@ -0,0 +1,82 @@
+#pragma once
+
+#include "nixexpr.hh"
+#include "symbol-table.hh"
+
+#include <algorithm>
+
+namespace nix {
+
+
+class EvalState;
+struct Value;
+
+/* Map one attribute name to its value. */
+struct Attr
+{
+    Symbol name;
+    Value * value;
+    Pos * pos;
+    Attr(Symbol name, Value * value, Pos * pos = &noPos)
+        : name(name), value(value), pos(pos) { };
+    Attr() : pos(&noPos) { };
+    bool operator < (const Attr & a) const
+    {
+        return name < a.name;
+    }
+};
+
+/* Bindings contains all the attributes of an attribute set. It is defined
+   by its size and its capacity, the capacity being the number of Attr
+   elements allocated after this structure, while the size corresponds to
+   the number of elements already inserted in this structure. */
+class Bindings
+{
+public:
+    typedef uint32_t size_t;
+
+private:
+    size_t size_, capacity_;
+    Attr attrs[0];
+
+    Bindings(size_t capacity) : size_(0), capacity_(capacity) { }
+    Bindings(const Bindings & bindings) = delete;
+
+public:
+    size_t size() const { return size_; }
+
+    bool empty() const { return !size_; }
+
+    typedef Attr * iterator;
+
+    void push_back(const Attr & attr)
+    {
+        assert(size_ < capacity_);
+        attrs[size_++] = attr;
+    }
+
+    iterator find(const Symbol & name)
+    {
+        Attr key(name, 0);
+        iterator i = std::lower_bound(begin(), end(), key);
+        if (i != end() && i->name == name) return i;
+        return end();
+    }
+
+    iterator begin() { return &attrs[0]; }
+    iterator end() { return &attrs[size_]; }
+
+    Attr & operator[](size_t pos)
+    {
+        return attrs[pos];
+    }
+
+    void sort();
+
+    size_t capacity() { return capacity_; }
+
+    friend class EvalState;
+};
+
+
+}
diff --git a/src/libexpr/common-opts.cc b/src/libexpr/common-opts.cc
index 13760490d9c4..8a7989aac663 100644
--- a/src/libexpr/common-opts.cc
+++ b/src/libexpr/common-opts.cc
@@ -55,7 +55,7 @@ bool parseSearchPathArg(Strings::iterator & i,
 Path lookupFileArg(EvalState & state, string s)
 {
     if (isUri(s))
-        return downloadFileCached(s, true);
+        return makeDownloader()->downloadCached(state.store, s, true);
     else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') {
         Path p = s.substr(1, s.size() - 2);
         return state.findFile(p);
diff --git a/src/libexpr/common-opts.hh b/src/libexpr/common-opts.hh
index be0f40202430..cb2732d6fe7e 100644
--- a/src/libexpr/common-opts.hh
+++ b/src/libexpr/common-opts.hh
@@ -4,6 +4,8 @@
 
 namespace nix {
 
+class Store;
+
 /* Some common option parsing between nix-env and nix-instantiate. */
 bool parseAutoArgs(Strings::iterator & i,
     const Strings::iterator & argsEnd, std::map<string, string> & res);
diff --git a/src/libexpr/download.cc b/src/libexpr/download.cc
deleted file mode 100644
index 9bf3e13aa9da..000000000000
--- a/src/libexpr/download.cc
+++ /dev/null
@@ -1,236 +0,0 @@
-#include "download.hh"
-#include "util.hh"
-#include "globals.hh"
-#include "hash.hh"
-#include "store-api.hh"
-
-#include <curl/curl.h>
-
-namespace nix {
-
-struct Curl
-{
-    CURL * curl;
-    string data;
-    string etag, status, expectedETag;
-
-    struct curl_slist * requestHeaders;
-
-    static size_t writeCallback(void * contents, size_t size, size_t nmemb, void * userp)
-    {
-        Curl & c(* (Curl *) userp);
-        size_t realSize = size * nmemb;
-        c.data.append((char *) contents, realSize);
-        return realSize;
-    }
-
-    static size_t headerCallback(void * contents, size_t size, size_t nmemb, void * userp)
-    {
-        Curl & c(* (Curl *) userp);
-        size_t realSize = size * nmemb;
-        string line = string((char *) contents, realSize);
-        printMsg(lvlVomit, format("got header: %1%") % trim(line));
-        if (line.compare(0, 5, "HTTP/") == 0) { // new response starts
-            c.etag = "";
-            auto ss = tokenizeString<vector<string>>(line, " ");
-            c.status = ss.size() >= 2 ? ss[1] : "";
-        } else {
-            auto i = line.find(':');
-            if (i != string::npos) {
-                string name = trim(string(line, 0, i));
-                if (name == "ETag") { // FIXME: case
-                    c.etag = trim(string(line, i + 1));
-                    /* Hack to work around a GitHub bug: it sends
-                       ETags, but ignores If-None-Match. So if we get
-                       the expected ETag on a 200 response, then shut
-                       down the connection because we already have the
-                       data. */
-                    printMsg(lvlDebug, format("got ETag: %1%") % c.etag);
-                    if (c.etag == c.expectedETag && c.status == "200") {
-                        printMsg(lvlDebug, format("shutting down on 200 HTTP response with expected ETag"));
-                        return 0;
-                    }
-                }
-            }
-        }
-        return realSize;
-    }
-
-    static int progressCallback(void * clientp, double dltotal, double dlnow, double ultotal, double ulnow)
-    {
-        return _isInterrupted;
-    }
-
-    Curl()
-    {
-        requestHeaders = 0;
-
-        curl = curl_easy_init();
-        if (!curl) throw Error("unable to initialize curl");
-
-        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
-        curl_easy_setopt(curl, CURLOPT_CAINFO, getEnv("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt").c_str());
-        curl_easy_setopt(curl, CURLOPT_USERAGENT, ("Nix/" + nixVersion).c_str());
-        curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
-
-        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
-        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &curl);
-
-        curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headerCallback);
-        curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *) &curl);
-
-        curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progressCallback);
-        curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
-    }
-
-    ~Curl()
-    {
-        if (curl) curl_easy_cleanup(curl);
-        if (requestHeaders) curl_slist_free_all(requestHeaders);
-    }
-
-    bool fetch(const string & url, const string & expectedETag = "")
-    {
-        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
-
-        data.clear();
-
-        if (requestHeaders) {
-            curl_slist_free_all(requestHeaders);
-            requestHeaders = 0;
-        }
-
-        if (!expectedETag.empty()) {
-            this->expectedETag = expectedETag;
-            requestHeaders = curl_slist_append(requestHeaders, ("If-None-Match: " + expectedETag).c_str());
-        }
-
-        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, requestHeaders);
-
-        CURLcode res = curl_easy_perform(curl);
-        checkInterrupt();
-        if (res == CURLE_WRITE_ERROR && etag == expectedETag) return false;
-        if (res != CURLE_OK)
-            throw DownloadError(format("unable to download ‘%1%’: %2% (%3%)")
-                % url % curl_easy_strerror(res) % res);
-
-        long httpStatus = 0;
-        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpStatus);
-        if (httpStatus == 304) return false;
-
-        return true;
-    }
-};
-
-
-DownloadResult downloadFile(string url, string expectedETag)
-{
-    DownloadResult res;
-    Curl curl;
-    if (curl.fetch(url, expectedETag)) {
-        res.cached = false;
-        res.data = curl.data;
-    } else
-        res.cached = true;
-    res.etag = curl.etag;
-    return res;
-}
-
-
-Path downloadFileCached(const string & url, bool unpack)
-{
-    Path cacheDir = getEnv("XDG_CACHE_HOME", getEnv("HOME", "") + "/.cache") + "/nix/tarballs";
-    createDirs(cacheDir);
-
-    string urlHash = printHash32(hashString(htSHA256, url));
-
-    Path dataFile = cacheDir + "/" + urlHash + ".info";
-    Path fileLink = cacheDir + "/" + urlHash + "-file";
-
-    Path storePath;
-
-    string expectedETag;
-
-    int ttl = settings.get("tarball-ttl", 60 * 60);
-    bool skip = false;
-
-    if (pathExists(fileLink) && pathExists(dataFile)) {
-        storePath = readLink(fileLink);
-        store->addTempRoot(storePath);
-        if (store->isValidPath(storePath)) {
-            auto ss = tokenizeString<vector<string>>(readFile(dataFile), "\n");
-            if (ss.size() >= 3 && ss[0] == url) {
-                time_t lastChecked;
-                if (string2Int(ss[2], lastChecked) && lastChecked + ttl >= time(0))
-                    skip = true;
-                else if (!ss[1].empty()) {
-                    printMsg(lvlDebug, format("verifying previous ETag ‘%1%’") % ss[1]);
-                    expectedETag = ss[1];
-                }
-            }
-        } else
-            storePath = "";
-    }
-
-    string name;
-    auto p = url.rfind('/');
-    if (p != string::npos) name = string(url, p + 1);
-
-    if (!skip) {
-
-        if (storePath.empty())
-            printMsg(lvlInfo, format("downloading ‘%1%’...") % url);
-        else
-            printMsg(lvlInfo, format("checking ‘%1%’...") % url);
-
-        try {
-            auto res = downloadFile(url, expectedETag);
-
-            if (!res.cached)
-                storePath = store->addTextToStore(name, res.data, PathSet(), false);
-
-            assert(!storePath.empty());
-            replaceSymlink(storePath, fileLink);
-
-            writeFile(dataFile, url + "\n" + res.etag + "\n" + int2String(time(0)) + "\n");
-        } catch (DownloadError & e) {
-            if (storePath.empty()) throw;
-            printMsg(lvlError, format("warning: %1%; using cached result") % e.msg());
-        }
-    }
-
-    if (unpack) {
-        Path unpackedLink = cacheDir + "/" + baseNameOf(storePath) + "-unpacked";
-        Path unpackedStorePath;
-        if (pathExists(unpackedLink)) {
-            unpackedStorePath = readLink(unpackedLink);
-            store->addTempRoot(unpackedStorePath);
-            if (!store->isValidPath(unpackedStorePath))
-                unpackedStorePath = "";
-        }
-        if (unpackedStorePath.empty()) {
-            printMsg(lvlInfo, format("unpacking ‘%1%’...") % url);
-            Path tmpDir = createTempDir();
-            AutoDelete autoDelete(tmpDir, true);
-            // FIXME: this requires GNU tar for decompression.
-            runProgram("tar", true, {"xf", storePath, "-C", tmpDir, "--strip-components", "1"}, "");
-            unpackedStorePath = store->addToStore(name, tmpDir, true, htSHA256, defaultPathFilter, false);
-        }
-        replaceSymlink(unpackedStorePath, unpackedLink);
-        return unpackedStorePath;
-    }
-
-    return storePath;
-}
-
-
-bool isUri(const string & s)
-{
-    size_t pos = s.find("://");
-    if (pos == string::npos) return false;
-    string scheme(s, 0, pos);
-    return scheme == "http" || scheme == "https" || scheme == "file";
-}
-
-
-}
diff --git a/src/libexpr/download.hh b/src/libexpr/download.hh
deleted file mode 100644
index 28c9117e4227..000000000000
--- a/src/libexpr/download.hh
+++ /dev/null
@@ -1,22 +0,0 @@
-#pragma once
-
-#include "types.hh"
-#include <string>
-
-namespace nix {
-
-struct DownloadResult
-{
-    bool cached;
-    string data, etag;
-};
-
-DownloadResult downloadFile(string url, string expectedETag = "");
-
-Path downloadFileCached(const string & url, bool unpack);
-
-MakeError(DownloadError, Error)
-
-bool isUri(const string & s);
-
-}
diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh
index c275f7ba83e8..0748fbd3f3e1 100644
--- a/src/libexpr/eval-inline.hh
+++ b/src/libexpr/eval-inline.hh
@@ -7,9 +7,9 @@
 
 namespace nix {
 
-LocalNoInlineNoReturn(void throwEvalError(const char * s))
+LocalNoInlineNoReturn(void throwEvalError(const char * s, const Pos & pos))
 {
-    throw EvalError(s);
+    throw EvalError(format(s) % pos);
 }
 
 LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v))
@@ -24,7 +24,7 @@ LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v, const
 }
 
 
-void EvalState::forceValue(Value & v)
+void EvalState::forceValue(Value & v, const Pos & pos)
 {
     if (v.type == tThunk) {
         Env * env = v.thunk.env;
@@ -43,7 +43,7 @@ void EvalState::forceValue(Value & v)
     else if (v.type == tApp)
         callFunction(*v.app.left, *v.app.right, v, noPos);
     else if (v.type == tBlackhole)
-        throwEvalError("infinite recursion encountered");
+        throwEvalError("infinite recursion encountered, at %1%", pos);
 }
 
 
@@ -66,7 +66,7 @@ inline void EvalState::forceAttrs(Value & v, const Pos & pos)
 inline void EvalState::forceList(Value & v)
 {
     forceValue(v);
-    if (v.type != tList)
+    if (!v.isList())
         throwTypeError("value is %1% while a list was expected", v);
 }
 
@@ -74,9 +74,8 @@ inline void EvalState::forceList(Value & v)
 inline void EvalState::forceList(Value & v, const Pos & pos)
 {
     forceValue(v);
-    if (v.type != tList)
+    if (!v.isList())
         throwTypeError("value is %1% while a list was expected, at %2%", v, pos);
 }
 
-
 }
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index d61ee7e80795..5a6428ca6b6f 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -5,6 +5,7 @@
 #include "derivations.hh"
 #include "globals.hh"
 #include "eval-inline.hh"
+#include "download.hh"
 
 #include <algorithm>
 #include <cstring>
@@ -55,14 +56,10 @@ static void * allocBytes(size_t n)
 }
 
 
-void Bindings::sort()
-{
-    std::sort(begin(), end());
-}
-
-
 static void printValue(std::ostream & str, std::set<const Value *> & active, const Value & v)
 {
+    checkInterrupt();
+
     if (active.find(&v) != active.end()) {
         str << "<CYCLE>";
         return;
@@ -96,8 +93,8 @@ static void printValue(std::ostream & str, std::set<const Value *> & active, con
         str << "{ ";
         typedef std::map<string, Value *> Sorted;
         Sorted sorted;
-        foreach (Bindings::iterator, i, *v.attrs)
-            sorted[i->name] = i->value;
+        for (auto & i : *v.attrs)
+            sorted[i.name] = i.value;
         for (auto & i : sorted) {
             str << i.first << " = ";
             printValue(str, active, *i.second);
@@ -106,10 +103,12 @@ static void printValue(std::ostream & str, std::set<const Value *> & active, con
         str << "}";
         break;
     }
-    case tList:
+    case tList1:
+    case tList2:
+    case tListN:
         str << "[ ";
-        for (unsigned int n = 0; n < v.list.length; ++n) {
-            printValue(str, active, *v.list.elems[n]);
+        for (unsigned int n = 0; n < v.listSize(); ++n) {
+            printValue(str, active, *v.listElems()[n]);
             str << " ";
         }
         str << "]";
@@ -130,6 +129,9 @@ static void printValue(std::ostream & str, std::set<const Value *> & active, con
     case tExternal:
         str << *v.external;
         break;
+    case tFloat:
+        str << v.fpoint;
+        break;
     default:
         throw Error("invalid value");
     }
@@ -155,7 +157,7 @@ string showType(const Value & v)
         case tPath: return "a path";
         case tNull: return "null";
         case tAttrs: return "a set";
-        case tList: return "a list";
+        case tList1: case tList2: case tListN: return "a list";
         case tThunk: return "a thunk";
         case tApp: return "a function application";
         case tLambda: return "a function";
@@ -163,6 +165,7 @@ string showType(const Value & v)
         case tPrimOp: return "a built-in function";
         case tPrimOpApp: return "a partially applied built-in function";
         case tExternal: return v.external->showType();
+        case tFloat: return "a float";
     }
     abort();
 }
@@ -236,17 +239,43 @@ void initGC()
 
 /* Very hacky way to parse $NIX_PATH, which is colon-separated, but
    can contain URLs (e.g. "nixpkgs=https://bla...:foo=https://"). */
-static Strings parseNixPath(const string & in)
+static Strings parseNixPath(const string & s)
 {
-    string marker = "\001//";
-    auto res = tokenizeString<Strings>(replaceStrings(in, "://", marker), ":");
-    for (auto & s : res)
-        s = replaceStrings(s, marker, "://");
+    Strings res;
+
+    auto p = s.begin();
+
+    while (p != s.end()) {
+        auto start = p;
+        auto start2 = p;
+
+        while (p != s.end() && *p != ':') {
+            if (*p == '=') start2 = p + 1;
+            ++p;
+        }
+
+        if (p == s.end()) {
+            if (p != start) res.push_back(std::string(start, p));
+            break;
+        }
+
+        if (*p == ':') {
+            if (isUri(std::string(start2, s.end()))) {
+                ++p;
+                while (p != s.end() && *p != ':') ++p;
+            }
+            res.push_back(std::string(start, p));
+            if (p == s.end()) break;
+        }
+
+        ++p;
+    }
+
     return res;
 }
 
 
-EvalState::EvalState(const Strings & _searchPath)
+EvalState::EvalState(const Strings & _searchPath, ref<Store> store)
     : sWith(symbols.create("<with>"))
     , sOutPath(symbols.create("outPath"))
     , sDrvPath(symbols.create("drvPath"))
@@ -263,14 +292,11 @@ EvalState::EvalState(const Strings & _searchPath)
     , sLine(symbols.create("line"))
     , sColumn(symbols.create("column"))
     , sFunctor(symbols.create("__functor"))
-    , repair(false)
+    , sToString(symbols.create("__toString"))
+    , store(store)
     , baseEnv(allocEnv(128))
     , staticBaseEnv(false, 0)
-    , baseEnvDispl(0)
 {
-    nrEnvs = nrValuesInEnvs = nrValues = nrListElems = 0;
-    nrAttrsets = nrAttrsInAttrsets = nrOpUpdates = nrOpUpdateValuesCopied = 0;
-    nrListConcats = nrPrimOpCalls = nrFunctionCalls = 0;
     countCalls = getEnv("NIX_COUNT_CALLS", "0") != "0";
 
     restricted = settings.get("restrict-eval", false);
@@ -279,10 +305,14 @@ EvalState::EvalState(const Strings & _searchPath)
 
     /* Initialise the Nix expression search path. */
     Strings paths = parseNixPath(getEnv("NIX_PATH", ""));
-    for (auto & i : _searchPath) addToSearchPath(i, true);
+    for (auto & i : _searchPath) addToSearchPath(i);
     for (auto & i : paths) addToSearchPath(i);
     addToSearchPath("nix=" + settings.nixDataDir + "/nix/corepkgs");
 
+    clearValue(vEmptySet);
+    vEmptySet.type = tAttrs;
+    vEmptySet.attrs = allocBindings(0);
+
     createBaseEnv();
 }
 
@@ -298,11 +328,15 @@ Path EvalState::checkSourcePath(const Path & path_)
     if (!restricted) return path_;
 
     /* Resolve symlinks. */
+    debug(format("checking access to ‘%s’") % path_);
     Path path = canonPath(path_, true);
 
-    for (auto & i : searchPath)
-        if (path == i.second || isInDir(path, i.second))
+    for (auto & i : searchPath) {
+        auto r = resolveSearchPathElem(i);
+        if (!r.first) continue;
+        if (path == r.second || isInDir(path, r.second))
             return path;
+    }
 
     /* To support import-from-derivation, allow access to anything in
        the store. FIXME: only allow access to paths that have been
@@ -361,11 +395,6 @@ LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2))
     throw EvalError(format(s) % s2);
 }
 
-LocalNoInlineNoReturn(void throwEvalError(const char * s, const Pos & pos))
-{
-    throw EvalError(format(s) % pos);
-}
-
 LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const Pos & pos))
 {
     throw EvalError(format(s) % s2 % pos);
@@ -440,8 +469,8 @@ void mkString(Value & v, const string & s, const PathSet & context)
         unsigned int n = 0;
         v.string.context = (const char * *)
             allocBytes((context.size() + 1) * sizeof(char *));
-        foreach (PathSet::const_iterator, i, context)
-            v.string.context[n++] = dupString(i->c_str());
+        for (auto & i : context)
+            v.string.context[n++] = dupString(i.c_str());
         v.string.context[n] = 0;
     }
 }
@@ -503,37 +532,19 @@ Env & EvalState::allocEnv(unsigned int size)
 }
 
 
-Value * EvalState::allocAttr(Value & vAttrs, const Symbol & name)
-{
-    Value * v = allocValue();
-    vAttrs.attrs->push_back(Attr(name, v));
-    return v;
-}
-
-
-Bindings * EvalState::allocBindings(Bindings::size_t capacity)
-{
-    return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings(capacity);
-}
-
-
-void EvalState::mkList(Value & v, unsigned int length)
-{
-    clearValue(v);
-    v.type = tList;
-    v.list.length = length;
-    v.list.elems = length ? (Value * *) allocBytes(length * sizeof(Value *)) : 0;
-    nrListElems += length;
-}
-
-
-void EvalState::mkAttrs(Value & v, unsigned int expected)
+void EvalState::mkList(Value & v, unsigned int size)
 {
     clearValue(v);
-    v.type = tAttrs;
-    v.attrs = allocBindings(expected);
-    nrAttrsets++;
-    nrAttrsInAttrsets += expected;
+    if (size == 1)
+        v.type = tList1;
+    else if (size == 2)
+        v.type = tList2;
+    else {
+        v.type = tListN;
+        v.bigList.size = size;
+        v.bigList.elems = size ? (Value * *) allocBytes(size * sizeof(Value *)) : 0;
+    }
+    nrListElems += size;
 }
 
 
@@ -603,6 +614,12 @@ Value * ExprInt::maybeThunk(EvalState & state, Env & env)
     return &v;
 }
 
+Value * ExprFloat::maybeThunk(EvalState & state, Env & env)
+{
+    nrAvoided++;
+    return &v;
+}
+
 Value * ExprPath::maybeThunk(EvalState & state, Env & env)
 {
     nrAvoided++;
@@ -624,7 +641,7 @@ void EvalState::evalFile(const Path & path, Value & v)
         return;
     }
 
-    startNest(nest, lvlTalkative, format("evaluating file ‘%1%’") % path2);
+    Activity act(*logger, lvlTalkative, format("evaluating file ‘%1%’") % path2);
     Expr * e = parseExprFromFile(checkSourcePath(path2));
     try {
         eval(e, v);
@@ -690,6 +707,11 @@ void ExprInt::eval(EvalState & state, Env & env, Value & v)
 }
 
 
+void ExprFloat::eval(EvalState & state, Env & env, Value & v)
+{
+    v = this->v;
+}
+
 void ExprString::eval(EvalState & state, Env & env, Value & v)
 {
     v = this->v;
@@ -721,15 +743,15 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
            environment, while the inherited attributes are evaluated
            in the original environment. */
         unsigned int displ = 0;
-        foreach (AttrDefs::iterator, i, attrs) {
+        for (auto & i : attrs) {
             Value * vAttr;
-            if (hasOverrides && !i->second.inherited) {
+            if (hasOverrides && !i.second.inherited) {
                 vAttr = state.allocValue();
-                mkThunk(*vAttr, env2, i->second.e);
+                mkThunk(*vAttr, env2, i.second.e);
             } else
-                vAttr = i->second.e->maybeThunk(state, i->second.inherited ? env : env2);
+                vAttr = i.second.e->maybeThunk(state, i.second.inherited ? env : env2);
             env2.values[displ++] = vAttr;
-            v.attrs->push_back(Attr(i->first, vAttr, &i->second.pos));
+            v.attrs->push_back(Attr(i.first, vAttr, &i.second.pos));
         }
 
         /* If the rec contains an attribute called `__overrides', then
@@ -760,25 +782,25 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
     }
 
     else
-        foreach (AttrDefs::iterator, i, attrs)
-            v.attrs->push_back(Attr(i->first, i->second.e->maybeThunk(state, env), &i->second.pos));
+        for (auto & i : attrs)
+            v.attrs->push_back(Attr(i.first, i.second.e->maybeThunk(state, env), &i.second.pos));
 
     /* Dynamic attrs apply *after* rec and __overrides. */
-    foreach (DynamicAttrDefs::iterator, i, dynamicAttrs) {
+    for (auto & i : dynamicAttrs) {
         Value nameVal;
-        i->nameExpr->eval(state, *dynamicEnv, nameVal);
-        state.forceValue(nameVal);
+        i.nameExpr->eval(state, *dynamicEnv, nameVal);
+        state.forceValue(nameVal, i.pos);
         if (nameVal.type == tNull)
             continue;
         state.forceStringNoCtx(nameVal);
         Symbol nameSym = state.symbols.create(nameVal.string.s);
         Bindings::iterator j = v.attrs->find(nameSym);
         if (j != v.attrs->end())
-            throwEvalError("dynamic attribute ‘%1%’ at %2% already defined at %3%", nameSym, i->pos, *j->pos);
+            throwEvalError("dynamic attribute ‘%1%’ at %2% already defined at %3%", nameSym, i.pos, *j->pos);
 
-        i->valueExpr->setName(nameSym);
+        i.valueExpr->setName(nameSym);
         /* Keep sorted order so find can catch duplicates */
-        v.attrs->push_back(Attr(nameSym, i->valueExpr->maybeThunk(state, *dynamicEnv), &i->pos));
+        v.attrs->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), &i.pos));
         v.attrs->sort(); // FIXME: inefficient
     }
 }
@@ -795,8 +817,8 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
        while the inherited attributes are evaluated in the original
        environment. */
     unsigned int displ = 0;
-    foreach (ExprAttrs::AttrDefs::iterator, i, attrs->attrs)
-        env2.values[displ++] = i->second.e->maybeThunk(state, i->second.inherited ? env : env2);
+    for (auto & i : attrs->attrs)
+        env2.values[displ++] = i.second.e->maybeThunk(state, i.second.inherited ? env : env2);
 
     body->eval(state, env2, v);
 }
@@ -805,15 +827,15 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
 void ExprList::eval(EvalState & state, Env & env, Value & v)
 {
     state.mkList(v, elems.size());
-    for (unsigned int n = 0; n < v.list.length; ++n)
-        v.list.elems[n] = elems[n]->maybeThunk(state, env);
+    for (unsigned int n = 0; n < elems.size(); ++n)
+        v.listElems()[n] = elems[n]->maybeThunk(state, env);
 }
 
 
 void ExprVar::eval(EvalState & state, Env & env, Value & v)
 {
     Value * v2 = state.lookupVar(&env, *this, false);
-    state.forceValue(*v2);
+    state.forceValue(*v2, pos);
     v = *v2;
 }
 
@@ -847,12 +869,12 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
 
     try {
 
-        foreach (AttrPath::const_iterator, i, attrPath) {
+        for (auto & i : attrPath) {
             nrLookups++;
             Bindings::iterator j;
-            Symbol name = getName(*i, state, env);
+            Symbol name = getName(i, state, env);
             if (def) {
-                state.forceValue(*vAttrs);
+                state.forceValue(*vAttrs, pos);
                 if (vAttrs->type != tAttrs ||
                     (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
                 {
@@ -869,7 +891,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
             if (state.countCalls && pos2) state.attrSelects[*pos2]++;
         }
 
-        state.forceValue(*vAttrs);
+        state.forceValue(*vAttrs, ( pos2 != NULL ? *pos2 : this->pos ) );
 
     } catch (Error & e) {
         if (pos2 && pos2->file != state.sDerivationNix)
@@ -889,10 +911,10 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
 
     e->eval(state, env, vTmp);
 
-    foreach (AttrPath::const_iterator, i, attrPath) {
+    for (auto & i : attrPath) {
         state.forceValue(*vAttrs);
         Bindings::iterator j;
-        Symbol name = getName(*i, state, env);
+        Symbol name = getName(i, state, env);
         if (vAttrs->type != tAttrs ||
             (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
         {
@@ -971,10 +993,10 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po
     if (fun.type == tAttrs) {
       auto found = fun.attrs->find(sFunctor);
       if (found != fun.attrs->end()) {
-        forceValue(*found->value);
+        forceValue(*found->value, pos);
         Value * v2 = allocValue();
         callFunction(*found->value, fun, *v2, pos);
-        forceValue(*v2);
+        forceValue(*v2, pos);
         return callFunction(*v2, arg, v, pos);
       }
     }
@@ -1005,12 +1027,12 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po
            there is no matching actual argument but the formal
            argument has a default, use the default. */
         unsigned int attrsUsed = 0;
-        foreach (Formals::Formals_::iterator, i, lambda.formals->formals) {
-            Bindings::iterator j = arg.attrs->find(i->name);
+        for (auto & i : lambda.formals->formals) {
+            Bindings::iterator j = arg.attrs->find(i.name);
             if (j == arg.attrs->end()) {
-                if (!i->def) throwTypeError("%1% called without required argument ‘%2%’, at %3%",
-                    lambda, i->name, pos);
-                env2.values[displ++] = i->def->maybeThunk(*this, env2);
+                if (!i.def) throwTypeError("%1% called without required argument ‘%2%’, at %3%",
+                    lambda, i.name, pos);
+                env2.values[displ++] = i.def->maybeThunk(*this, env2);
             } else {
                 attrsUsed++;
                 env2.values[displ++] = j->value;
@@ -1022,9 +1044,9 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po
         if (!lambda.formals->ellipsis && attrsUsed != arg.attrs->size()) {
             /* Nope, so show the first unexpected argument to the
                user. */
-            foreach (Bindings::iterator, i, *arg.attrs)
-                if (lambda.formals->argNames.find(i->name) == lambda.formals->argNames.end())
-                    throwTypeError("%1% called with unexpected argument ‘%2%’, at %3%", lambda, i->name, pos);
+            for (auto & i : *arg.attrs)
+                if (lambda.formals->argNames.find(i.name) == lambda.formals->argNames.end())
+                    throwTypeError("%1% called with unexpected argument ‘%2%’, at %3%", lambda, i.name, pos);
             abort(); // can't happen
         }
     }
@@ -1058,6 +1080,17 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
 {
     forceValue(fun);
 
+    if (fun.type == tAttrs) {
+        auto found = fun.attrs->find(sFunctor);
+        if (found != fun.attrs->end()) {
+            forceValue(*found->value);
+            Value * v = allocValue();
+            callFunction(*found->value, fun, *v, noPos);
+            forceValue(*v);
+            return autoCallFunction(args, *v, res);
+        }
+    }
+
     if (fun.type != tLambda || !fun.lambda.fun->matchAttrs) {
         res = fun;
         return;
@@ -1066,12 +1099,12 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
     Value * actualArgs = allocValue();
     mkAttrs(*actualArgs, fun.lambda.fun->formals->formals.size());
 
-    foreach (Formals::Formals_::iterator, i, fun.lambda.fun->formals->formals) {
-        Bindings::iterator j = args.find(i->name);
+    for (auto & i : fun.lambda.fun->formals->formals) {
+        Bindings::iterator j = args.find(i.name);
         if (j != args.end())
             actualArgs->attrs->push_back(*j);
-        else if (!i->def)
-            throwTypeError("cannot auto-call a function that has an argument without a default value (‘%1%’)", i->name);
+        else if (!i.def)
+            throwTypeError("cannot auto-call a function that has an argument without a default value (‘%1%’)", i.name);
     }
 
     actualArgs->attrs->sort();
@@ -1199,20 +1232,21 @@ void EvalState::concatLists(Value & v, unsigned int nrLists, Value * * lists, co
     unsigned int len = 0;
     for (unsigned int n = 0; n < nrLists; ++n) {
         forceList(*lists[n], pos);
-        unsigned int l = lists[n]->list.length;
+        unsigned int l = lists[n]->listSize();
         len += l;
         if (l) nonEmpty = lists[n];
     }
 
-    if (nonEmpty && len == nonEmpty->list.length) {
+    if (nonEmpty && len == nonEmpty->listSize()) {
         v = *nonEmpty;
         return;
     }
 
     mkList(v, len);
+    auto out = v.listElems();
     for (unsigned int n = 0, pos = 0; n < nrLists; ++n) {
-        unsigned int l = lists[n]->list.length;
-        memcpy(v.list.elems + pos, lists[n]->list.elems, l * sizeof(Value *));
+        unsigned int l = lists[n]->listSize();
+        memcpy(out + pos, lists[n]->listElems(), l * sizeof(Value *));
         pos += l;
     }
 }
@@ -1223,13 +1257,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
     PathSet context;
     std::ostringstream s;
     NixInt n = 0;
+    NixFloat nf = 0;
 
     bool first = !forceString;
     ValueType firstType = tString;
 
-    foreach (vector<Expr *>::iterator, i, *es) {
+    for (auto & i : *es) {
         Value vTmp;
-        (*i)->eval(state, env, vTmp);
+        i->eval(state, env, vTmp);
 
         /* If the first element is a path, then the result will also
            be a path, we don't copy anything (yet - that's done later,
@@ -1241,15 +1276,30 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
         }
 
         if (firstType == tInt) {
-            if (vTmp.type != tInt)
+            if (vTmp.type == tInt) {
+                n += vTmp.integer;
+            } else if (vTmp.type == tFloat) {
+                // Upgrade the type from int to float;
+                firstType = tFloat;
+                nf = n;
+                nf += vTmp.fpoint;
+            } else
                 throwEvalError("cannot add %1% to an integer, at %2%", showType(vTmp), pos);
-            n += vTmp.integer;
+        } else if (firstType == tFloat) {
+            if (vTmp.type == tInt) {
+                nf += vTmp.integer;
+            } else if (vTmp.type == tFloat) {
+                nf += vTmp.fpoint;
+            } else
+                throwEvalError("cannot add %1% to a float, at %2%", showType(vTmp), pos);
         } else
             s << state.coerceToString(pos, vTmp, context, false, firstType == tString);
     }
 
     if (firstType == tInt)
         mkInt(v, n);
+    else if (firstType == tFloat)
+        mkFloat(v, nf);
     else if (firstType == tPath) {
         if (!context.empty())
             throwEvalError("a string that refers to a store path cannot be appended to a path, at %1%", pos);
@@ -1288,9 +1338,9 @@ void EvalState::forceValueDeep(Value & v)
                 }
         }
 
-        else if (v.type == tList) {
-            for (unsigned int n = 0; n < v.list.length; ++n)
-                recurse(*v.list.elems[n]);
+        else if (v.isList()) {
+            for (unsigned int n = 0; n < v.listSize(); ++n)
+                recurse(*v.listElems()[n]);
         }
     };
 
@@ -1300,13 +1350,24 @@ void EvalState::forceValueDeep(Value & v)
 
 NixInt EvalState::forceInt(Value & v, const Pos & pos)
 {
-    forceValue(v);
+    forceValue(v, pos);
     if (v.type != tInt)
         throwTypeError("value is %1% while an integer was expected, at %2%", v, pos);
     return v.integer;
 }
 
 
+NixFloat EvalState::forceFloat(Value & v, const Pos & pos)
+{
+    forceValue(v, pos);
+    if (v.type == tInt)
+        return v.integer;
+    else if (v.type != tFloat)
+        throwTypeError("value is %1% while a float was expected, at %2%", v, pos);
+    return v.fpoint;
+}
+
+
 bool EvalState::forceBool(Value & v)
 {
     forceValue(v);
@@ -1316,17 +1377,23 @@ bool EvalState::forceBool(Value & v)
 }
 
 
+bool EvalState::isFunctor(Value & fun)
+{
+    return fun.type == tAttrs && fun.attrs->find(sFunctor) != fun.attrs->end();
+}
+
+
 void EvalState::forceFunction(Value & v, const Pos & pos)
 {
     forceValue(v);
-    if (v.type != tLambda && v.type != tPrimOp && v.type != tPrimOpApp)
+    if (v.type != tLambda && v.type != tPrimOp && v.type != tPrimOpApp && !isFunctor(v))
         throwTypeError("value is %1% while a function was expected, at %2%", v, pos);
 }
 
 
 string EvalState::forceString(Value & v, const Pos & pos)
 {
-    forceValue(v);
+    forceValue(v, pos);
     if (v.type != tString) {
         if (pos)
             throwTypeError("value is %1% while a string was expected, at %2%", v, pos);
@@ -1397,7 +1464,14 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
     }
 
     if (v.type == tAttrs) {
-        Bindings::iterator i = v.attrs->find(sOutPath);
+        auto i = v.attrs->find(sToString);
+        if (i != v.attrs->end()) {
+            forceValue(*i->value, pos);
+            Value v1;
+            callFunction(*i->value, v, v1, pos);
+            return coerceToString(pos, v1, context, coerceMore, copyToStore);
+        }
+        i = v.attrs->find(sOutPath);
         if (i == v.attrs->end()) throwTypeError("cannot coerce a set to a string, at %1%", pos);
         return coerceToString(pos, *i->value, context, coerceMore, copyToStore);
     }
@@ -1411,17 +1485,18 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
            shell scripting convenience, just like `null'. */
         if (v.type == tBool && v.boolean) return "1";
         if (v.type == tBool && !v.boolean) return "";
-        if (v.type == tInt) return int2String(v.integer);
+        if (v.type == tInt) return std::to_string(v.integer);
+        if (v.type == tFloat) return std::to_string(v.fpoint);
         if (v.type == tNull) return "";
 
-        if (v.type == tList) {
+        if (v.isList()) {
             string result;
-            for (unsigned int n = 0; n < v.list.length; ++n) {
-                result += coerceToString(pos, *v.list.elems[n],
+            for (unsigned int n = 0; n < v.listSize(); ++n) {
+                result += coerceToString(pos, *v.listElems()[n],
                     context, coerceMore, copyToStore);
-                if (n < v.list.length - 1
+                if (n < v.listSize() - 1
                     /* !!! not quite correct */
-                    && (v.list.elems[n]->type != tList || v.list.elems[n]->list.length != 0))
+                    && (!v.listElems()[n]->isList() || v.listElems()[n]->listSize() != 0))
                     result += " ";
             }
             return result;
@@ -1473,6 +1548,13 @@ bool EvalState::eqValues(Value & v1, Value & v2)
        uniqList on a list of sets.)  Will remove this eventually. */
     if (&v1 == &v2) return true;
 
+    // Special case type-compatibility between float and int
+    if (v1.type == tInt && v2.type == tFloat)
+        return v1.integer == v2.fpoint;
+    if (v1.type == tFloat && v2.type == tInt)
+        return v1.fpoint == v2.integer;
+
+    // All other types are not compatible with each other.
     if (v1.type != v2.type) return false;
 
     switch (v1.type) {
@@ -1492,10 +1574,12 @@ bool EvalState::eqValues(Value & v1, Value & v2)
         case tNull:
             return true;
 
-        case tList:
-            if (v1.list.length != v2.list.length) return false;
-            for (unsigned int n = 0; n < v1.list.length; ++n)
-                if (!eqValues(*v1.list.elems[n], *v2.list.elems[n])) return false;
+        case tList1:
+        case tList2:
+        case tListN:
+            if (v1.listSize() != v2.listSize()) return false;
+            for (unsigned int n = 0; n < v1.listSize(); ++n)
+                if (!eqValues(*v1.listElems()[n], *v2.listElems()[n])) return false;
             return true;
 
         case tAttrs: {
@@ -1528,6 +1612,9 @@ bool EvalState::eqValues(Value & v1, Value & v2)
         case tExternal:
             return *v1.external == *v2.external;
 
+        case tFloat:
+            return v1.fpoint == v2.fpoint;
+
         default:
             throwEvalError("cannot compare %1% with %2%", showType(v1), showType(v2));
     }
@@ -1581,25 +1668,25 @@ void EvalState::printStats()
         printMsg(v, format("calls to %1% primops:") % primOpCalls.size());
         typedef std::multimap<unsigned int, Symbol> PrimOpCalls_;
         PrimOpCalls_ primOpCalls_;
-        foreach (PrimOpCalls::iterator, i, primOpCalls)
-            primOpCalls_.insert(std::pair<unsigned int, Symbol>(i->second, i->first));
-        foreach_reverse (PrimOpCalls_::reverse_iterator, i, primOpCalls_)
+        for (auto & i : primOpCalls)
+            primOpCalls_.insert(std::pair<unsigned int, Symbol>(i.second, i.first));
+        for (auto i = primOpCalls_.rbegin(); i != primOpCalls_.rend(); ++i)
             printMsg(v, format("%1$10d %2%") % i->first % i->second);
 
         printMsg(v, format("calls to %1% functions:") % functionCalls.size());
         typedef std::multimap<unsigned int, ExprLambda *> FunctionCalls_;
         FunctionCalls_ functionCalls_;
-        foreach (FunctionCalls::iterator, i, functionCalls)
-            functionCalls_.insert(std::pair<unsigned int, ExprLambda *>(i->second, i->first));
-        foreach_reverse (FunctionCalls_::reverse_iterator, i, functionCalls_)
+        for (auto & i : functionCalls)
+            functionCalls_.insert(std::pair<unsigned int, ExprLambda *>(i.second, i.first));
+        for (auto i = functionCalls_.rbegin(); i != functionCalls_.rend(); ++i)
             printMsg(v, format("%1$10d %2%") % i->first % i->second->showNamePos());
 
         printMsg(v, format("evaluations of %1% attributes:") % attrSelects.size());
         typedef std::multimap<unsigned int, Pos> AttrSelects_;
         AttrSelects_ attrSelects_;
-        foreach (AttrSelects::iterator, i, attrSelects)
-            attrSelects_.insert(std::pair<unsigned int, Pos>(i->second, i->first));
-        foreach_reverse (AttrSelects_::reverse_iterator, i, attrSelects_)
+        for (auto & i : attrSelects)
+            attrSelects_.insert(std::pair<unsigned int, Pos>(i.second, i.first));
+        for (auto i = attrSelects_.rbegin(); i != attrSelects_.rend(); ++i)
             printMsg(v, format("%1$10d %2%") % i->first % i->second);
 
     }
@@ -1643,12 +1730,14 @@ size_t valueSize(Value & v)
                     sz += doValue(*i.value);
             }
             break;
-        case tList:
-            if (seen.find(v.list.elems) == seen.end()) {
-                seen.insert(v.list.elems);
-                sz += v.list.length * sizeof(Value *);
-                for (unsigned int n = 0; n < v.list.length; ++n)
-                    sz += doValue(*v.list.elems[n]);
+        case tList1:
+        case tList2:
+        case tListN:
+            if (seen.find(v.listElems()) == seen.end()) {
+                seen.insert(v.listElems());
+                sz += v.listSize() * sizeof(Value *);
+                for (unsigned int n = 0; n < v.listSize(); ++n)
+                    sz += doValue(*v.listElems()[n]);
             }
             break;
         case tThunk:
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index 627fae3ff363..80e369f2d68f 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -1,5 +1,6 @@
 #pragma once
 
+#include "attr-set.hh"
 #include "value.hh"
 #include "nixexpr.hh"
 #include "symbol-table.hh"
@@ -15,82 +16,19 @@
 namespace nix {
 
 
+class Store;
 class EvalState;
 
 
-struct Attr
-{
-    Symbol name;
-    Value * value;
-    Pos * pos;
-    Attr(Symbol name, Value * value, Pos * pos = &noPos)
-        : name(name), value(value), pos(pos) { };
-    Attr() : pos(&noPos) { };
-    bool operator < (const Attr & a) const
-    {
-        return name < a.name;
-    }
-};
-
-
-class Bindings
-{
-public:
-    typedef uint32_t size_t;
-
-private:
-    size_t size_, capacity_;
-    Attr attrs[0];
-
-    Bindings(size_t capacity) : size_(0), capacity_(capacity) { }
-    Bindings(const Bindings & bindings) = delete;
-
-public:
-    size_t size() const { return size_; }
-
-    bool empty() const { return !size_; }
-
-    typedef Attr * iterator;
-
-    void push_back(const Attr & attr)
-    {
-        assert(size_ < capacity_);
-        attrs[size_++] = attr;
-    }
-
-    iterator find(const Symbol & name)
-    {
-        Attr key(name, 0);
-        iterator i = std::lower_bound(begin(), end(), key);
-        if (i != end() && i->name == name) return i;
-        return end();
-    }
-
-    iterator begin() { return &attrs[0]; }
-    iterator end() { return &attrs[size_]; }
-
-    Attr & operator[](size_t pos)
-    {
-        return attrs[pos];
-    }
-
-    void sort();
-
-    size_t capacity() { return capacity_; }
-
-    friend class EvalState;
-};
-
-
 typedef void (* PrimOpFun) (EvalState & state, const Pos & pos, Value * * args, Value & v);
 
 
 struct PrimOp
 {
     PrimOpFun fun;
-    unsigned int arity;
+    size_t arity;
     Symbol name;
-    PrimOp(PrimOpFun fun, unsigned int arity, Symbol name)
+    PrimOp(PrimOpFun fun, size_t arity, Symbol name)
         : fun(fun), arity(arity), name(name) { }
 };
 
@@ -118,7 +56,8 @@ typedef std::map<Path, Path> SrcToStore;
 std::ostream & operator << (std::ostream & str, const Value & v);
 
 
-typedef list<std::pair<string, Path> > SearchPath;
+typedef std::pair<std::string, std::string> SearchPathElem;
+typedef std::list<SearchPathElem> SearchPath;
 
 
 /* Initialise the Boehm GC, if applicable. */
@@ -132,17 +71,21 @@ public:
 
     const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue,
         sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls,
-        sFile, sLine, sColumn, sFunctor;
+        sFile, sLine, sColumn, sFunctor, sToString;
     Symbol sDerivationNix;
 
     /* If set, force copying files to the Nix store even if they
        already exist there. */
-    bool repair;
+    bool repair = false;
 
     /* If set, don't allow access to files outside of the Nix search
        path or to environment variables. */
     bool restricted;
 
+    Value vEmptySet;
+
+    const ref<Store> store;
+
 private:
     SrcToStore srcToStore;
 
@@ -156,12 +99,14 @@ private:
 
     SearchPath searchPath;
 
+    std::map<std::string, std::pair<bool, std::string>> searchPathResolved;
+
 public:
 
-    EvalState(const Strings & _searchPath);
+    EvalState(const Strings & _searchPath, ref<Store> store);
     ~EvalState();
 
-    void addToSearchPath(const string & s, bool warn = false);
+    void addToSearchPath(const string & s);
 
     Path checkSourcePath(const Path & path);
 
@@ -183,6 +128,9 @@ public:
     Path findFile(const string & path);
     Path findFile(SearchPath & searchPath, const string & path, const Pos & pos = noPos);
 
+    /* If the specified search path element is a URI, download it. */
+    std::pair<bool, std::string> resolveSearchPathElem(const SearchPathElem & elem);
+
     /* Evaluate an expression to normal form, storing the result in
        value `v'. */
     void eval(Expr * e, Value & v);
@@ -197,7 +145,7 @@ public:
        of the evaluation of the thunk.  If `v' is a delayed function
        application, call the function and overwrite `v' with the
        result.  Otherwise, this is a no-op. */
-    inline void forceValue(Value & v);
+    inline void forceValue(Value & v, const Pos & pos = noPos);
 
     /* Force a value, then recursively force list elements and
        attributes. */
@@ -205,6 +153,7 @@ public:
 
     /* Force `v', and then verify that it has the expected type. */
     NixInt forceInt(Value & v, const Pos & pos);
+    NixFloat forceFloat(Value & v, const Pos & pos);
     bool forceBool(Value & v);
     inline void forceAttrs(Value & v);
     inline void forceAttrs(Value & v, const Pos & pos);
@@ -244,7 +193,7 @@ public:
 
 private:
 
-    unsigned int baseEnvDispl;
+    unsigned int baseEnvDispl = 0;
 
     void createBaseEnv();
 
@@ -274,6 +223,8 @@ public:
        elements and attributes are compared recursively. */
     bool eqValues(Value & v1, Value & v2);
 
+    bool isFunctor(Value & fun);
+
     void callFunction(Value & fun, Value & arg, Value & v, const Pos & pos);
     void callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos);
 
@@ -290,7 +241,7 @@ public:
     Bindings * allocBindings(Bindings::size_t capacity);
 
     void mkList(Value & v, unsigned int length);
-    void mkAttrs(Value & v, unsigned int expected);
+    void mkAttrs(Value & v, unsigned int capacity);
     void mkThunk_(Value & v, Expr * expr);
     void mkPos(Value & v, Pos * pos);
 
@@ -299,19 +250,21 @@ public:
     /* Print statistics. */
     void printStats();
 
+    void realiseContext(const PathSet & context);
+
 private:
 
-    unsigned long nrEnvs;
-    unsigned long nrValuesInEnvs;
-    unsigned long nrValues;
-    unsigned long nrListElems;
-    unsigned long nrAttrsets;
-    unsigned long nrAttrsInAttrsets;
-    unsigned long nrOpUpdates;
-    unsigned long nrOpUpdateValuesCopied;
-    unsigned long nrListConcats;
-    unsigned long nrPrimOpCalls;
-    unsigned long nrFunctionCalls;
+    unsigned long nrEnvs = 0;
+    unsigned long nrValuesInEnvs = 0;
+    unsigned long nrValues = 0;
+    unsigned long nrListElems = 0;
+    unsigned long nrAttrsets = 0;
+    unsigned long nrAttrsInAttrsets = 0;
+    unsigned long nrOpUpdates = 0;
+    unsigned long nrOpUpdateValuesCopied = 0;
+    unsigned long nrListConcats = 0;
+    unsigned long nrPrimOpCalls = 0;
+    unsigned long nrFunctionCalls = 0;
 
     bool countCalls;
 
@@ -349,7 +302,4 @@ struct InvalidPathError : EvalError
 #endif
 };
 
-/* Realise all paths in `context' */
-void realiseContext(const PathSet & context);
-
 }
diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc
index 1c9fa02a366a..b06c539de0fb 100644
--- a/src/libexpr/get-drvs.cc
+++ b/src/libexpr/get-drvs.cc
@@ -30,7 +30,7 @@ string DrvInfo::queryOutPath()
 }
 
 
-DrvInfo::Outputs DrvInfo::queryOutputs()
+DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall)
 {
     if (outputs.empty()) {
         /* Get the ‘outputs’ list. */
@@ -39,9 +39,9 @@ DrvInfo::Outputs DrvInfo::queryOutputs()
             state->forceList(*i->value, *i->pos);
 
             /* For each output... */
-            for (unsigned int j = 0; j < i->value->list.length; ++j) {
+            for (unsigned int j = 0; j < i->value->listSize(); ++j) {
                 /* Evaluate the corresponding set. */
-                string name = state->forceStringNoCtx(*i->value->list.elems[j], *i->pos);
+                string name = state->forceStringNoCtx(*i->value->listElems()[j], *i->pos);
                 Bindings::iterator out = attrs->find(state->symbols.create(name));
                 if (out == attrs->end()) continue; // FIXME: throw error?
                 state->forceAttrs(*out->value);
@@ -55,7 +55,23 @@ DrvInfo::Outputs DrvInfo::queryOutputs()
         } else
             outputs["out"] = queryOutPath();
     }
-    return outputs;
+    if (!onlyOutputsToInstall || !attrs)
+        return outputs;
+
+    /* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */
+    const Value * outTI = queryMeta("outputsToInstall");
+    if (!outTI) return outputs;
+    const auto errMsg = Error("this derivation has bad ‘meta.outputsToInstall’");
+        /* ^ this shows during `nix-env -i` right under the bad derivation */
+    if (!outTI->isList()) throw errMsg;
+    Outputs result;
+    for (auto i = outTI->listElems(); i != outTI->listElems() + outTI->listSize(); ++i) {
+        if ((*i)->type != tString) throw errMsg;
+        auto out = outputs.find((*i)->string.s);
+        if (out == outputs.end()) throw errMsg;
+        result.insert(*out);
+    }
+    return result;
 }
 
 
@@ -85,8 +101,8 @@ StringSet DrvInfo::queryMetaNames()
 {
     StringSet res;
     if (!getMeta()) return res;
-    foreach (Bindings::iterator, i, *meta)
-        res.insert(i->name);
+    for (auto & i : *meta)
+        res.insert(i.name);
     return res;
 }
 
@@ -94,19 +110,20 @@ StringSet DrvInfo::queryMetaNames()
 bool DrvInfo::checkMeta(Value & v)
 {
     state->forceValue(v);
-    if (v.type == tList) {
-        for (unsigned int n = 0; n < v.list.length; ++n)
-            if (!checkMeta(*v.list.elems[n])) return false;
+    if (v.isList()) {
+        for (unsigned int n = 0; n < v.listSize(); ++n)
+            if (!checkMeta(*v.listElems()[n])) return false;
         return true;
     }
     else if (v.type == tAttrs) {
         Bindings::iterator i = v.attrs->find(state->sOutPath);
         if (i != v.attrs->end()) return false;
-        foreach (Bindings::iterator, i, *v.attrs)
-            if (!checkMeta(*i->value)) return false;
+        for (auto & i : *v.attrs)
+            if (!checkMeta(*i.value)) return false;
         return true;
     }
-    else return v.type == tInt || v.type == tBool || v.type == tString;
+    else return v.type == tInt || v.type == tBool || v.type == tString ||
+                v.type == tFloat;
 }
 
 
@@ -127,7 +144,7 @@ string DrvInfo::queryMetaString(const string & name)
 }
 
 
-int DrvInfo::queryMetaInt(const string & name, int def)
+NixInt DrvInfo::queryMetaInt(const string & name, NixInt def)
 {
     Value * v = queryMeta(name);
     if (!v) return def;
@@ -135,12 +152,26 @@ int DrvInfo::queryMetaInt(const string & name, int def)
     if (v->type == tString) {
         /* Backwards compatibility with before we had support for
            integer meta fields. */
-        int n;
+        NixInt n;
         if (string2Int(v->string.s, n)) return n;
     }
     return def;
 }
 
+NixFloat DrvInfo::queryMetaFloat(const string & name, NixFloat def)
+{
+    Value * v = queryMeta(name);
+    if (!v) return def;
+    if (v->type == tFloat) return v->fpoint;
+    if (v->type == tString) {
+        /* Backwards compatibility with before we had support for
+           float meta fields. */
+        NixFloat n;
+        if (string2Float(v->string.s, n)) return n;
+    }
+    return def;
+}
+
 
 bool DrvInfo::queryMetaBool(const string & name, bool def)
 {
@@ -177,8 +208,8 @@ typedef set<Bindings *> Done;
 
 
 /* Evaluate value `v'.  If it evaluates to a set of type `derivation',
-   then put information about it in `drvs' (unless it's already in
-   `doneExprs').  The result boolean indicates whether it makes sense
+   then put information about it in `drvs' (unless it's already in `done').
+   The result boolean indicates whether it makes sense
    for the caller to recursively search for derivations in `v'. */
 static bool getDerivation(EvalState & state, Value & v,
     const string & attrPath, DrvInfos & drvs, Done & done,
@@ -255,13 +286,13 @@ static void getDerivations(EvalState & state, Value & vIn,
            precedence). */
         typedef std::map<string, Symbol> SortedSymbols;
         SortedSymbols attrs;
-        foreach (Bindings::iterator, i, *v.attrs)
-            attrs.insert(std::pair<string, Symbol>(i->name, i->name));
+        for (auto & i : *v.attrs)
+            attrs.insert(std::pair<string, Symbol>(i.name, i.name));
 
-        foreach (SortedSymbols::iterator, i, attrs) {
-            startNest(nest, lvlDebug, format("evaluating attribute ‘%1%’") % i->first);
-            string pathPrefix2 = addToPath(pathPrefix, i->first);
-            Value & v2(*v.attrs->find(i->second)->value);
+        for (auto & i : attrs) {
+            Activity act(*logger, lvlDebug, format("evaluating attribute ‘%1%’") % i.first);
+            string pathPrefix2 = addToPath(pathPrefix, i.first);
+            Value & v2(*v.attrs->find(i.second)->value);
             if (combineChannels)
                 getDerivations(state, v2, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
             else if (getDerivation(state, v2, pathPrefix2, drvs, done, ignoreAssertionFailures)) {
@@ -277,13 +308,12 @@ static void getDerivations(EvalState & state, Value & vIn,
         }
     }
 
-    else if (v.type == tList) {
-        for (unsigned int n = 0; n < v.list.length; ++n) {
-            startNest(nest, lvlDebug,
-                format("evaluating list element"));
+    else if (v.isList()) {
+        for (unsigned int n = 0; n < v.listSize(); ++n) {
+            Activity act(*logger, lvlDebug, "evaluating list element");
             string pathPrefix2 = addToPath(pathPrefix, (format("%1%") % n).str());
-            if (getDerivation(state, *v.list.elems[n], pathPrefix2, drvs, done, ignoreAssertionFailures))
-                getDerivations(state, *v.list.elems[n], pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
+            if (getDerivation(state, *v.listElems()[n], pathPrefix2, drvs, done, ignoreAssertionFailures))
+                getDerivations(state, *v.listElems()[n], pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
         }
     }
 
diff --git a/src/libexpr/get-drvs.hh b/src/libexpr/get-drvs.hh
index 98f762494aa5..37fcbe829d3c 100644
--- a/src/libexpr/get-drvs.hh
+++ b/src/libexpr/get-drvs.hh
@@ -42,12 +42,14 @@ public:
     string queryDrvPath();
     string queryOutPath();
     string queryOutputName();
-    Outputs queryOutputs();
+    /** Return the list of outputs. The "outputs to install" are determined by `mesa.outputsToInstall`. */
+    Outputs queryOutputs(bool onlyOutputsToInstall = false);
 
     StringSet queryMetaNames();
     Value * queryMeta(const string & name);
     string queryMetaString(const string & name);
-    int queryMetaInt(const string & name, int def);
+    NixInt queryMetaInt(const string & name, NixInt def);
+    NixFloat queryMetaFloat(const string & name, NixFloat def);
     bool queryMetaBool(const string & name, bool def);
     void setMeta(const string & name, Value * v);
 
diff --git a/src/libexpr/json-to-value.cc b/src/libexpr/json-to-value.cc
index 1892b0bac1af..1daf84600dca 100644
--- a/src/libexpr/json-to-value.cc
+++ b/src/libexpr/json-to-value.cc
@@ -73,7 +73,7 @@ static void parseJSON(EvalState & state, const char * & s, Value & v)
         s++;
         state.mkList(v, values.size());
         for (size_t n = 0; n < values.size(); ++n)
-            v.list.elems[n] = values[n];
+            v.listElems()[n] = values[n];
     }
 
     else if (*s == '{') {
@@ -105,17 +105,21 @@ static void parseJSON(EvalState & state, const char * & s, Value & v)
         mkString(v, parseJSONString(s));
     }
 
-    else if (isdigit(*s) || *s == '-') {
-        bool neg = false;
-        if (*s == '-') {
-            neg = true;
-            if (!*++s) throw JSONParseError("unexpected end of JSON number");
+    else if (isdigit(*s) || *s == '-' || *s == '.' ) {
+        // Buffer into a string first, then use built-in C++ conversions
+        std::string tmp_number;
+        ValueType number_type = tInt;
+
+        while (isdigit(*s) || *s == '-' || *s == '.' || *s == 'e' || *s == 'E') {
+            if (*s == '.' || *s == 'e' || *s == 'E')
+                number_type = tFloat;
+            tmp_number += *s++;
         }
-        NixInt n = 0;
-        // FIXME: detect overflow
-        while (isdigit(*s)) n = n * 10 + (*s++ - '0');
-        if (*s == '.' || *s == 'e') throw JSONParseError("floating point JSON numbers are not supported");
-        mkInt(v, neg ? -n : n);
+
+        if (number_type == tFloat)
+            mkFloat(v, stod(tmp_number));
+        else
+            mkInt(v, stoi(tmp_number));
     }
 
     else if (strncmp(s, "true", 4) == 0) {
diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l
index 7051909008d1..f3660ab43723 100644
--- a/src/libexpr/lexer.l
+++ b/src/libexpr/lexer.l
@@ -1,10 +1,14 @@
 %option reentrant bison-bridge bison-locations
 %option noyywrap
 %option never-interactive
+%option stack
+%option nodefault
+%option nounput noyy_top_state
 
 
 %x STRING
 %x IND_STRING
+%x INSIDE_DOLLAR_CURLY
 
 
 %{
@@ -74,11 +78,15 @@ static Expr * unescapeStr(SymbolTable & symbols, const char * s)
 #define YY_USER_INIT initLoc(yylloc)
 #define YY_USER_ACTION adjustLoc(yylloc, yytext, yyleng);
 
+#define PUSH_STATE(state) yy_push_state(state, yyscanner)
+#define POP_STATE() yy_pop_state(yyscanner)
+
 %}
 
 
 ID          [a-zA-Z\_][a-zA-Z0-9\_\'\-]*
 INT         [0-9]+
+FLOAT       (([1-9][0-9]*\.[0-9]*)|(0?\.[0-9]+))([Ee][+-]?[0-9]+)?
 PATH        [a-zA-Z0-9\.\_\-\+]*(\/[a-zA-Z0-9\.\_\-\+]+)+
 HPATH       \~(\/[a-zA-Z0-9\.\_\-\+]+)+
 SPATH       \<[a-zA-Z0-9\.\_\-\+]+(\/[a-zA-Z0-9\.\_\-\+]+)*\>
@@ -87,6 +95,8 @@ URI         [a-zA-Z][a-zA-Z0-9\+\-\.]*\:[a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~
 
 %%
 
+<INITIAL,INSIDE_DOLLAR_CURLY>{
+
 
 if          { return IF; }
 then        { return THEN; }
@@ -117,24 +127,35 @@ or          { return OR_KW; }
                   throw ParseError(format("invalid integer ‘%1%’") % yytext);
               return INT;
             }
+{FLOAT}     { errno = 0;
+              yylval->nf = strtod(yytext, 0);
+              if (errno != 0)
+                  throw ParseError(format("invalid float ‘%1%’") % yytext);
+              return FLOAT;
+            }
 
-\$\{        { return DOLLAR_CURLY; }
+\$\{        { PUSH_STATE(INSIDE_DOLLAR_CURLY); return DOLLAR_CURLY; }
+}
 
-\"          { BEGIN(STRING); return '"'; }
-<STRING>([^\$\"\\]|\$[^\{\"]|\\.)+ {
-              /* !!! Not quite right: we want a follow restriction on
-                 "$", it shouldn't be followed by a "{".  Right now
-                 "$\"" will be consumed as part of a string, rather
-                 than a "$" followed by the string terminator.
-                 Disallow "$\"" for now. */
+\}                           { return '}'; }
+<INSIDE_DOLLAR_CURLY>\}      { POP_STATE(); return '}'; }
+\{                           { return '{'; }
+<INSIDE_DOLLAR_CURLY>\{      { PUSH_STATE(INSIDE_DOLLAR_CURLY); return '{'; }
+
+<INITIAL,INSIDE_DOLLAR_CURLY>\"          { PUSH_STATE(STRING); return '"'; }
+<STRING>([^\$\"\\]|\$[^\{\"\\]|\\.|\$\\.)*\$/\" |
+<STRING>([^\$\"\\]|\$[^\{\"\\]|\\.|\$\\.)+ {
+              /* It is impossible to match strings ending with '$' with one
+                 regex because trailing contexts are only valid at the end
+                 of a rule. (A sane but undocumented limitation.) */
               yylval->e = unescapeStr(data->symbols, yytext);
               return STR;
             }
-<STRING>\$\{  { BEGIN(INITIAL); return DOLLAR_CURLY; }
-<STRING>\"  { BEGIN(INITIAL); return '"'; }
+<STRING>\$\{  { PUSH_STATE(INSIDE_DOLLAR_CURLY); return DOLLAR_CURLY; }
+<STRING>\"  { POP_STATE(); return '"'; }
 <STRING>.   return yytext[0]; /* just in case: shouldn't be reached */
 
-\'\'(\ *\n)?     { BEGIN(IND_STRING); return IND_STRING_OPEN; }
+<INITIAL,INSIDE_DOLLAR_CURLY>\'\'(\ *\n)?     { PUSH_STATE(IND_STRING); return IND_STRING_OPEN; }
 <IND_STRING>([^\$\']|\$[^\{\']|\'[^\'\$])+ {
                    yylval->e = new ExprIndStr(yytext);
                    return IND_STR;
@@ -151,14 +172,16 @@ or          { return OR_KW; }
                    yylval->e = unescapeStr(data->symbols, yytext + 2);
                    return IND_STR;
                  }
-<IND_STRING>\$\{ { BEGIN(INITIAL); return DOLLAR_CURLY; }
-<IND_STRING>\'\' { BEGIN(INITIAL); return IND_STRING_CLOSE; }
+<IND_STRING>\$\{ { PUSH_STATE(INSIDE_DOLLAR_CURLY); return DOLLAR_CURLY; }
+<IND_STRING>\'\' { POP_STATE(); return IND_STRING_CLOSE; }
 <IND_STRING>\'   {
                    yylval->e = new ExprIndStr("'");
                    return IND_STR;
                  }
 <IND_STRING>.    return yytext[0]; /* just in case: shouldn't be reached */
 
+<INITIAL,INSIDE_DOLLAR_CURLY>{
+
 {PATH}      { yylval->path = strdup(yytext); return PATH; }
 {HPATH}     { yylval->path = strdup(yytext); return HPATH; }
 {SPATH}     { yylval->path = strdup(yytext); return SPATH; }
@@ -170,26 +193,9 @@ or          { return OR_KW; }
 
 .           return yytext[0];
 
-
-%%
-
-
-namespace nix {
-
-/* Horrible, disgusting hack: allow the parser to set the scanner
-   start condition back to STRING.  Necessary in interpolations like
-   "foo${expr}bar"; after the close brace we have to go back to the
-   STRING state. */
-void backToString(yyscan_t scanner)
-{
-    struct yyguts_t * yyg = (struct yyguts_t *) scanner;
-    BEGIN(STRING);
 }
 
-void backToIndString(yyscan_t scanner)
-{
-    struct yyguts_t * yyg = (struct yyguts_t *) scanner;
-    BEGIN(IND_STRING);
-}
+<<EOF>> { data->atEnd = true; return 0; }
+
+%%
 
-}
diff --git a/src/libexpr/local.mk b/src/libexpr/local.mk
index 35e84980a6dd..620050a13b05 100644
--- a/src/libexpr/local.mk
+++ b/src/libexpr/local.mk
@@ -4,11 +4,16 @@ libexpr_NAME = libnixexpr
 
 libexpr_DIR := $(d)
 
-libexpr_SOURCES := $(wildcard $(d)/*.cc) $(d)/lexer-tab.cc $(d)/parser-tab.cc
+libexpr_SOURCES := $(wildcard $(d)/*.cc) $(wildcard $(d)/primops/*.cc) $(d)/lexer-tab.cc $(d)/parser-tab.cc
+
+libexpr_CXXFLAGS := -Wno-deprecated-register
 
 libexpr_LIBS = libutil libstore libformat
 
-libexpr_LDFLAGS = -ldl -lcurl
+libexpr_LDFLAGS =
+ifneq ($(OS), FreeBSD)
+ libexpr_LDFLAGS += -ldl
+endif
 
 # The dependency on libgc must be propagated (i.e. meaning that
 # programs/libraries that use libexpr must explicitly pass -lgc),
diff --git a/src/libexpr/names.cc b/src/libexpr/names.cc
index cda5aa1952ea..7bca9b6550be 100644
--- a/src/libexpr/names.cc
+++ b/src/libexpr/names.cc
@@ -98,8 +98,8 @@ int compareVersions(const string & v1, const string & v2)
 DrvNames drvNamesFromArgs(const Strings & opArgs)
 {
     DrvNames result;
-    foreach (Strings::const_iterator, i, opArgs)
-        result.push_back(DrvName(*i));
+    for (auto & i : opArgs)
+        result.push_back(DrvName(i));
     return result;
 }
 
diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc
index 43f3161f8baf..b2c9f0528ca9 100644
--- a/src/libexpr/nixexpr.cc
+++ b/src/libexpr/nixexpr.cc
@@ -30,8 +30,9 @@ static void showString(std::ostream & str, const string & s)
 
 static void showId(std::ostream & str, const string & s)
 {
-    assert(!s.empty());
-    if (s == "if")
+    if (s.empty())
+        str << "\"\"";
+    else if (s == "if") // FIXME: handle other keywords
         str << '"' << s << '"';
     else {
         char c = s[0];
@@ -67,6 +68,11 @@ void ExprInt::show(std::ostream & str)
     str << n;
 }
 
+void ExprFloat::show(std::ostream & str)
+{
+    str << nf;
+}
+
 void ExprString::show(std::ostream & str)
 {
     showString(str, s);
@@ -97,21 +103,21 @@ void ExprAttrs::show(std::ostream & str)
 {
     if (recursive) str << "rec ";
     str << "{ ";
-    foreach (AttrDefs::iterator, i, attrs)
-        if (i->second.inherited)
-            str << "inherit " << i->first << " " << "; ";
+    for (auto & i : attrs)
+        if (i.second.inherited)
+            str << "inherit " << i.first << " " << "; ";
         else
-            str << i->first << " = " << *i->second.e << "; ";
-    foreach (DynamicAttrDefs::iterator, i, dynamicAttrs)
-        str << "\"${" << *i->nameExpr << "}\" = " << *i->valueExpr << "; ";
+            str << i.first << " = " << *i.second.e << "; ";
+    for (auto & i : dynamicAttrs)
+        str << "\"${" << *i.nameExpr << "}\" = " << *i.valueExpr << "; ";
     str << "}";
 }
 
 void ExprList::show(std::ostream & str)
 {
     str << "[ ";
-    foreach (vector<Expr *>::iterator, i, elems)
-        str << "(" << **i << ") ";
+    for (auto & i : elems)
+        str << "(" << *i << ") ";
     str << "]";
 }
 
@@ -121,10 +127,10 @@ void ExprLambda::show(std::ostream & str)
     if (matchAttrs) {
         str << "{ ";
         bool first = true;
-        foreach (Formals::Formals_::iterator, i, formals->formals) {
+        for (auto & i : formals->formals) {
             if (first) first = false; else str << ", ";
-            str << i->name;
-            if (i->def) str << " ? " << *i->def;
+            str << i.name;
+            if (i.def) str << " ? " << *i.def;
         }
         if (formals->ellipsis) {
             if (!first) str << ", ";
@@ -140,12 +146,12 @@ void ExprLambda::show(std::ostream & str)
 void ExprLet::show(std::ostream & str)
 {
     str << "(let ";
-    foreach (ExprAttrs::AttrDefs::iterator, i, attrs->attrs)
-        if (i->second.inherited) {
-            str << "inherit " << i->first << "; ";
+    for (auto & i : attrs->attrs)
+        if (i.second.inherited) {
+            str << "inherit " << i.first << "; ";
         }
         else
-            str << i->first << " = " << *i->second.e << "; ";
+            str << i.first << " = " << *i.second.e << "; ";
     str << "in " << *body << ")";
 }
 
@@ -173,9 +179,9 @@ void ExprConcatStrings::show(std::ostream & str)
 {
     bool first = true;
     str << "(";
-    foreach (vector<Expr *>::iterator, i, *es) {
+    for (auto & i : *es) {
         if (first) first = false; else str << " + ";
-        str << **i;
+        str << *i;
     }
     str << ")";
 }
@@ -225,6 +231,10 @@ void ExprInt::bindVars(const StaticEnv & env)
 {
 }
 
+void ExprFloat::bindVars(const StaticEnv & env)
+{
+}
+
 void ExprString::bindVars(const StaticEnv & env)
 {
 }
@@ -267,17 +277,17 @@ void ExprSelect::bindVars(const StaticEnv & env)
 {
     e->bindVars(env);
     if (def) def->bindVars(env);
-    foreach (AttrPath::iterator, i, attrPath)
-        if (!i->symbol.set())
-            i->expr->bindVars(env);
+    for (auto & i : attrPath)
+        if (!i.symbol.set())
+            i.expr->bindVars(env);
 }
 
 void ExprOpHasAttr::bindVars(const StaticEnv & env)
 {
     e->bindVars(env);
-    foreach (AttrPath::iterator, i, attrPath)
-        if (!i->symbol.set())
-            i->expr->bindVars(env);
+    for (auto & i : attrPath)
+        if (!i.symbol.set())
+            i.expr->bindVars(env);
 }
 
 void ExprAttrs::bindVars(const StaticEnv & env)
@@ -289,27 +299,27 @@ void ExprAttrs::bindVars(const StaticEnv & env)
         dynamicEnv = &newEnv;
 
         unsigned int displ = 0;
-        foreach (AttrDefs::iterator, i, attrs)
-            newEnv.vars[i->first] = i->second.displ = displ++;
+        for (auto & i : attrs)
+            newEnv.vars[i.first] = i.second.displ = displ++;
 
-        foreach (AttrDefs::iterator, i, attrs)
-            i->second.e->bindVars(i->second.inherited ? env : newEnv);
+        for (auto & i : attrs)
+            i.second.e->bindVars(i.second.inherited ? env : newEnv);
     }
 
     else
-        foreach (AttrDefs::iterator, i, attrs)
-            i->second.e->bindVars(env);
+        for (auto & i : attrs)
+            i.second.e->bindVars(env);
 
-    foreach (DynamicAttrDefs::iterator, i, dynamicAttrs) {
-        i->nameExpr->bindVars(*dynamicEnv);
-        i->valueExpr->bindVars(*dynamicEnv);
+    for (auto & i : dynamicAttrs) {
+        i.nameExpr->bindVars(*dynamicEnv);
+        i.valueExpr->bindVars(*dynamicEnv);
     }
 }
 
 void ExprList::bindVars(const StaticEnv & env)
 {
-    foreach (vector<Expr *>::iterator, i, elems)
-        (*i)->bindVars(env);
+    for (auto & i : elems)
+        i->bindVars(env);
 }
 
 void ExprLambda::bindVars(const StaticEnv & env)
@@ -321,11 +331,11 @@ void ExprLambda::bindVars(const StaticEnv & env)
     if (!arg.empty()) newEnv.vars[arg] = displ++;
 
     if (matchAttrs) {
-        foreach (Formals::Formals_::iterator, i, formals->formals)
-            newEnv.vars[i->name] = displ++;
+        for (auto & i : formals->formals)
+            newEnv.vars[i.name] = displ++;
 
-        foreach (Formals::Formals_::iterator, i, formals->formals)
-            if (i->def) i->def->bindVars(newEnv);
+        for (auto & i : formals->formals)
+            if (i.def) i.def->bindVars(newEnv);
     }
 
     body->bindVars(newEnv);
@@ -336,11 +346,11 @@ void ExprLet::bindVars(const StaticEnv & env)
     StaticEnv newEnv(false, &env);
 
     unsigned int displ = 0;
-    foreach (ExprAttrs::AttrDefs::iterator, i, attrs->attrs)
-        newEnv.vars[i->first] = i->second.displ = displ++;
+    for (auto & i : attrs->attrs)
+        newEnv.vars[i.first] = i.second.displ = displ++;
 
-    foreach (ExprAttrs::AttrDefs::iterator, i, attrs->attrs)
-        i->second.e->bindVars(i->second.inherited ? env : newEnv);
+    for (auto & i : attrs->attrs)
+        i.second.e->bindVars(i.second.inherited ? env : newEnv);
 
     body->bindVars(newEnv);
 }
@@ -384,8 +394,8 @@ void ExprOpNot::bindVars(const StaticEnv & env)
 
 void ExprConcatStrings::bindVars(const StaticEnv & env)
 {
-    foreach (vector<Expr *>::iterator, i, *es)
-        (*i)->bindVars(env);
+    for (auto & i : *es)
+        i->bindVars(env);
 }
 
 void ExprPos::bindVars(const StaticEnv & env)
@@ -419,8 +429,8 @@ string ExprLambda::showNamePos() const
 size_t SymbolTable::totalSize() const
 {
     size_t n = 0;
-    foreach (Symbols::const_iterator, i, symbols)
-        n += i->size();
+    for (auto & i : symbols)
+        n += i.size();
     return n;
 }
 
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
index ef07d4557fe8..d2ca09b3a5bb 100644
--- a/src/libexpr/nixexpr.hh
+++ b/src/libexpr/nixexpr.hh
@@ -11,6 +11,7 @@ namespace nix {
 
 MakeError(EvalError, Error)
 MakeError(ParseError, Error)
+MakeError(IncompleteParseError, ParseError)
 MakeError(AssertionError, EvalError)
 MakeError(ThrownError, AssertionError)
 MakeError(Abort, EvalError)
@@ -98,6 +99,15 @@ struct ExprInt : Expr
     Value * maybeThunk(EvalState & state, Env & env);
 };
 
+struct ExprFloat : Expr
+{
+    NixFloat nf;
+    Value v;
+    ExprFloat(NixFloat nf) : nf(nf) { mkFloat(v, nf); };
+    COMMON_METHODS
+    Value * maybeThunk(EvalState & state, Env & env);
+};
+
 struct ExprString : Expr
 {
     Symbol s;
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index 26168b2ed420..776e5cb39b81 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -31,10 +31,12 @@ namespace nix {
         Path basePath;
         Symbol path;
         string error;
+        bool atEnd;
         Symbol sLetBody;
         ParseData(EvalState & state)
             : state(state)
             , symbols(state.symbols)
+            , atEnd(false)
             , sLetBody(symbols.create("<let-body>"))
             { };
     };
@@ -136,8 +138,8 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols, vector<Ex
     bool atStartOfLine = true; /* = seen only whitespace in the current line */
     unsigned int minIndent = 1000000;
     unsigned int curIndent = 0;
-    foreach (vector<Expr *>::iterator, i, es) {
-        ExprIndStr * e = dynamic_cast<ExprIndStr *>(*i);
+    for (auto & i : es) {
+        ExprIndStr * e = dynamic_cast<ExprIndStr *>(i);
         if (!e) {
             /* Anti-quotations end the current start-of-line whitespace. */
             if (atStartOfLine) {
@@ -216,10 +218,6 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols, vector<Ex
 }
 
 
-void backToString(yyscan_t scanner);
-void backToIndString(yyscan_t scanner);
-
-
 static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data)
 {
     return Pos(data->path, loc.first_line, loc.first_column);
@@ -248,6 +246,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
   nix::Formals * formals;
   nix::Formal * formal;
   nix::NixInt n;
+  nix::NixFloat nf;
   const char * id; // !!! -> Symbol
   char * path;
   char * uri;
@@ -268,6 +267,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
 %token <id> ID ATTRPATH
 %token <e> STR IND_STR
 %token <n> INT
+%token <nf> FLOAT
 %token <path> PATH HPATH SPATH
 %token <uri> URI
 %token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL OR_KW
@@ -370,6 +370,7 @@ expr_simple
           $$ = new ExprVar(CUR_POS, data->symbols.create($1));
   }
   | INT { $$ = new ExprInt($1); }
+  | FLOAT { $$ = new ExprFloat($1); }
   | '"' string_parts '"' { $$ = $2; }
   | IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE {
       $$ = stripIndentation(CUR_POS, data->symbols, *$2);
@@ -404,25 +405,18 @@ string_parts
 
 string_parts_interpolated
   : string_parts_interpolated STR { $$ = $1; $1->push_back($2); }
-  | string_parts_interpolated DOLLAR_CURLY expr '}' { backToString(scanner); $$ = $1; $1->push_back($3); }
-  | STR DOLLAR_CURLY expr '}'
-    {
-      backToString(scanner);
+  | string_parts_interpolated DOLLAR_CURLY expr '}' { $$ = $1; $1->push_back($3); }
+  | DOLLAR_CURLY expr '}' { $$ = new vector<Expr *>; $$->push_back($2); }
+  | STR DOLLAR_CURLY expr '}' {
       $$ = new vector<Expr *>;
       $$->push_back($1);
       $$->push_back($3);
     }
-  | DOLLAR_CURLY expr '}'
-    {
-      backToString(scanner);
-      $$ = new vector<Expr *>;
-      $$->push_back($2);
-    }
   ;
 
 ind_string_parts
   : ind_string_parts IND_STR { $$ = $1; $1->push_back($2); }
-  | ind_string_parts DOLLAR_CURLY expr '}' { backToIndString(scanner); $$ = $1; $1->push_back($3); }
+  | ind_string_parts DOLLAR_CURLY expr '}' { $$ = $1; $1->push_back($3); }
   | { $$ = new vector<Expr *>; }
   ;
 
@@ -430,20 +424,20 @@ binds
   : binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data)); }
   | binds INHERIT attrs ';'
     { $$ = $1;
-      foreach (AttrPath::iterator, i, *$3) {
-          if ($$->attrs.find(i->symbol) != $$->attrs.end())
-              dupAttr(i->symbol, makeCurPos(@3, data), $$->attrs[i->symbol].pos);
+      for (auto & i : *$3) {
+          if ($$->attrs.find(i.symbol) != $$->attrs.end())
+              dupAttr(i.symbol, makeCurPos(@3, data), $$->attrs[i.symbol].pos);
           Pos pos = makeCurPos(@3, data);
-          $$->attrs[i->symbol] = ExprAttrs::AttrDef(new ExprVar(CUR_POS, i->symbol), pos, true);
+          $$->attrs[i.symbol] = ExprAttrs::AttrDef(new ExprVar(CUR_POS, i.symbol), pos, true);
       }
     }
   | binds INHERIT '(' expr ')' attrs ';'
     { $$ = $1;
       /* !!! Should ensure sharing of the expression in $4. */
-      foreach (AttrPath::iterator, i, *$6) {
-          if ($$->attrs.find(i->symbol) != $$->attrs.end())
-              dupAttr(i->symbol, makeCurPos(@6, data), $$->attrs[i->symbol].pos);
-          $$->attrs[i->symbol] = ExprAttrs::AttrDef(new ExprSelect(CUR_POS, $4, i->symbol), makeCurPos(@6, data));
+      for (auto & i : *$6) {
+          if ($$->attrs.find(i.symbol) != $$->attrs.end())
+              dupAttr(i.symbol, makeCurPos(@6, data), $$->attrs[i.symbol].pos);
+          $$->attrs[i.symbol] = ExprAttrs::AttrDef(new ExprSelect(CUR_POS, $4, i.symbol), makeCurPos(@6, data));
       }
     }
   | { $$ = new ExprAttrs; }
@@ -526,9 +520,10 @@ formal
 #include <fcntl.h>
 #include <unistd.h>
 
-#include <eval.hh>
-#include <download.hh>
-#include <store-api.hh>
+#include "eval.hh"
+#include "download.hh"
+#include "store-api.hh"
+#include "primops/fetchgit.hh"
 
 
 namespace nix {
@@ -547,7 +542,12 @@ Expr * EvalState::parse(const char * text,
     int res = yyparse(scanner, &data);
     yylex_destroy(scanner);
 
-    if (res) throw ParseError(data.error);
+    if (res) {
+      if (data.atEnd)
+        throw IncompleteParseError(data.error);
+      else
+        throw ParseError(data.error);
+    }
 
     data.result->bindVars(staticEnv);
 
@@ -601,7 +601,7 @@ Expr * EvalState::parseExprFromString(const string & s, const Path & basePath)
 }
 
 
-void EvalState::addToSearchPath(const string & s, bool warn)
+void EvalState::addToSearchPath(const string & s)
 {
     size_t pos = s.find('=');
     string prefix;
@@ -613,16 +613,7 @@ void EvalState::addToSearchPath(const string & s, bool warn)
         path = string(s, pos + 1);
     }
 
-    if (isUri(path))
-        path = downloadFileCached(path, true);
-
-    path = absPath(path);
-    if (pathExists(path)) {
-        debug(format("adding path ‘%1%’ to the search path") % path);
-        /* Resolve symlinks in the path to support restricted mode. */
-        searchPath.push_back(std::pair<string, Path>(prefix, canonPath(path, true)));
-    } else if (warn)
-        printMsg(lvlError, format("warning: Nix search path entry ‘%1%’ does not exist, ignoring") % path);
+    searchPath.emplace_back(prefix, path);
 }
 
 
@@ -635,17 +626,19 @@ Path EvalState::findFile(const string & path)
 Path EvalState::findFile(SearchPath & searchPath, const string & path, const Pos & pos)
 {
     for (auto & i : searchPath) {
-        assert(!isUri(i.second));
-        Path res;
+        std::string suffix;
         if (i.first.empty())
-            res = i.second + "/" + path;
+            suffix = "/" + path;
         else {
-            if (path.compare(0, i.first.size(), i.first) != 0 ||
-                (path.size() > i.first.size() && path[i.first.size()] != '/'))
+            auto s = i.first.size();
+            if (path.compare(0, s, i.first) != 0 ||
+                (path.size() > s && path[s] != '/'))
                 continue;
-            res = i.second +
-                (path.size() == i.first.size() ? "" : "/" + string(path, i.first.size()));
+            suffix = path.size() == s ? "" : "/" + string(path, s);
         }
+        auto r = resolveSearchPathElem(i);
+        if (!r.first) continue;
+        Path res = r.second + suffix;
         if (pathExists(res)) return canonPath(res);
     }
     format f = format(
@@ -656,4 +649,39 @@ Path EvalState::findFile(SearchPath & searchPath, const string & path, const Pos
 }
 
 
+std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathElem & elem)
+{
+    auto i = searchPathResolved.find(elem.second);
+    if (i != searchPathResolved.end()) return i->second;
+
+    std::pair<bool, std::string> res;
+
+    if (isUri(elem.second)) {
+        try {
+            if (hasPrefix(elem.second, "git://") || hasSuffix(elem.second, ".git"))
+                // FIXME: support specifying revision/branch
+                res = { true, exportGit(store, elem.second, "master") };
+            else
+                res = { true, makeDownloader()->downloadCached(store, elem.second, true) };
+        } catch (DownloadError & e) {
+            printMsg(lvlError, format("warning: Nix search path entry ‘%1%’ cannot be downloaded, ignoring") % elem.second);
+            res = { false, "" };
+        }
+    } else {
+        auto path = absPath(elem.second);
+        if (pathExists(path))
+            res = { true, path };
+        else {
+            printMsg(lvlError, format("warning: Nix search path entry ‘%1%’ does not exist, ignoring") % elem.second);
+            res = { false, "" };
+        }
+    }
+
+    debug(format("resolved search path element ‘%s’ to ‘%s’") % elem.second % res.second);
+
+    searchPathResolved[elem.second] = res;
+    return res;
+}
+
+
 }
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();
diff --git a/src/libexpr/primops.hh b/src/libexpr/primops.hh
new file mode 100644
index 000000000000..39d23b04a5ce
--- /dev/null
+++ b/src/libexpr/primops.hh
@@ -0,0 +1,15 @@
+#include "eval.hh"
+
+#include <tuple>
+#include <vector>
+
+namespace nix {
+
+struct RegisterPrimOp
+{
+    typedef std::vector<std::tuple<std::string, size_t, PrimOpFun>> PrimOps;
+    static PrimOps * primOps;
+    RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun);
+};
+
+}
diff --git a/src/libexpr/primops/fetchgit.cc b/src/libexpr/primops/fetchgit.cc
new file mode 100644
index 000000000000..bd440c8c62ad
--- /dev/null
+++ b/src/libexpr/primops/fetchgit.cc
@@ -0,0 +1,82 @@
+#include "primops.hh"
+#include "eval-inline.hh"
+#include "download.hh"
+#include "store-api.hh"
+
+namespace nix {
+
+Path exportGit(ref<Store> store, const std::string & uri, const std::string & rev)
+{
+    if (!isUri(uri))
+        throw EvalError(format("‘%s’ is not a valid URI") % uri);
+
+    Path cacheDir = getCacheDir() + "/nix/git";
+
+    if (!pathExists(cacheDir)) {
+        createDirs(cacheDir);
+        runProgram("git", true, { "init", "--bare", cacheDir });
+    }
+
+    Activity act(*logger, lvlInfo, format("fetching Git repository ‘%s’") % uri);
+
+    std::string localRef = "pid-" + std::to_string(getpid());
+    Path localRefFile = cacheDir + "/refs/heads/" + localRef;
+
+    runProgram("git", true, { "-C", cacheDir, "fetch", uri, rev + ":" + localRef });
+
+    std::string commitHash = chomp(readFile(localRefFile));
+
+    unlink(localRefFile.c_str());
+
+    debug(format("got revision ‘%s’") % commitHash);
+
+    // FIXME: should pipe this, or find some better way to extract a
+    // revision.
+    auto tar = runProgram("git", true, { "-C", cacheDir, "archive", commitHash });
+
+    Path tmpDir = createTempDir();
+    AutoDelete delTmpDir(tmpDir, true);
+
+    runProgram("tar", true, { "x", "-C", tmpDir }, tar);
+
+    return store->addToStore("git-export", tmpDir);
+}
+
+static void prim_fetchgit(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+    // FIXME: cut&paste from fetch().
+    if (state.restricted) throw Error("‘fetchgit’ is not allowed in restricted mode");
+
+    std::string url;
+    std::string rev = "master";
+
+    state.forceValue(*args[0]);
+
+    if (args[0]->type == tAttrs) {
+
+        state.forceAttrs(*args[0], pos);
+
+        for (auto & attr : *args[0]->attrs) {
+            string name(attr.name);
+            if (name == "url")
+                url = state.forceStringNoCtx(*attr.value, *attr.pos);
+            else if (name == "rev")
+                rev = state.forceStringNoCtx(*attr.value, *attr.pos);
+            else
+                throw EvalError(format("unsupported argument ‘%1%’ to ‘fetchgit’, at %3%") % attr.name % attr.pos);
+        }
+
+        if (url.empty())
+            throw EvalError(format("‘url’ argument required, at %1%") % pos);
+
+    } else
+        url = state.forceStringNoCtx(*args[0], pos);
+
+    Path storePath = exportGit(state.store, url, rev);
+
+    mkString(v, storePath, PathSet({storePath}));
+}
+
+static RegisterPrimOp r("__fetchgit", 1, prim_fetchgit);
+
+}
diff --git a/src/libexpr/primops/fetchgit.hh b/src/libexpr/primops/fetchgit.hh
new file mode 100644
index 000000000000..6ffb21a96daa
--- /dev/null
+++ b/src/libexpr/primops/fetchgit.hh
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <string>
+
+#include "ref.hh"
+
+namespace nix {
+
+class Store;
+
+Path exportGit(ref<Store> store,
+    const std::string & uri, const std::string & rev);
+
+}
diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc
index cdb71341875a..47ee324a6e4f 100644
--- a/src/libexpr/value-to-json.cc
+++ b/src/libexpr/value-to-json.cc
@@ -12,14 +12,14 @@ namespace nix {
 void escapeJSON(std::ostream & str, const string & s)
 {
     str << "\"";
-    foreach (string::const_iterator, i, s)
-        if (*i == '\"' || *i == '\\') str << "\\" << *i;
-        else if (*i == '\n') str << "\\n";
-        else if (*i == '\r') str << "\\r";
-        else if (*i == '\t') str << "\\t";
-        else if (*i >= 0 && *i < 32)
-            str << "\\u" << std::setfill('0') << std::setw(4) << std::hex << (uint16_t) *i << std::dec;
-        else str << *i;
+    for (auto & i : s)
+        if (i == '\"' || i == '\\') str << "\\" << i;
+        else if (i == '\n') str << "\\n";
+        else if (i == '\r') str << "\\r";
+        else if (i == '\t') str << "\\t";
+        else if (i >= 0 && i < 32)
+            str << "\\u" << std::setfill('0') << std::setw(4) << std::hex << (uint16_t) i << std::dec;
+        else str << i;
     str << "\"";
 }
 
@@ -59,11 +59,11 @@ void printValueAsJSON(EvalState & state, bool strict,
             if (i == v.attrs->end()) {
                 JSONObject json(str);
                 StringSet names;
-                foreach (Bindings::iterator, i, *v.attrs)
-                    names.insert(i->name);
-                foreach (StringSet::iterator, i, names) {
-                    Attr & a(*v.attrs->find(state.symbols.create(*i)));
-                    json.attr(*i);
+                for (auto & j : *v.attrs)
+                    names.insert(j.name);
+                for (auto & j : names) {
+                    Attr & a(*v.attrs->find(state.symbols.create(j)));
+                    json.attr(j);
                     printValueAsJSON(state, strict, *a.value, str, context);
                 }
             } else
@@ -71,19 +71,23 @@ void printValueAsJSON(EvalState & state, bool strict,
             break;
         }
 
-        case tList: {
+        case tList1: case tList2: case tListN: {
             JSONList json(str);
-            for (unsigned int n = 0; n < v.list.length; ++n) {
+            for (unsigned int n = 0; n < v.listSize(); ++n) {
                 json.elem();
-                printValueAsJSON(state, strict, *v.list.elems[n], str, context);
+                printValueAsJSON(state, strict, *v.listElems()[n], str, context);
             }
             break;
         }
 
-	case tExternal:
+        case tExternal:
             v.external->printValueAsJSON(state, strict, str, context);
             break;
 
+        case tFloat:
+            str << v.fpoint;
+            break;
+
         default:
             throw TypeError(format("cannot convert %1% to JSON") % showType(v));
     }
diff --git a/src/libexpr/value-to-json.hh b/src/libexpr/value-to-json.hh
index f6796f2053e9..c59caf5641bc 100644
--- a/src/libexpr/value-to-json.hh
+++ b/src/libexpr/value-to-json.hh
@@ -36,7 +36,18 @@ struct JSONObject
         attr(s);
         escapeJSON(str, t);
     }
-    void attr(const string & s, int n)
+    void attr(const string & s, const char * t)
+    {
+        attr(s);
+        escapeJSON(str, t);
+    }
+    void attr(const string & s, bool b)
+    {
+        attr(s);
+        str << (b ? "true" : "false");
+    }
+    template<typename T>
+    void attr(const string & s, const T & n)
     {
         attr(s);
         str << n;
diff --git a/src/libexpr/value-to-xml.cc b/src/libexpr/value-to-xml.cc
index bbbb7039bf70..00b1918a82aa 100644
--- a/src/libexpr/value-to-xml.cc
+++ b/src/libexpr/value-to-xml.cc
@@ -8,7 +8,7 @@
 
 namespace nix {
 
-    
+
 static XMLAttrs singletonAttrs(const string & name, const string & value)
 {
     XMLAttrs attrs;
@@ -33,17 +33,17 @@ static void showAttrs(EvalState & state, bool strict, bool location,
     Bindings & attrs, XMLWriter & doc, PathSet & context, PathSet & drvsSeen)
 {
     StringSet names;
-    
-    foreach (Bindings::iterator, i, attrs)
-        names.insert(i->name);
-    
-    foreach (StringSet::iterator, i, names) {
-        Attr & a(*attrs.find(state.symbols.create(*i)));
-        
+
+    for (auto & i : attrs)
+        names.insert(i.name);
+
+    for (auto & i : names) {
+        Attr & a(*attrs.find(state.symbols.create(i)));
+
         XMLAttrs xmlAttrs;
-        xmlAttrs["name"] = *i;
+        xmlAttrs["name"] = i;
         if (location && a.pos != &noPos) posToXML(xmlAttrs, *a.pos);
-        
+
         XMLOpenElement _(doc, "attr", xmlAttrs);
         printValueAsXML(state, strict, location,
             *a.value, doc, context, drvsSeen);
@@ -57,7 +57,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
     checkInterrupt();
 
     if (strict) state.forceValue(v);
-        
+
     switch (v.type) {
 
         case tInt:
@@ -85,7 +85,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
         case tAttrs:
             if (state.isDerivation(v)) {
                 XMLAttrs xmlAttrs;
-            
+
                 Bindings::iterator a = v.attrs->find(state.symbols.create("derivation"));
 
                 Path drvPath;
@@ -95,7 +95,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
                     if (a->value->type == tString)
                         xmlAttrs["drvPath"] = drvPath = a->value->string.s;
                 }
-        
+
                 a = v.attrs->find(state.sOutPath);
                 if (a != v.attrs->end()) {
                     if (strict) state.forceValue(*a->value);
@@ -116,13 +116,13 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
                 XMLOpenElement _(doc, "attrs");
                 showAttrs(state, strict, location, *v.attrs, doc, context, drvsSeen);
             }
-            
+
             break;
 
-        case tList: {
+        case tList1: case tList2: case tListN: {
             XMLOpenElement _(doc, "list");
-            for (unsigned int n = 0; n < v.list.length; ++n)
-                printValueAsXML(state, strict, location, *v.list.elems[n], doc, context, drvsSeen);
+            for (unsigned int n = 0; n < v.listSize(); ++n)
+                printValueAsXML(state, strict, location, *v.listElems()[n], doc, context, drvsSeen);
             break;
         }
 
@@ -130,17 +130,17 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
             XMLAttrs xmlAttrs;
             if (location) posToXML(xmlAttrs, v.lambda.fun->pos);
             XMLOpenElement _(doc, "function", xmlAttrs);
-            
+
             if (v.lambda.fun->matchAttrs) {
                 XMLAttrs attrs;
                 if (!v.lambda.fun->arg.empty()) attrs["name"] = v.lambda.fun->arg;
                 if (v.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1";
                 XMLOpenElement _(doc, "attrspat", attrs);
-                foreach (Formals::Formals_::iterator, i, v.lambda.fun->formals->formals)
-                    doc.writeEmptyElement("attr", singletonAttrs("name", i->name));
+                for (auto & i : v.lambda.fun->formals->formals)
+                    doc.writeEmptyElement("attr", singletonAttrs("name", i.name));
             } else
                 doc.writeEmptyElement("varpat", singletonAttrs("name", v.lambda.fun->arg));
-            
+
             break;
         }
 
@@ -148,6 +148,10 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
             v.external->printValueAsXML(state, strict, location, doc, context, drvsSeen);
             break;
 
+        case tFloat:
+            doc.writeEmptyElement("float", singletonAttrs("value", (format("%1%") % v.fpoint).str()));
+            break;
+
         default:
             doc.writeEmptyElement("unevaluated");
     }
@@ -166,9 +170,9 @@ void printValueAsXML(EvalState & state, bool strict, bool location,
 {
     XMLWriter doc(true, out);
     XMLOpenElement root(doc, "expr");
-    PathSet drvsSeen;    
+    PathSet drvsSeen;
     printValueAsXML(state, strict, location, v, doc, context, drvsSeen);
 }
 
- 
+
 }
diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh
index c06b5a6d1153..62bdd9281f08 100644
--- a/src/libexpr/value.hh
+++ b/src/libexpr/value.hh
@@ -12,7 +12,9 @@ typedef enum {
     tPath,
     tNull,
     tAttrs,
-    tList,
+    tList1,
+    tList2,
+    tListN,
     tThunk,
     tApp,
     tLambda,
@@ -20,6 +22,7 @@ typedef enum {
     tPrimOp,
     tPrimOpApp,
     tExternal,
+    tFloat
 } ValueType;
 
 
@@ -36,6 +39,7 @@ class XMLWriter;
 
 
 typedef long NixInt;
+typedef float NixFloat;
 
 /* External values must descend from ExternalValueBase, so that
  * type-agnostic nix functions (e.g. showType) can be implemented
@@ -119,9 +123,10 @@ struct Value
         const char * path;
         Bindings * attrs;
         struct {
-            unsigned int length;
+            unsigned int size;
             Value * * elems;
-        } list;
+        } bigList;
+        Value * smallList[2];
         struct {
             Env * env;
             Expr * expr;
@@ -138,7 +143,28 @@ struct Value
             Value * left, * right;
         } primOpApp;
         ExternalValueBase * external;
+        NixFloat fpoint;
     };
+
+    bool isList() const
+    {
+        return type == tList1 || type == tList2 || type == tListN;
+    }
+
+    Value * * listElems()
+    {
+        return type == tList1 || type == tList2 ? smallList : bigList.elems;
+    }
+
+    const Value * const * listElems() const
+    {
+        return type == tList1 || type == tList2 ? smallList : bigList.elems;
+    }
+
+    unsigned int listSize() const
+    {
+        return type == tList1 ? 1 : type == tList2 ? 2 : bigList.size;
+    }
 };
 
 
@@ -158,6 +184,14 @@ static inline void mkInt(Value & v, NixInt n)
 }
 
 
+static inline void mkFloat(Value & v, NixFloat n)
+{
+    clearValue(v);
+    v.type = tFloat;
+    v.fpoint = n;
+}
+
+
 static inline void mkBool(Value & v, bool b)
 {
     clearValue(v);