diff options
Diffstat (limited to 'src/libexpr')
-rw-r--r-- | src/libexpr/attr-path.cc | 11 | ||||
-rw-r--r-- | src/libexpr/attr-set.cc | 63 | ||||
-rw-r--r-- | src/libexpr/attr-set.hh | 82 | ||||
-rw-r--r-- | src/libexpr/common-opts.cc | 2 | ||||
-rw-r--r-- | src/libexpr/common-opts.hh | 2 | ||||
-rw-r--r-- | src/libexpr/download.cc | 236 | ||||
-rw-r--r-- | src/libexpr/download.hh | 22 | ||||
-rw-r--r-- | src/libexpr/eval-inline.hh | 13 | ||||
-rw-r--r-- | src/libexpr/eval.cc | 371 | ||||
-rw-r--r-- | src/libexpr/eval.hh | 126 | ||||
-rw-r--r-- | src/libexpr/get-drvs.cc | 86 | ||||
-rw-r--r-- | src/libexpr/get-drvs.hh | 6 | ||||
-rw-r--r-- | src/libexpr/json-to-value.cc | 26 | ||||
-rw-r--r-- | src/libexpr/lexer.l | 72 | ||||
-rw-r--r-- | src/libexpr/local.mk | 9 | ||||
-rw-r--r-- | src/libexpr/names.cc | 4 | ||||
-rw-r--r-- | src/libexpr/nixexpr.cc | 106 | ||||
-rw-r--r-- | src/libexpr/nixexpr.hh | 10 | ||||
-rw-r--r-- | src/libexpr/parser.y | 122 | ||||
-rw-r--r-- | src/libexpr/primops.cc | 529 | ||||
-rw-r--r-- | src/libexpr/primops.hh | 15 | ||||
-rw-r--r-- | src/libexpr/primops/fetchgit.cc | 82 | ||||
-rw-r--r-- | src/libexpr/primops/fetchgit.hh | 14 | ||||
-rw-r--r-- | src/libexpr/value-to-json.cc | 38 | ||||
-rw-r--r-- | src/libexpr/value-to-json.hh | 13 | ||||
-rw-r--r-- | src/libexpr/value-to-xml.cc | 50 | ||||
-rw-r--r-- | src/libexpr/value.hh | 40 |
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); |