primops: Move functions to primops/list.cc
Moved builtins: all, any, concatLists, concatMap, elem, elemAt, filter, foldl', head, length, listToAttrs, map, partition, sort, tail The CompareValues struct has been moved to primops.hh Change-Id: Ifc5457298215fd20c96aa8acac65749ed42c28dd
This commit is contained in:
parent
95968c44eb
commit
7f1ebde7b8
4 changed files with 772 additions and 602 deletions
|
@ -94,6 +94,7 @@ libexpr_sources = files(
|
|||
'primops/fetchMercurial.cc',
|
||||
'primops/fetchTree.cc',
|
||||
'primops/fromTOML.cc',
|
||||
'primops/list.cc',
|
||||
'primops/string.cc',
|
||||
'value/context.cc',
|
||||
)
|
||||
|
|
|
@ -558,63 +558,6 @@ template<typename Callable>
|
|||
}
|
||||
}
|
||||
|
||||
struct CompareValues
|
||||
{
|
||||
EvalState & state;
|
||||
const PosIdx pos;
|
||||
const std::string_view errorCtx;
|
||||
|
||||
CompareValues(EvalState & state, const PosIdx pos, const std::string_view && errorCtx) : state(state), pos(pos), errorCtx(errorCtx) { };
|
||||
|
||||
bool operator () (Value * v1, Value * v2) const
|
||||
{
|
||||
return (*this)(v1, v2, errorCtx);
|
||||
}
|
||||
|
||||
bool operator () (Value * v1, Value * v2, std::string_view errorCtx) const
|
||||
{
|
||||
try {
|
||||
if (v1->type() == nFloat && v2->type() == nInt)
|
||||
return v1->fpoint < v2->integer;
|
||||
if (v1->type() == nInt && v2->type() == nFloat)
|
||||
return v1->integer < v2->fpoint;
|
||||
if (v1->type() != v2->type())
|
||||
state.error<EvalError>("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow();
|
||||
// Allow selecting a subset of enum values
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
||||
switch (v1->type()) {
|
||||
case nInt:
|
||||
return v1->integer < v2->integer;
|
||||
case nFloat:
|
||||
return v1->fpoint < v2->fpoint;
|
||||
case nString:
|
||||
return strcmp(v1->string.s, v2->string.s) < 0;
|
||||
case nPath:
|
||||
return strcmp(v1->_path, v2->_path) < 0;
|
||||
case nList:
|
||||
// Lexicographic comparison
|
||||
for (size_t i = 0;; i++) {
|
||||
if (i == v2->listSize()) {
|
||||
return false;
|
||||
} else if (i == v1->listSize()) {
|
||||
return true;
|
||||
} else if (!state.eqValues(*v1->listElems()[i], *v2->listElems()[i], pos, errorCtx)) {
|
||||
return (*this)(v1->listElems()[i], v2->listElems()[i], "while comparing two list elements");
|
||||
}
|
||||
}
|
||||
default:
|
||||
state.error<EvalError>("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow();
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
} catch (Error & e) {
|
||||
if (!errorCtx.empty())
|
||||
e.addTrace(nullptr, errorCtx);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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");
|
||||
|
@ -2293,61 +2236,6 @@ static RegisterPrimOp primop_isAttrs({
|
|||
"nameN"; value = valueN;}] is transformed to {name1 = value1;
|
||||
... nameN = valueN;}. In case of duplicate occurrences of the same
|
||||
name, the first takes precedence. */
|
||||
static void prim_listToAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceList(*args[0], pos, "while evaluating the argument passed to builtins.listToAttrs");
|
||||
|
||||
auto attrs = state.buildBindings(args[0]->listSize());
|
||||
|
||||
std::set<Symbol> seen;
|
||||
|
||||
for (auto v2 : args[0]->listItems()) {
|
||||
state.forceAttrs(*v2, pos, "while evaluating an element of the list passed to builtins.listToAttrs");
|
||||
|
||||
Bindings::iterator j = getAttr(state, state.sName, v2->attrs, "in a {name=...; value=...;} pair");
|
||||
|
||||
auto name = state.forceStringNoCtx(*j->value, j->pos, "while evaluating the `name` attribute of an element of the list passed to builtins.listToAttrs");
|
||||
|
||||
auto sym = state.symbols.create(name);
|
||||
if (seen.insert(sym).second) {
|
||||
Bindings::iterator j2 = getAttr(state, state.sValue, v2->attrs, "in a {name=...; value=...;} pair");
|
||||
attrs.insert(sym, j2->value, j2->pos);
|
||||
}
|
||||
}
|
||||
|
||||
v.mkAttrs(attrs);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_listToAttrs({
|
||||
.name = "__listToAttrs",
|
||||
.args = {"e"},
|
||||
.doc = R"(
|
||||
Construct a set from a list specifying the names and values of each
|
||||
attribute. Each element of the list should be a set consisting of a
|
||||
string-valued attribute `name` specifying the name of the attribute,
|
||||
and an attribute `value` specifying its value.
|
||||
|
||||
In case of duplicate occurrences of the same name, the first
|
||||
takes precedence.
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
builtins.listToAttrs
|
||||
[ { name = "foo"; value = 123; }
|
||||
{ name = "bar"; value = 456; }
|
||||
{ name = "bar"; value = 420; }
|
||||
]
|
||||
```
|
||||
|
||||
evaluates to
|
||||
|
||||
```nix
|
||||
{ foo = 123; bar = 456; }
|
||||
```
|
||||
)",
|
||||
.fun = prim_listToAttrs,
|
||||
});
|
||||
|
||||
static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
|
@ -2409,499 +2297,16 @@ static RegisterPrimOp primop_isList({
|
|||
.fun = prim_isList,
|
||||
});
|
||||
|
||||
static void elemAt(EvalState & state, const PosIdx pos, Value & list, int n, Value & v)
|
||||
{
|
||||
state.forceList(list, pos, "while evaluating the first argument passed to builtins.elemAt");
|
||||
if (n < 0 || (unsigned int) n >= list.listSize())
|
||||
state.error<EvalError>(
|
||||
"list index %1% is out of bounds",
|
||||
n
|
||||
).atPos(pos).debugThrow();
|
||||
state.forceValue(*list.listElems()[n], pos);
|
||||
v = *list.listElems()[n];
|
||||
}
|
||||
|
||||
/* Return the n-1'th element of a list. */
|
||||
static void prim_elemAt(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
elemAt(state, pos, *args[0], state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.elemAt"), v);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_elemAt({
|
||||
.name = "__elemAt",
|
||||
.args = {"xs", "n"},
|
||||
.doc = R"(
|
||||
Return element *n* from the list *xs*. Elements are counted starting
|
||||
from 0. A fatal error occurs if the index is out of bounds.
|
||||
)",
|
||||
.fun = prim_elemAt,
|
||||
});
|
||||
|
||||
/* Return the first element of a list. */
|
||||
static void prim_head(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
elemAt(state, pos, *args[0], 0, v);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_head({
|
||||
.name = "__head",
|
||||
.args = {"list"},
|
||||
.doc = R"(
|
||||
Return the first element of a list; abort evaluation if the argument
|
||||
isn’t a list or is an empty list. You can test whether a list is
|
||||
empty by comparing it with `[]`.
|
||||
)",
|
||||
.fun = prim_head,
|
||||
});
|
||||
|
||||
/* Return a list consisting of everything but the first element of
|
||||
a list. Warning: this function takes O(n) time, so you probably
|
||||
don't want to use it! */
|
||||
static void prim_tail(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.tail");
|
||||
if (args[0]->listSize() == 0)
|
||||
state.error<EvalError>("'tail' called on an empty list").atPos(pos).debugThrow();
|
||||
|
||||
state.mkList(v, args[0]->listSize() - 1);
|
||||
for (unsigned int n = 0; n < v.listSize(); ++n)
|
||||
v.listElems()[n] = args[0]->listElems()[n + 1];
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_tail({
|
||||
.name = "__tail",
|
||||
.args = {"list"},
|
||||
.doc = R"(
|
||||
Return the second to last elements of a list; abort evaluation if
|
||||
the argument isn’t a list or is an empty list.
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> This function should generally be avoided since it's inefficient:
|
||||
> unlike Haskell's `tail`, it takes O(n) time, so recursing over a
|
||||
> list by repeatedly calling `tail` takes O(n^2) time.
|
||||
)",
|
||||
.fun = prim_tail,
|
||||
});
|
||||
|
||||
/* Apply a function to every element of a list. */
|
||||
static void prim_map(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.map");
|
||||
|
||||
if (args[1]->listSize() == 0) {
|
||||
v = *args[1];
|
||||
return;
|
||||
}
|
||||
|
||||
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.map");
|
||||
|
||||
state.mkList(v, args[1]->listSize());
|
||||
for (unsigned int n = 0; n < v.listSize(); ++n)
|
||||
(v.listElems()[n] = state.allocValue())->mkApp(
|
||||
args[0], args[1]->listElems()[n]);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_map({
|
||||
.name = "map",
|
||||
.args = {"f", "list"},
|
||||
.doc = R"(
|
||||
Apply the function *f* to each element in the list *list*. For
|
||||
example,
|
||||
|
||||
```nix
|
||||
map (x: "foo" + x) [ "bar" "bla" "abc" ]
|
||||
```
|
||||
|
||||
evaluates to `[ "foobar" "foobla" "fooabc" ]`.
|
||||
)",
|
||||
.fun = prim_map,
|
||||
});
|
||||
|
||||
/* Filter a list using a predicate; that is, return a list containing
|
||||
every element from the list for which the predicate function
|
||||
returns true. */
|
||||
static void prim_filter(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.filter");
|
||||
|
||||
if (args[1]->listSize() == 0) {
|
||||
v = *args[1];
|
||||
return;
|
||||
}
|
||||
|
||||
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filter");
|
||||
|
||||
SmallValueVector<nonRecursiveStackReservation> vs(args[1]->listSize());
|
||||
size_t k = 0;
|
||||
|
||||
bool same = true;
|
||||
for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
|
||||
Value res;
|
||||
state.callFunction(*args[0], *args[1]->listElems()[n], res, noPos);
|
||||
if (state.forceBool(res, pos, "while evaluating the return value of the filtering function passed to builtins.filter"))
|
||||
vs[k++] = args[1]->listElems()[n];
|
||||
else
|
||||
same = false;
|
||||
}
|
||||
|
||||
if (same)
|
||||
v = *args[1];
|
||||
else {
|
||||
state.mkList(v, k);
|
||||
for (unsigned int n = 0; n < k; ++n) v.listElems()[n] = vs[n];
|
||||
}
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_filter({
|
||||
.name = "__filter",
|
||||
.args = {"f", "list"},
|
||||
.doc = R"(
|
||||
Return a list consisting of the elements of *list* for which the
|
||||
function *f* returns `true`.
|
||||
)",
|
||||
.fun = prim_filter,
|
||||
});
|
||||
|
||||
/* Return true if a list contains a given element. */
|
||||
static void prim_elem(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
bool res = false;
|
||||
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.elem");
|
||||
for (auto elem : args[1]->listItems())
|
||||
if (state.eqValues(*args[0], *elem, pos, "while searching for the presence of the given element in the list")) {
|
||||
res = true;
|
||||
break;
|
||||
}
|
||||
v.mkBool(res);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_elem({
|
||||
.name = "__elem",
|
||||
.args = {"x", "xs"},
|
||||
.doc = R"(
|
||||
Return `true` if a value equal to *x* occurs in the list *xs*, and
|
||||
`false` otherwise.
|
||||
)",
|
||||
.fun = prim_elem,
|
||||
});
|
||||
|
||||
/* Concatenate a list of lists. */
|
||||
static void prim_concatLists(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.concatLists");
|
||||
state.concatLists(v, args[0]->listSize(), args[0]->listElems(), pos, "while evaluating a value of the list passed to builtins.concatLists");
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_concatLists({
|
||||
.name = "__concatLists",
|
||||
.args = {"lists"},
|
||||
.doc = R"(
|
||||
Concatenate a list of lists into a single list.
|
||||
)",
|
||||
.fun = prim_concatLists,
|
||||
});
|
||||
|
||||
/* Return the length of a list. This is an O(1) time operation. */
|
||||
static void prim_length(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.length");
|
||||
v.mkInt(args[0]->listSize());
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_length({
|
||||
.name = "__length",
|
||||
.args = {"e"},
|
||||
.doc = R"(
|
||||
Return the length of the list *e*.
|
||||
)",
|
||||
.fun = prim_length,
|
||||
});
|
||||
|
||||
/* Reduce a list by applying a binary operator, from left to
|
||||
right. The operator is applied strictly. */
|
||||
static void prim_foldlStrict(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.foldlStrict");
|
||||
state.forceList(*args[2], pos, "while evaluating the third argument passed to builtins.foldlStrict");
|
||||
|
||||
if (args[2]->listSize()) {
|
||||
Value * vCur = args[1];
|
||||
|
||||
for (auto [n, elem] : enumerate(args[2]->listItems())) {
|
||||
Value * vs []{vCur, elem};
|
||||
vCur = n == args[2]->listSize() - 1 ? &v : state.allocValue();
|
||||
state.callFunction(*args[0], 2, vs, *vCur, pos);
|
||||
}
|
||||
state.forceValue(v, pos);
|
||||
} else {
|
||||
state.forceValue(*args[1], pos);
|
||||
v = *args[1];
|
||||
}
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_foldlStrict({
|
||||
.name = "__foldl'",
|
||||
.args = {"op", "nul", "list"},
|
||||
.doc = R"(
|
||||
Reduce a list by applying a binary operator, from left to right,
|
||||
e.g. `foldl' op nul [x0 x1 x2 ...] = op (op (op nul x0) x1) x2)
|
||||
...`. For example, `foldl' (x: y: x + y) 0 [1 2 3]` evaluates to 6.
|
||||
The return value of each application of `op` is evaluated immediately,
|
||||
even for intermediate values.
|
||||
)",
|
||||
.fun = prim_foldlStrict,
|
||||
});
|
||||
|
||||
static void anyOrAll(bool any, EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceFunction(*args[0], pos, std::string("while evaluating the first argument passed to builtins.") + (any ? "any" : "all"));
|
||||
state.forceList(*args[1], pos, std::string("while evaluating the second argument passed to builtins.") + (any ? "any" : "all"));
|
||||
|
||||
std::string_view errorCtx = any
|
||||
? "while evaluating the return value of the function passed to builtins.any"
|
||||
: "while evaluating the return value of the function passed to builtins.all";
|
||||
|
||||
Value vTmp;
|
||||
for (auto elem : args[1]->listItems()) {
|
||||
state.callFunction(*args[0], *elem, vTmp, pos);
|
||||
bool res = state.forceBool(vTmp, pos, errorCtx);
|
||||
if (res == any) {
|
||||
v.mkBool(any);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
v.mkBool(!any);
|
||||
}
|
||||
|
||||
|
||||
static void prim_any(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
anyOrAll(true, state, pos, args, v);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_any({
|
||||
.name = "__any",
|
||||
.args = {"pred", "list"},
|
||||
.doc = R"(
|
||||
Return `true` if the function *pred* returns `true` for at least one
|
||||
element of *list*, and `false` otherwise.
|
||||
)",
|
||||
.fun = prim_any,
|
||||
});
|
||||
|
||||
static void prim_all(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
anyOrAll(false, state, pos, args, v);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_all({
|
||||
.name = "__all",
|
||||
.args = {"pred", "list"},
|
||||
.doc = R"(
|
||||
Return `true` if the function *pred* returns `true` for all elements
|
||||
of *list*, and `false` otherwise.
|
||||
)",
|
||||
.fun = prim_all,
|
||||
});
|
||||
|
||||
static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
auto len = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList");
|
||||
|
||||
if (len < 0)
|
||||
state.error<EvalError>("cannot create list of size %1%", len).atPos(pos).debugThrow();
|
||||
|
||||
// More strict than striclty (!) necessary, but acceptable
|
||||
// as evaluating map without accessing any values makes little sense.
|
||||
state.forceFunction(*args[0], noPos, "while evaluating the first argument passed to builtins.genList");
|
||||
|
||||
state.mkList(v, len);
|
||||
for (unsigned int n = 0; n < (unsigned int) len; ++n) {
|
||||
auto arg = state.allocValue();
|
||||
arg->mkInt(n);
|
||||
(v.listElems()[n] = state.allocValue())->mkApp(args[0], arg);
|
||||
}
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_genList({
|
||||
.name = "__genList",
|
||||
.args = {"generator", "length"},
|
||||
.doc = R"(
|
||||
Generate list of size *length*, with each element *i* equal to the
|
||||
value returned by *generator* `i`. For example,
|
||||
|
||||
```nix
|
||||
builtins.genList (x: x * x) 5
|
||||
```
|
||||
|
||||
returns the list `[ 0 1 4 9 16 ]`.
|
||||
)",
|
||||
.fun = prim_genList,
|
||||
});
|
||||
|
||||
static void prim_lessThan(EvalState & state, const PosIdx pos, Value * * args, Value & v);
|
||||
|
||||
|
||||
static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.sort");
|
||||
|
||||
auto len = args[1]->listSize();
|
||||
if (len == 0) {
|
||||
v = *args[1];
|
||||
return;
|
||||
}
|
||||
|
||||
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.sort");
|
||||
|
||||
state.mkList(v, len);
|
||||
for (unsigned int n = 0; n < len; ++n) {
|
||||
state.forceValue(*args[1]->listElems()[n], pos);
|
||||
v.listElems()[n] = args[1]->listElems()[n];
|
||||
}
|
||||
|
||||
auto comparator = [&](Value * a, Value * b) {
|
||||
/* Optimization: if the comparator is lessThan, bypass
|
||||
callFunction. */
|
||||
/* TODO: (layus) this is absurd. An optimisation like this
|
||||
should be outside the lambda creation */
|
||||
if (args[0]->isPrimOp()) {
|
||||
auto ptr = args[0]->primOp->fun.target<decltype(&prim_lessThan)>();
|
||||
if (ptr && *ptr == prim_lessThan)
|
||||
return CompareValues(state, noPos, "while evaluating the ordering function passed to builtins.sort")(a, b);
|
||||
}
|
||||
|
||||
Value * vs[] = {a, b};
|
||||
Value vBool;
|
||||
state.callFunction(*args[0], 2, vs, vBool, noPos);
|
||||
return state.forceBool(vBool, pos, "while evaluating the return value of the sorting function passed to builtins.sort");
|
||||
};
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_sort({
|
||||
.name = "__sort",
|
||||
.args = {"comparator", "list"},
|
||||
.doc = R"(
|
||||
Return *list* in sorted order. It repeatedly calls the function
|
||||
*comparator* with two elements. The comparator should return `true`
|
||||
if the first element is less than the second, and `false` otherwise.
|
||||
For example,
|
||||
|
||||
```nix
|
||||
builtins.sort builtins.lessThan [ 483 249 526 147 42 77 ]
|
||||
```
|
||||
|
||||
produces the list `[ 42 77 147 249 483 526 ]`.
|
||||
|
||||
This is a stable sort: it preserves the relative order of elements
|
||||
deemed equal by the comparator.
|
||||
)",
|
||||
.fun = prim_sort,
|
||||
});
|
||||
|
||||
static void prim_partition(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.partition");
|
||||
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.partition");
|
||||
|
||||
auto len = args[1]->listSize();
|
||||
|
||||
ValueVector right, wrong;
|
||||
|
||||
for (unsigned int n = 0; n < len; ++n) {
|
||||
auto vElem = args[1]->listElems()[n];
|
||||
state.forceValue(*vElem, pos);
|
||||
Value res;
|
||||
state.callFunction(*args[0], *vElem, res, pos);
|
||||
if (state.forceBool(res, pos, "while evaluating the return value of the partition function passed to builtins.partition"))
|
||||
right.push_back(vElem);
|
||||
else
|
||||
wrong.push_back(vElem);
|
||||
}
|
||||
|
||||
auto attrs = state.buildBindings(2);
|
||||
|
||||
auto & vRight = attrs.alloc(state.sRight);
|
||||
auto rsize = right.size();
|
||||
state.mkList(vRight, rsize);
|
||||
if (rsize)
|
||||
memcpy(vRight.listElems(), right.data(), sizeof(Value *) * rsize);
|
||||
|
||||
auto & vWrong = attrs.alloc(state.sWrong);
|
||||
auto wsize = wrong.size();
|
||||
state.mkList(vWrong, wsize);
|
||||
if (wsize)
|
||||
memcpy(vWrong.listElems(), wrong.data(), sizeof(Value *) * wsize);
|
||||
|
||||
v.mkAttrs(attrs);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_partition({
|
||||
.name = "__partition",
|
||||
.args = {"pred", "list"},
|
||||
.doc = R"(
|
||||
Given a predicate function *pred*, this function returns an
|
||||
attrset containing a list named `right`, containing the elements
|
||||
in *list* for which *pred* returned `true`, and a list named
|
||||
`wrong`, containing the elements for which it returned
|
||||
`false`. For example,
|
||||
|
||||
```nix
|
||||
builtins.partition (x: x > 10) [1 23 9 3 42]
|
||||
```
|
||||
|
||||
evaluates to
|
||||
|
||||
```nix
|
||||
{ right = [ 23 42 ]; wrong = [ 1 9 3 ]; }
|
||||
```
|
||||
)",
|
||||
.fun = prim_partition,
|
||||
});
|
||||
|
||||
static void prim_concatMap(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.concatMap");
|
||||
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.concatMap");
|
||||
auto nrLists = args[1]->listSize();
|
||||
|
||||
// List of returned lists before concatenation. References to these Values must NOT be persisted.
|
||||
SmallTemporaryValueVector<conservativeStackReservation> lists(nrLists);
|
||||
size_t len = 0;
|
||||
|
||||
for (unsigned int n = 0; n < nrLists; ++n) {
|
||||
Value * vElem = args[1]->listElems()[n];
|
||||
state.callFunction(*args[0], *vElem, lists[n], pos);
|
||||
state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)), "while evaluating the return value of the function passed to builtins.concatMap");
|
||||
len += lists[n].listSize();
|
||||
}
|
||||
|
||||
state.mkList(v, len);
|
||||
auto out = v.listElems();
|
||||
for (unsigned int n = 0, pos = 0; n < nrLists; ++n) {
|
||||
auto l = lists[n].listSize();
|
||||
if (l)
|
||||
memcpy(out + pos, lists[n].listElems(), l * sizeof(Value *));
|
||||
pos += l;
|
||||
}
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_concatMap({
|
||||
.name = "__concatMap",
|
||||
.args = {"f", "list"},
|
||||
.doc = R"(
|
||||
This function is equivalent to `builtins.concatLists (map f list)`
|
||||
but is more efficient.
|
||||
)",
|
||||
.fun = prim_concatMap,
|
||||
});
|
||||
|
||||
|
||||
/*************************************************************
|
||||
|
@ -3048,7 +2453,7 @@ static RegisterPrimOp primop_bitXor({
|
|||
.fun = prim_bitXor,
|
||||
});
|
||||
|
||||
static void prim_lessThan(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
void prim_lessThan(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceValue(*args[0], pos);
|
||||
state.forceValue(*args[1], pos);
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#include "eval.hh"
|
||||
|
||||
#include <regex>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace nix {
|
||||
|
@ -52,6 +51,8 @@ void prim_importNative(EvalState & state, const PosIdx pos, Value ** args, Value
|
|||
*/
|
||||
void prim_exec(EvalState & state, const PosIdx pos, Value ** args, Value & v);
|
||||
|
||||
void prim_lessThan(EvalState & state, const PosIdx pos, Value ** args, Value & v);
|
||||
|
||||
void makePositionThunks(EvalState & state, const PosIdx pos, Value & line, Value & column);
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
|
@ -64,7 +65,8 @@ typedef std::list<Value *> ValueList;
|
|||
* getAttr wrapper
|
||||
*/
|
||||
|
||||
Bindings::iterator getAttr(EvalState & state, Symbol attrSym, Bindings * attrSet, std::string_view errorCtx);
|
||||
Bindings::iterator
|
||||
getAttr(EvalState & state, Symbol attrSym, Bindings * attrSet, std::string_view errorCtx);
|
||||
|
||||
/**
|
||||
* Struct definitions
|
||||
|
@ -79,18 +81,100 @@ struct RegexCache
|
|||
std::regex get(std::string_view re)
|
||||
{
|
||||
auto it = cache.find(re);
|
||||
if (it != cache.end())
|
||||
if (it != cache.end()) {
|
||||
return it->second;
|
||||
}
|
||||
keys.emplace_back(re);
|
||||
return cache.emplace(keys.back(), std::regex(keys.back(), std::regex::extended)).first->second;
|
||||
return cache.emplace(keys.back(), std::regex(keys.back(), std::regex::extended))
|
||||
.first->second;
|
||||
}
|
||||
};
|
||||
|
||||
struct RealisePathFlags {
|
||||
struct CompareValues
|
||||
{
|
||||
EvalState & state;
|
||||
const PosIdx pos;
|
||||
const std::string_view errorCtx;
|
||||
|
||||
CompareValues(EvalState & state, const PosIdx pos, const std::string_view && errorCtx)
|
||||
: state(state)
|
||||
, pos(pos)
|
||||
, errorCtx(errorCtx){};
|
||||
|
||||
bool operator()(Value * v1, Value * v2) const
|
||||
{
|
||||
return (*this)(v1, v2, errorCtx);
|
||||
}
|
||||
|
||||
bool operator()(Value * v1, Value * v2, std::string_view errorCtx) const
|
||||
{
|
||||
try {
|
||||
if (v1->type() == nFloat && v2->type() == nInt) {
|
||||
return v1->fpoint < v2->integer;
|
||||
}
|
||||
if (v1->type() == nInt && v2->type() == nFloat) {
|
||||
return v1->integer < v2->fpoint;
|
||||
}
|
||||
if (v1->type() != v2->type()) {
|
||||
state.error<EvalError>("cannot compare %s with %s", showType(*v1), showType(*v2))
|
||||
.debugThrow();
|
||||
}
|
||||
// Allow selecting a subset of enum values
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
||||
switch (v1->type()) {
|
||||
case nInt:
|
||||
return v1->integer < v2->integer;
|
||||
case nFloat:
|
||||
return v1->fpoint < v2->fpoint;
|
||||
case nString:
|
||||
return strcmp(v1->string.s, v2->string.s) < 0;
|
||||
case nPath:
|
||||
return strcmp(v1->_path, v2->_path) < 0;
|
||||
case nList:
|
||||
// Lexicographic comparison
|
||||
for (size_t i = 0;; i++) {
|
||||
if (i == v2->listSize()) {
|
||||
return false;
|
||||
} else if (i == v1->listSize()) {
|
||||
return true;
|
||||
} else if (!state.eqValues(
|
||||
*v1->listElems()[i], *v2->listElems()[i], pos, errorCtx
|
||||
))
|
||||
{
|
||||
return (*this)(
|
||||
v1->listElems()[i],
|
||||
v2->listElems()[i],
|
||||
"while comparing two list elements"
|
||||
);
|
||||
}
|
||||
}
|
||||
default:
|
||||
state
|
||||
.error<EvalError>(
|
||||
"cannot compare %s with %s; values of that type are incomparable",
|
||||
showType(*v1),
|
||||
showType(*v2)
|
||||
)
|
||||
.debugThrow();
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
} catch (Error & e) {
|
||||
if (!errorCtx.empty()) {
|
||||
e.addTrace(nullptr, errorCtx);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct RealisePathFlags
|
||||
{
|
||||
// Whether to check that the path is allowed in pure eval mode
|
||||
bool checkForPureEval = true;
|
||||
};
|
||||
|
||||
SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, const RealisePathFlags flags = {});
|
||||
SourcePath
|
||||
realisePath(EvalState & state, const PosIdx pos, Value & v, const RealisePathFlags flags = {});
|
||||
|
||||
}
|
||||
|
|
680
src/libexpr/primops/list.cc
Normal file
680
src/libexpr/primops/list.cc
Normal file
|
@ -0,0 +1,680 @@
|
|||
#include "gc-small-vector.hh"
|
||||
#include "primops.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
static void anyOrAll(bool any, EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
const std::string fun = any ? "builtins.any" : "builtins.all";
|
||||
state.forceFunction(
|
||||
*args[0], pos, std::string("while evaluating the first argument passed to " + fun)
|
||||
);
|
||||
state.forceList(
|
||||
*args[1], pos, std::string("while evaluating the second argument passed to " + fun)
|
||||
);
|
||||
Value vTmp;
|
||||
for (auto elem : args[1]->listItems()) {
|
||||
state.callFunction(*args[0], *elem, vTmp, pos);
|
||||
bool res = state.forceBool(
|
||||
vTmp, pos, "while evaluating the return value of the function passed to " + fun
|
||||
);
|
||||
if (res == any) {
|
||||
v.mkBool(any);
|
||||
return;
|
||||
}
|
||||
}
|
||||
v.mkBool(!any);
|
||||
}
|
||||
|
||||
static void elemAt(EvalState & state, const PosIdx pos, Value & list, int n, Value & v)
|
||||
{
|
||||
state.forceList(list, pos, "while evaluating the first argument passed to builtins.elemAt");
|
||||
if (n < 0 || (unsigned int) n >= list.listSize()) {
|
||||
state.error<EvalError>("list index %1% is out of bounds", n).atPos(pos).debugThrow();
|
||||
}
|
||||
state.forceValue(*list.listElems()[n], pos);
|
||||
v = *list.listElems()[n];
|
||||
}
|
||||
|
||||
/**
|
||||
* builtins.all
|
||||
*/
|
||||
|
||||
static void prim_all(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
anyOrAll(false, state, pos, args, v);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_all({
|
||||
.name = "__all",
|
||||
.args = {"pred", "list"},
|
||||
.doc = R"(
|
||||
Return `true` if the function *pred* returns `true` for all elements
|
||||
of *list*, and `false` otherwise.
|
||||
)",
|
||||
.fun = prim_all,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.any
|
||||
*/
|
||||
|
||||
static void prim_any(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
anyOrAll(true, state, pos, args, v);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_any({
|
||||
.name = "__any",
|
||||
.args = {"pred", "list"},
|
||||
.doc = R"(
|
||||
Return `true` if the function *pred* returns `true` for at least one
|
||||
element of *list*, and `false` otherwise.
|
||||
)",
|
||||
.fun = prim_any,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.concatLists
|
||||
*/
|
||||
|
||||
static void prim_concatLists(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
state.forceList(
|
||||
*args[0], pos, "while evaluating the first argument passed to builtins.concatLists"
|
||||
);
|
||||
state.concatLists(
|
||||
v,
|
||||
args[0]->listSize(),
|
||||
args[0]->listElems(),
|
||||
pos,
|
||||
"while evaluating a value of the list passed to builtins.concatLists"
|
||||
);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_concatLists({
|
||||
.name = "__concatLists",
|
||||
.args = {"lists"},
|
||||
.doc = R"(
|
||||
Concatenate a list of lists into a single list.
|
||||
)",
|
||||
.fun = prim_concatLists,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.concatMap
|
||||
*/
|
||||
|
||||
static void prim_concatMap(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
state.forceFunction(
|
||||
*args[0], pos, "while evaluating the first argument passed to builtins.concatMap"
|
||||
);
|
||||
state.forceList(
|
||||
*args[1], pos, "while evaluating the second argument passed to builtins.concatMap"
|
||||
);
|
||||
auto nrLists = args[1]->listSize();
|
||||
|
||||
// List of returned lists before concatenation. References to these Values must NOT be
|
||||
// persisted.
|
||||
SmallTemporaryValueVector<conservativeStackReservation> lists(nrLists);
|
||||
size_t len = 0;
|
||||
|
||||
for (unsigned int n = 0; n < nrLists; ++n) {
|
||||
Value * vElem = args[1]->listElems()[n];
|
||||
state.callFunction(*args[0], *vElem, lists[n], pos);
|
||||
state.forceList(
|
||||
lists[n],
|
||||
lists[n].determinePos(args[0]->determinePos(pos)),
|
||||
"while evaluating the return value of the function passed to builtins.concatMap"
|
||||
);
|
||||
len += lists[n].listSize();
|
||||
}
|
||||
|
||||
state.mkList(v, len);
|
||||
auto out = v.listElems();
|
||||
for (unsigned int n = 0, pos = 0; n < nrLists; ++n) {
|
||||
auto l = lists[n].listSize();
|
||||
if (l) {
|
||||
memcpy(out + pos, lists[n].listElems(), l * sizeof(Value *));
|
||||
}
|
||||
pos += l;
|
||||
}
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_concatMap({
|
||||
.name = "__concatMap",
|
||||
.args = {"f", "list"},
|
||||
.doc = R"(
|
||||
This function is equivalent to `builtins.concatLists (map f list)`
|
||||
but is more efficient.
|
||||
)",
|
||||
.fun = prim_concatMap,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.elem
|
||||
*/
|
||||
|
||||
static void prim_elem(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
bool res = false;
|
||||
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.elem");
|
||||
for (auto elem : args[1]->listItems()) {
|
||||
if (state.eqValues(
|
||||
*args[0],
|
||||
*elem,
|
||||
pos,
|
||||
"while searching for the presence of the given element in the list"
|
||||
))
|
||||
{
|
||||
res = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
v.mkBool(res);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_elem({
|
||||
.name = "__elem",
|
||||
.args = {"x", "xs"},
|
||||
.doc = R"(
|
||||
Return `true` if a value equal to *x* occurs in the list *xs*, and
|
||||
`false` otherwise.
|
||||
)",
|
||||
.fun = prim_elem,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.elemAt
|
||||
*/
|
||||
|
||||
static void prim_elemAt(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
elemAt(
|
||||
state,
|
||||
pos,
|
||||
*args[0],
|
||||
state.forceInt(
|
||||
*args[1], pos, "while evaluating the second argument passed to builtins.elemAt"
|
||||
),
|
||||
v
|
||||
);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_elemAt({
|
||||
.name = "__elemAt",
|
||||
.args = {"xs", "n"},
|
||||
.doc = R"(
|
||||
Return element *n* from the list *xs*. Elements are counted starting
|
||||
from 0. A fatal error occurs if the index is out of bounds.
|
||||
)",
|
||||
.fun = prim_elemAt,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.filter
|
||||
*/
|
||||
|
||||
static void prim_filter(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
state.forceList(
|
||||
*args[1], pos, "while evaluating the second argument passed to builtins.filter"
|
||||
);
|
||||
|
||||
if (args[1]->listSize() == 0) {
|
||||
v = *args[1];
|
||||
return;
|
||||
}
|
||||
|
||||
state.forceFunction(
|
||||
*args[0], pos, "while evaluating the first argument passed to builtins.filter"
|
||||
);
|
||||
|
||||
SmallValueVector<nonRecursiveStackReservation> vs(args[1]->listSize());
|
||||
size_t k = 0;
|
||||
|
||||
bool same = true;
|
||||
for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
|
||||
Value res;
|
||||
state.callFunction(*args[0], *args[1]->listElems()[n], res, noPos);
|
||||
if (state.forceBool(
|
||||
res,
|
||||
pos,
|
||||
"while evaluating the return value of the filtering function passed to "
|
||||
"builtins.filter"
|
||||
))
|
||||
{
|
||||
vs[k++] = args[1]->listElems()[n];
|
||||
} else {
|
||||
same = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (same) {
|
||||
v = *args[1];
|
||||
} else {
|
||||
state.mkList(v, k);
|
||||
for (unsigned int n = 0; n < k; ++n) {
|
||||
v.listElems()[n] = vs[n];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* builtins.foldl'
|
||||
*/
|
||||
|
||||
static void prim_foldlStrict(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
state.forceFunction(
|
||||
*args[0], pos, "while evaluating the first argument passed to builtins.foldlStrict"
|
||||
);
|
||||
state.forceList(
|
||||
*args[2], pos, "while evaluating the third argument passed to builtins.foldlStrict"
|
||||
);
|
||||
if (args[2]->listSize()) {
|
||||
Value * vCur = args[1];
|
||||
for (auto [n, elem] : enumerate(args[2]->listItems())) {
|
||||
Value * vs[]{vCur, elem};
|
||||
vCur = n == args[2]->listSize() - 1 ? &v : state.allocValue();
|
||||
state.callFunction(*args[0], 2, vs, *vCur, pos);
|
||||
}
|
||||
state.forceValue(v, pos);
|
||||
} else {
|
||||
state.forceValue(*args[1], pos);
|
||||
v = *args[1];
|
||||
}
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_foldlStrict({
|
||||
.name = "__foldl'",
|
||||
.args = {"op", "nul", "list"},
|
||||
.doc = R"(
|
||||
Reduce a list by applying a binary operator, from left to right,
|
||||
e.g. `foldl' op nul [x0 x1 x2 ...] = op (op (op nul x0) x1) x2)
|
||||
...`. For example, `foldl' (x: y: x + y) 0 [1 2 3]` evaluates to 6.
|
||||
The return value of each application of `op` is evaluated immediately,
|
||||
even for intermediate values.
|
||||
)",
|
||||
.fun = prim_foldlStrict,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.genList
|
||||
*/
|
||||
|
||||
static void prim_genList(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
auto len = state.forceInt(
|
||||
*args[1], pos, "while evaluating the second argument passed to builtins.genList"
|
||||
);
|
||||
if (len < 0) {
|
||||
state.error<EvalError>("cannot create list of size %1%", len).atPos(pos).debugThrow();
|
||||
}
|
||||
// More strict than striclty (!) necessary, but acceptable
|
||||
// as evaluating map without accessing any values makes little sense.
|
||||
state.forceFunction(
|
||||
*args[0], noPos, "while evaluating the first argument passed to builtins.genList"
|
||||
);
|
||||
state.mkList(v, len);
|
||||
for (unsigned int n = 0; n < (unsigned int) len; ++n) {
|
||||
auto arg = state.allocValue();
|
||||
arg->mkInt(n);
|
||||
(v.listElems()[n] = state.allocValue())->mkApp(args[0], arg);
|
||||
}
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_genList({
|
||||
.name = "__genList",
|
||||
.args = {"generator", "length"},
|
||||
.doc = R"(
|
||||
Generate list of size *length*, with each element *i* equal to the
|
||||
value returned by *generator* `i`. For example,
|
||||
```nix
|
||||
builtins.genList (x: x * x) 5
|
||||
```
|
||||
returns the list `[ 0 1 4 9 16 ]`.
|
||||
)",
|
||||
.fun = prim_genList,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.head
|
||||
*/
|
||||
|
||||
static void prim_head(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
elemAt(state, pos, *args[0], 0, v);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_head({
|
||||
.name = "__head",
|
||||
.args = {"list"},
|
||||
.doc = R"(
|
||||
Return the first element of a list; abort evaluation if the argument
|
||||
isn’t a list or is an empty list. You can test whether a list is
|
||||
empty by comparing it with `[]`.
|
||||
)",
|
||||
.fun = prim_head,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.length
|
||||
*/
|
||||
|
||||
static void prim_length(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.length");
|
||||
v.mkInt(args[0]->listSize());
|
||||
}
|
||||
static RegisterPrimOp primop_length({
|
||||
.name = "__length",
|
||||
.args = {"e"},
|
||||
.doc = R"(
|
||||
Return the length of the list *e*.
|
||||
)",
|
||||
.fun = prim_length,
|
||||
});
|
||||
|
||||
static RegisterPrimOp primop_filter({
|
||||
.name = "__filter",
|
||||
.args = {"f", "list"},
|
||||
.doc = R"(
|
||||
Return a list consisting of the elements of *list* for which the
|
||||
function *f* returns `true`.
|
||||
)",
|
||||
.fun = prim_filter,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.listToAttrs
|
||||
*/
|
||||
|
||||
static void prim_listToAttrs(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
state.forceList(*args[0], pos, "while evaluating the argument passed to builtins.listToAttrs");
|
||||
|
||||
auto attrs = state.buildBindings(args[0]->listSize());
|
||||
|
||||
std::set<Symbol> seen;
|
||||
|
||||
for (auto v2 : args[0]->listItems()) {
|
||||
state.forceAttrs(
|
||||
*v2, pos, "while evaluating an element of the list passed to builtins.listToAttrs"
|
||||
);
|
||||
|
||||
Bindings::iterator j =
|
||||
getAttr(state, state.sName, v2->attrs, "in a {name=...; value=...;} pair");
|
||||
|
||||
auto name = state.forceStringNoCtx(
|
||||
*j->value,
|
||||
j->pos,
|
||||
"while evaluating the `name` attribute of an element of the list passed to "
|
||||
"builtins.listToAttrs"
|
||||
);
|
||||
|
||||
auto sym = state.symbols.create(name);
|
||||
if (seen.insert(sym).second) {
|
||||
Bindings::iterator j2 =
|
||||
getAttr(state, state.sValue, v2->attrs, "in a {name=...; value=...;} pair");
|
||||
attrs.insert(sym, j2->value, j2->pos);
|
||||
}
|
||||
}
|
||||
|
||||
v.mkAttrs(attrs);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_listToAttrs({
|
||||
.name = "__listToAttrs",
|
||||
.args = {"e"},
|
||||
.doc = R"(
|
||||
Construct a set from a list specifying the names and values of each
|
||||
attribute. Each element of the list should be a set consisting of a
|
||||
string-valued attribute `name` specifying the name of the attribute,
|
||||
and an attribute `value` specifying its value.
|
||||
|
||||
In case of duplicate occurrences of the same name, the first
|
||||
takes precedence.
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
builtins.listToAttrs
|
||||
[ { name = "foo"; value = 123; }
|
||||
{ name = "bar"; value = 456; }
|
||||
{ name = "bar"; value = 420; }
|
||||
]
|
||||
```
|
||||
|
||||
evaluates to
|
||||
|
||||
```nix
|
||||
{ foo = 123; bar = 456; }
|
||||
```
|
||||
)",
|
||||
.fun = prim_listToAttrs,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.map
|
||||
*/
|
||||
|
||||
static void prim_map(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.map");
|
||||
|
||||
if (args[1]->listSize() == 0) {
|
||||
v = *args[1];
|
||||
return;
|
||||
}
|
||||
|
||||
state.forceFunction(
|
||||
*args[0], pos, "while evaluating the first argument passed to builtins.map"
|
||||
);
|
||||
|
||||
state.mkList(v, args[1]->listSize());
|
||||
for (unsigned int n = 0; n < v.listSize(); ++n) {
|
||||
(v.listElems()[n] = state.allocValue())->mkApp(args[0], args[1]->listElems()[n]);
|
||||
}
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_map({
|
||||
.name = "map",
|
||||
.args = {"f", "list"},
|
||||
.doc = R"(
|
||||
Apply the function *f* to each element in the list *list*. For
|
||||
example,
|
||||
|
||||
```nix
|
||||
map (x: "foo" + x) [ "bar" "bla" "abc" ]
|
||||
```
|
||||
|
||||
evaluates to `[ "foobar" "foobla" "fooabc" ]`.
|
||||
)",
|
||||
.fun = prim_map,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.partition
|
||||
*/
|
||||
|
||||
static void prim_partition(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
state.forceFunction(
|
||||
*args[0], pos, "while evaluating the first argument passed to builtins.partition"
|
||||
);
|
||||
state.forceList(
|
||||
*args[1], pos, "while evaluating the second argument passed to builtins.partition"
|
||||
);
|
||||
|
||||
auto len = args[1]->listSize();
|
||||
|
||||
ValueVector right, wrong;
|
||||
|
||||
for (unsigned int n = 0; n < len; ++n) {
|
||||
auto vElem = args[1]->listElems()[n];
|
||||
state.forceValue(*vElem, pos);
|
||||
Value res;
|
||||
state.callFunction(*args[0], *vElem, res, pos);
|
||||
if (state.forceBool(
|
||||
res,
|
||||
pos,
|
||||
"while evaluating the return value of the partition function passed to "
|
||||
"builtins.partition"
|
||||
))
|
||||
{
|
||||
right.push_back(vElem);
|
||||
} else {
|
||||
wrong.push_back(vElem);
|
||||
}
|
||||
}
|
||||
|
||||
auto attrs = state.buildBindings(2);
|
||||
|
||||
auto & vRight = attrs.alloc(state.sRight);
|
||||
auto rsize = right.size();
|
||||
state.mkList(vRight, rsize);
|
||||
if (rsize) {
|
||||
memcpy(vRight.listElems(), right.data(), sizeof(Value *) * rsize);
|
||||
}
|
||||
|
||||
auto & vWrong = attrs.alloc(state.sWrong);
|
||||
auto wsize = wrong.size();
|
||||
state.mkList(vWrong, wsize);
|
||||
if (wsize) {
|
||||
memcpy(vWrong.listElems(), wrong.data(), sizeof(Value *) * wsize);
|
||||
}
|
||||
|
||||
v.mkAttrs(attrs);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_partition({
|
||||
.name = "__partition",
|
||||
.args = {"pred", "list"},
|
||||
.doc = R"(
|
||||
Given a predicate function *pred*, this function returns an
|
||||
attrset containing a list named `right`, containing the elements
|
||||
in *list* for which *pred* returned `true`, and a list named
|
||||
`wrong`, containing the elements for which it returned
|
||||
`false`. For example,
|
||||
|
||||
```nix
|
||||
builtins.partition (x: x > 10) [1 23 9 3 42]
|
||||
```
|
||||
|
||||
evaluates to
|
||||
|
||||
```nix
|
||||
{ right = [ 23 42 ]; wrong = [ 1 9 3 ]; }
|
||||
```
|
||||
)",
|
||||
.fun = prim_partition,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.sort
|
||||
*/
|
||||
|
||||
static void prim_sort(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.sort");
|
||||
|
||||
auto len = args[1]->listSize();
|
||||
if (len == 0) {
|
||||
v = *args[1];
|
||||
return;
|
||||
}
|
||||
|
||||
state.forceFunction(
|
||||
*args[0], pos, "while evaluating the first argument passed to builtins.sort"
|
||||
);
|
||||
|
||||
state.mkList(v, len);
|
||||
for (unsigned int n = 0; n < len; ++n) {
|
||||
state.forceValue(*args[1]->listElems()[n], pos);
|
||||
v.listElems()[n] = args[1]->listElems()[n];
|
||||
}
|
||||
|
||||
auto comparator = [&](Value * a, Value * b) {
|
||||
/* Optimization: if the comparator is lessThan, bypass
|
||||
callFunction. */
|
||||
/* TODO: (layus) this is absurd. An optimisation like this
|
||||
should be outside the lambda creation */
|
||||
if (args[0]->isPrimOp()) {
|
||||
auto ptr = args[0]->primOp->fun.target<decltype(&prim_lessThan)>();
|
||||
if (ptr && *ptr == prim_lessThan) {
|
||||
return CompareValues(state, noPos, "while evaluating the ordering function passed to builtins.sort")(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
Value * vs[] = {a, b};
|
||||
Value vBool;
|
||||
state.callFunction(*args[0], 2, vs, vBool, noPos);
|
||||
return state.forceBool(
|
||||
vBool,
|
||||
pos,
|
||||
"while evaluating the return value of the sorting function passed to builtins.sort"
|
||||
);
|
||||
};
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_sort({
|
||||
.name = "__sort",
|
||||
.args = {"comparator", "list"},
|
||||
.doc = R"(
|
||||
Return *list* in sorted order. It repeatedly calls the function
|
||||
*comparator* with two elements. The comparator should return `true`
|
||||
if the first element is less than the second, and `false` otherwise.
|
||||
For example,
|
||||
|
||||
```nix
|
||||
builtins.sort builtins.lessThan [ 483 249 526 147 42 77 ]
|
||||
```
|
||||
|
||||
produces the list `[ 42 77 147 249 483 526 ]`.
|
||||
|
||||
This is a stable sort: it preserves the relative order of elements
|
||||
deemed equal by the comparator.
|
||||
)",
|
||||
.fun = prim_sort,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.tail
|
||||
*/
|
||||
|
||||
static void prim_tail(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.tail");
|
||||
if (args[0]->listSize() == 0) {
|
||||
state.error<EvalError>("'tail' called on an empty list").atPos(pos).debugThrow();
|
||||
}
|
||||
|
||||
state.mkList(v, args[0]->listSize() - 1);
|
||||
for (unsigned int n = 0; n < v.listSize(); ++n) {
|
||||
v.listElems()[n] = args[0]->listElems()[n + 1];
|
||||
}
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_tail({
|
||||
.name = "__tail",
|
||||
.args = {"list"},
|
||||
.doc = R"(
|
||||
Return the second to last elements of a list; abort evaluation if
|
||||
the argument isn’t a list or is an empty list.
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> This function should generally be avoided since it's inefficient:
|
||||
> unlike Haskell's `tail`, it takes O(n) time, so recursing over a
|
||||
> list by repeatedly calling `tail` takes O(n^2) time.
|
||||
)",
|
||||
.fun = prim_tail,
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue