diff --git a/doc/manual/release-notes.xml b/doc/manual/release-notes.xml index a3c33b5f..3559b124 100644 --- a/doc/manual/release-notes.xml +++ b/doc/manual/release-notes.xml @@ -17,8 +17,13 @@ nix-store --dump-db / --load-db. New primops: - builtins.parseDrvName and - builtins.compareVersions. + builtins.parseDrvName, + builtins.compareVersions, + builtins.length, + builtins.add, + builtins.sub, + builtins.genericClosure. + diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index fbef2272..c747f46c 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -128,59 +128,7 @@ static Expr prim_isFunction(EvalState & state, const ATermVector & args) } -static Path findDependency(Path dir, string dep) -{ - if (dep[0] == '/') throw EvalError( - format("illegal absolute dependency `%1%'") % dep); - - Path p = canonPath(dir + "/" + dep); - - if (pathExists(p)) - return p; - else - return ""; -} - - -/* Make path `p' relative to directory `pivot'. E.g., - relativise("/a/b/c", "a/b/x/y") => "../x/y". Both input paths - should be in absolute canonical form. */ -static string relativise(Path pivot, Path p) -{ - assert(pivot.size() > 0 && pivot[0] == '/'); - assert(p.size() > 0 && p[0] == '/'); - - if (pivot == p) return "."; - - /* `p' is in `pivot'? */ - Path pivot2 = pivot + "/"; - if (p.substr(0, pivot2.size()) == pivot2) { - return p.substr(pivot2.size()); - } - - /* Otherwise, `p' is in a parent of `pivot'. Find up till which - path component `p' and `pivot' match, and add an appropriate - number of `..' components. */ - string::size_type i = 1; - while (1) { - string::size_type j = pivot.find('/', i); - if (j == string::npos) break; - j++; - if (pivot.substr(0, j) != p.substr(0, j)) break; - i = j; - } - - string prefix; - unsigned int slashes = count(pivot.begin() + i, pivot.end(), '/') + 1; - while (slashes--) { - prefix += "../"; - } - - return prefix + p.substr(i); -} - - -static Expr prim_dependencyClosure(EvalState & state, const ATermVector & args) +static Expr prim_genericClosure(EvalState & state, const ATermVector & args) { startNest(nest, lvlDebug, "finding dependencies"); @@ -191,87 +139,40 @@ static Expr prim_dependencyClosure(EvalState & state, const ATermVector & args) if (!startSet) throw EvalError("attribute `startSet' required"); ATermList startSet2 = evalList(state, startSet); - Path pivot; - PathSet workSet; - for (ATermIterator i(startSet2); i; ++i) { - PathSet context; /* !!! what to do? */ - Path p = coerceToPath(state, *i, context); - workSet.insert(p); - pivot = dirOf(p); - } + set workSet; // !!! gc roots + for (ATermIterator i(startSet2); i; ++i) workSet.insert(*i); - /* Get the search path. */ - PathSet searchPath; - Expr e = queryAttr(attrs, "searchPath"); - if (e) { - ATermList list = evalList(state, e); - for (ATermIterator i(list); i; ++i) { - PathSet context; /* !!! what to do? */ - Path p = coerceToPath(state, *i, context); - searchPath.insert(p); - } - } - - Expr scanner = queryAttr(attrs, "scanner"); - if (!scanner) throw EvalError("attribute `scanner' required"); + /* Get the operator. */ + Expr op = queryAttr(attrs, "operator"); + if (!op) throw EvalError("attribute `operator' required"); - /* Construct the dependency closure by querying the dependency of - each path in `workSet', adding the dependencies to - `workSet'. */ - PathSet doneSet; + /* Construct the closure by applying the operator to element of + `workSet', adding the result to `workSet', continuing until + no new elements are found. */ + ATermList res = ATempty; + set doneKeys; // !!! gc roots while (!workSet.empty()) { - Path path = *(workSet.begin()); - workSet.erase(path); + Expr e = *(workSet.begin()); + workSet.erase(e); - if (doneSet.find(path) != doneSet.end()) continue; - doneSet.insert(path); + e = strictEvalExpr(state, e); - try { - - /* Call the `scanner' function with `path' as argument. */ - debug(format("finding dependencies in `%1%'") % path); - ATermList deps = evalList(state, makeCall(scanner, makeStr(path))); + Expr key = queryAttr(e, "key"); + if (!key) throw EvalError("attribute `key' required"); - /* Try to find the dependencies relative to the `path'. */ - for (ATermIterator i(deps); i; ++i) { - string s = evalStringNoCtx(state, *i); - - Path dep = findDependency(dirOf(path), s); + if (doneKeys.find(key) != doneKeys.end()) continue; + doneKeys.insert(key); + res = ATinsert(res, e); + + /* Call the `operator' function with `e' as argument. */ + ATermList res = evalList(state, makeCall(op, e)); - if (dep == "") { - for (PathSet::iterator j = searchPath.begin(); - j != searchPath.end(); ++j) - { - dep = findDependency(*j, s); - if (dep != "") break; - } - } - - if (dep == "") - debug(format("did NOT find dependency `%1%'") % s); - else { - debug(format("found dependency `%1%'") % dep); - workSet.insert(dep); - } - } - - } catch (Error & e) { - e.addPrefix(format("while finding dependencies in `%1%':\n") - % path); - throw; - } + /* Try to find the dependencies relative to the `path'. */ + for (ATermIterator i(res); i; ++i) + workSet.insert(evalExpr(state, *i)); } - /* Return a list of the dependencies we've just found. */ - ATermList deps = ATempty; - for (PathSet::iterator i = doneSet.begin(); i != doneSet.end(); ++i) { - deps = ATinsert(deps, makeStr(relativise(pivot, *i))); - deps = ATinsert(deps, makeStr(*i)); - } - - debug(format("dependency list is `%1%'") % makeList(deps)); - - return makeList(deps); + return makeList(res); } @@ -311,15 +212,6 @@ static Expr prim_trace(EvalState & state, const ATermVector & args) } -static Expr prim_relativise(EvalState & state, const ATermVector & args) -{ - PathSet context; /* !!! what to do? */ - Path pivot = coerceToPath(state, args[0], context); - Path path = coerceToPath(state, args[1], context); - return makeStr(relativise(pivot, path)); -} - - /************************************************************* * Derivations *************************************************************/ @@ -874,6 +766,14 @@ static Expr prim_map(EvalState & state, const ATermVector & args) } +/* Return the length of a list. This is an O(1) time operation. */ +static Expr prim_length(EvalState & state, const ATermVector & args) +{ + ATermList list = evalList(state, args[0]); + return makeInt(ATgetLength(list)); +} + + /************************************************************* * Integer arithmetic *************************************************************/ @@ -895,6 +795,23 @@ static Expr prim_sub(EvalState & state, const ATermVector & args) } +static Expr prim_mul(EvalState & state, const ATermVector & args) +{ + int i1 = evalInt(state, args[0]); + int i2 = evalInt(state, args[1]); + return makeInt(i1 * i2); +} + + +static Expr prim_div(EvalState & state, const ATermVector & args) +{ + int i1 = evalInt(state, args[0]); + int i2 = evalInt(state, args[1]); + if (i2 == 0) throw EvalError("division by zero"); + return makeInt(i1 / i2); +} + + static Expr prim_lessThan(EvalState & state, const ATermVector & args) { int i1 = evalInt(state, args[0]); @@ -1019,7 +936,7 @@ void EvalState::addPrimOps() addPrimOp("import", 1, prim_import); addPrimOp("isNull", 1, prim_isNull); addPrimOp("__isFunction", 1, prim_isFunction); - addPrimOp("dependencyClosure", 1, prim_dependencyClosure); + addPrimOp("__genericClosure", 1, prim_genericClosure); addPrimOp("abort", 1, prim_abort); addPrimOp("throw", 1, prim_throw); addPrimOp("__getEnv", 1, prim_getEnv); @@ -1029,8 +946,6 @@ void EvalState::addPrimOps() addPrimOp("__exprToString", 1, prim_exprToString); addPrimOp("__stringToExpr", 1, prim_stringToExpr); - addPrimOp("relativise", 2, prim_relativise); - // Derivations addPrimOp("derivation!", 1, prim_derivationStrict); addPrimOp("derivation", 1, prim_derivationLazy); @@ -1060,10 +975,13 @@ void EvalState::addPrimOps() addPrimOp("__head", 1, prim_head); addPrimOp("__tail", 1, prim_tail); addPrimOp("map", 2, prim_map); + addPrimOp("__length", 1, prim_length); // Integer arithmetic addPrimOp("__add", 2, prim_add); addPrimOp("__sub", 2, prim_sub); + addPrimOp("__mul", 2, prim_mul); + addPrimOp("__div", 2, prim_div); addPrimOp("__lessThan", 2, prim_lessThan); // String manipulation diff --git a/tests/lang/eval-okay-closure.exp.xml b/tests/lang/eval-okay-closure.exp.xml new file mode 100644 index 00000000..dffc03a9 --- /dev/null +++ b/tests/lang/eval-okay-closure.exp.xml @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/lang/eval-okay-closure.nix b/tests/lang/eval-okay-closure.nix new file mode 100644 index 00000000..cccd4dc3 --- /dev/null +++ b/tests/lang/eval-okay-closure.nix @@ -0,0 +1,13 @@ +let + + closure = builtins.genericClosure { + startSet = [{key = 80;}]; + operator = {key, foo ? false}: + if builtins.lessThan key 0 + then [] + else [{key = builtins.sub key 9;} {key = builtins.sub key 13; foo = true;}]; + }; + + sort = (import ./lib.nix).sortBy (a: b: builtins.lessThan a.key b.key); + +in sort closure diff --git a/tests/lang/lib.nix b/tests/lang/lib.nix index e508f28b..551b67ae 100644 --- a/tests/lang/lib.nix +++ b/tests/lang/lib.nix @@ -25,4 +25,28 @@ rec { in !(lessThan lenFileName lenExt) && substring (sub lenFileName lenExt) lenFileName fileName == ext; + # Split a list at the given position. + splitAt = pos: list: + if pos == 0 then {first = []; second = list;} else + if list == [] then {first = []; second = [];} else + let res = splitAt (sub pos 1) (tail list); + in {first = [(head list)] ++ res.first; second = res.second;}; + + # Stable merge sort. + sortBy = comp: list: + if lessThan 1 (length list) + then + let + split = splitAt (div (length list) 2) list; + first = sortBy comp split.first; + second = sortBy comp split.second; + in mergeLists comp first second + else list; + + mergeLists = comp: list1: list2: + if list1 == [] then list2 else + if list2 == [] then list1 else + if comp (head list2) (head list1) then [(head list2)] ++ mergeLists comp list1 (tail list2) else + [(head list1)] ++ mergeLists comp (tail list1) list2; + }