primops: Move genericClosure to attrset.cc

Change-Id: I0f8ae3dfcc1a4e4ce6e631e13e5b63ac03c1cd74
This commit is contained in:
Tom Hubrecht 2024-05-30 09:51:42 +02:00
parent 1e1cc1d741
commit 0bf532b0c1
2 changed files with 139 additions and 93 deletions

View file

@ -174,99 +174,6 @@ template<typename Callable>
} }
} }
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<Value *, decltype(cmp)> 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) static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {

View file

@ -133,6 +133,145 @@ static RegisterPrimOp primop_catAttrs({
.fun = prim_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<Value *, decltype(cmp)> 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 * builtins.getAttr
*/ */