From 2b91c1dc11317774a0e37037b36277238e93f01f Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Thu, 30 May 2024 09:51:42 +0200 Subject: [PATCH] primops: Move genericClosure to attrset.cc Change-Id: I0f8ae3dfcc1a4e4ce6e631e13e5b63ac03c1cd74 --- src/libexpr/primops.cc | 93 ---------------------- src/libexpr/primops/attrset.cc | 139 +++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 93 deletions(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index e629490ca..af2e930ef 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -174,99 +174,6 @@ template } } -static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v) -{ - state.forceAttrs(*args[0], noPos, "while evaluating the first argument passed to builtins.genericClosure"); - - /* Get the start set. */ - Bindings::iterator startSet = getAttr(state, state.sStartSet, args[0]->attrs, "in the attrset passed as argument to builtins.genericClosure"); - - state.forceList(*startSet->value, noPos, "while evaluating the 'startSet' attribute passed as argument to builtins.genericClosure"); - - ValueList workSet; - for (auto elem : startSet->value->listItems()) - workSet.push_back(elem); - - if (startSet->value->listSize() == 0) { - v = *startSet->value; - return; - } - - /* Get the operator. */ - Bindings::iterator op = getAttr(state, state.sOperator, args[0]->attrs, "in the attrset passed as argument to builtins.genericClosure"); - state.forceFunction(*op->value, noPos, "while evaluating the 'operator' attribute passed as argument to builtins.genericClosure"); - - /* Construct the closure by applying the operator to elements of - `workSet', adding the result to `workSet', continuing until - no new elements are found. */ - ValueList res; - // `doneKeys' doesn't need to be a GC root, because its values are - // reachable from res. - auto cmp = CompareValues(state, noPos, "while comparing the `key` attributes of two genericClosure elements"); - std::set doneKeys(cmp); - while (!workSet.empty()) { - Value * e = *(workSet.begin()); - workSet.pop_front(); - - state.forceAttrs(*e, noPos, "while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"); - - Bindings::iterator key = getAttr(state, state.sKey, e->attrs, "in one of the attrsets generated by (or initially passed to) builtins.genericClosure"); - state.forceValue(*key->value, noPos); - - if (!doneKeys.insert(key->value).second) continue; - res.push_back(e); - - /* Call the `operator' function with `e' as argument. */ - Value newElements; - state.callFunction(*op->value, 1, &e, newElements, noPos); - state.forceList(newElements, noPos, "while evaluating the return value of the `operator` passed to builtins.genericClosure"); - - /* Add the values returned by the operator to the work set. */ - for (auto elem : newElements.listItems()) { - state.forceValue(*elem, noPos); // "while evaluating one one of the elements returned by the `operator` passed to builtins.genericClosure"); - workSet.push_back(elem); - } - } - - /* Create the result list. */ - state.mkList(v, res.size()); - unsigned int n = 0; - for (auto & i : res) - v.listElems()[n++] = i; -} - -static RegisterPrimOp primop_genericClosure(PrimOp { - .name = "__genericClosure", - .args = {"attrset"}, - .arity = 1, - .doc = R"( - Take an *attrset* with values named `startSet` and `operator` in order to - return a *list of attrsets* by starting with the `startSet` and recursively - applying the `operator` function to each `item`. The *attrsets* in the - `startSet` and the *attrsets* produced by `operator` must contain a value - named `key` which is comparable. The result is produced by calling `operator` - for each `item` with a value for `key` that has not been called yet including - newly produced `item`s. The function terminates when no new `item`s are - produced. The resulting *list of attrsets* contains only *attrsets* with a - unique key. For example, - - ``` - builtins.genericClosure { - startSet = [ {key = 5;} ]; - operator = item: [{ - key = if (item.key / 2 ) * 2 == item.key - then item.key / 2 - else 3 * item.key + 1; - }]; - } - ``` - evaluates to - ``` - [ { key = 5; } { key = 16; } { key = 8; } { key = 4; } { key = 2; } { key = 1; } ] - ``` - )", - .fun = prim_genericClosure, -}); static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) { diff --git a/src/libexpr/primops/attrset.cc b/src/libexpr/primops/attrset.cc index 158d971a8..6f500f7a6 100644 --- a/src/libexpr/primops/attrset.cc +++ b/src/libexpr/primops/attrset.cc @@ -133,6 +133,145 @@ static RegisterPrimOp primop_catAttrs({ .fun = prim_catAttrs, }); +/** + * builtins.genericClosure + */ + +static void prim_genericClosure(EvalState & state, const PosIdx pos, Value ** args, Value & v) +{ + state.forceAttrs( + *args[0], noPos, "while evaluating the first argument passed to builtins.genericClosure" + ); + + /* Get the start set. */ + Bindings::iterator startSet = getAttr( + state, + state.sStartSet, + args[0]->attrs, + "in the attrset passed as argument to builtins.genericClosure" + ); + + state.forceList( + *startSet->value, + noPos, + "while evaluating the 'startSet' attribute passed as argument to builtins.genericClosure" + ); + + ValueList workSet; + for (auto elem : startSet->value->listItems()) { + workSet.push_back(elem); + } + + if (startSet->value->listSize() == 0) { + v = *startSet->value; + return; + } + + /* Get the operator. */ + Bindings::iterator op = getAttr( + state, + state.sOperator, + args[0]->attrs, + "in the attrset passed as argument to builtins.genericClosure" + ); + state.forceFunction( + *op->value, + noPos, + "while evaluating the 'operator' attribute passed as argument to builtins.genericClosure" + ); + + /* Construct the closure by applying the operator to elements of + `workSet', adding the result to `workSet', continuing until + no new elements are found. */ + ValueList res; + // `doneKeys' doesn't need to be a GC root, because its values are + // reachable from res. + auto cmp = CompareValues( + state, noPos, "while comparing the `key` attributes of two genericClosure elements" + ); + std::set doneKeys(cmp); + while (!workSet.empty()) { + Value * e = *(workSet.begin()); + workSet.pop_front(); + + state.forceAttrs( + *e, + noPos, + "while evaluating one of the elements generated by (or initially passed to) " + "builtins.genericClosure" + ); + + Bindings::iterator key = getAttr( + state, + state.sKey, + e->attrs, + "in one of the attrsets generated by (or initially passed to) builtins.genericClosure" + ); + state.forceValue(*key->value, noPos); + + if (!doneKeys.insert(key->value).second) { + continue; + } + res.push_back(e); + + /* Call the `operator' function with `e' as argument. */ + Value newElements; + state.callFunction(*op->value, 1, &e, newElements, noPos); + state.forceList( + newElements, + noPos, + "while evaluating the return value of the `operator` passed to builtins.genericClosure" + ); + + /* Add the values returned by the operator to the work set. */ + for (auto elem : newElements.listItems()) { + state.forceValue(*elem, noPos); // "while evaluating one one of the elements returned by + // the `operator` passed to builtins.genericClosure"); + workSet.push_back(elem); + } + } + + /* Create the result list. */ + state.mkList(v, res.size()); + unsigned int n = 0; + for (auto & i : res) { + v.listElems()[n++] = i; + } +} + +static RegisterPrimOp primop_genericClosure(PrimOp{ + .name = "__genericClosure", + .args = {"attrset"}, + .arity = 1, + .doc = R"( + Take an *attrset* with values named `startSet` and `operator` in order to + return a *list of attrsets* by starting with the `startSet` and recursively + applying the `operator` function to each `item`. The *attrsets* in the + `startSet` and the *attrsets* produced by `operator` must contain a value + named `key` which is comparable. The result is produced by calling `operator` + for each `item` with a value for `key` that has not been called yet including + newly produced `item`s. The function terminates when no new `item`s are + produced. The resulting *list of attrsets* contains only *attrsets* with a + unique key. For example, + + ``` + builtins.genericClosure { + startSet = [ {key = 5;} ]; + operator = item: [{ + key = if (item.key / 2 ) * 2 == item.key + then item.key / 2 + else 3 * item.key + 1; + }]; + } + ``` + evaluates to + ``` + [ { key = 5; } { key = 16; } { key = 8; } { key = 4; } { key = 2; } { key = 1; } ] + ``` + )", + .fun = prim_genericClosure, +}); + /** * builtins.getAttr */