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