libexpr/nix2: Add slicing operators

Change-Id: Iff104eb344992b41c03840ff330224c630e5cacf
This commit is contained in:
piegames 2024-08-26 10:59:46 +02:00
parent b2201a05c6
commit 5b1e088dfd
18 changed files with 266 additions and 4 deletions

View file

@ -1366,8 +1366,11 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
if (state.countCalls) state.attrSelects[posCurrent]++;
}
state.forceValue(*vCurrent, (posCurrent ? posCurrent : this->pos));
// Successfully made it to the end of the chain
posCurrent = posCurrent ? posCurrent : this->pos;
state.forceValue(*vCurrent, posCurrent);
v = *vCurrent;
} catch (Error & e) {
if (posCurrent) {
auto pos2r = state.positions[posCurrent];
@ -1378,10 +1381,83 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
}
throw;
}
v = *vCurrent;
}
// nix-lang v2 exclusive
void ExprSlice::eval(EvalState & state, Env & env, Value & v)
{
Value toSlice;
e->eval(state, env, toSlice);
// Can only slice into attrsets
if (toSlice.type() != nAttrs) {
state.error<TypeError>(
"expected a set but found %s: %s",
showType(toSlice),
ValuePrinter(state, toSlice, errorPrintOptions)
).addTrace(
getPos(),
HintFmt("while slicing an attribute set")
).debugThrow();
}
if (this->sliceSet) {
BindingsBuilder builder = state.buildBindings(this->slice.size());
for (auto & [name, pos] : this->slice) {
Bindings::iterator attrIt = toSlice.attrs->find(name);
// Item not found, throw missing attr error
if (attrIt == toSlice.attrs->end()) {
std::set<std::string> allAttrNames;
for (auto const & attr : *toSlice.attrs) {
allAttrNames.insert(state.symbols[attr.name]);
}
auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]);
state.error<EvalError>("attribute '%s' missing", state.symbols[name])
.addTrace(
getPos(),
HintFmt("while slicing an attribute set into a subset")
)
.atPos(pos)
.withSuggestions(suggestions)
.withFrame(env, *this)
.debugThrow();
}
builder.insert(*attrIt);
}
v.mkAttrs(builder);
} else {
state.mkList(v, this->slice.size());
for (auto [n, v2] : enumerate(v.listItems())) {
auto [name, pos] = this->slice[n];
Bindings::iterator attrIt = toSlice.attrs->find(name);
// Item not found, throw missing attr error
if (attrIt == toSlice.attrs->end()) {
std::set<std::string> allAttrNames;
for (auto const & attr : *toSlice.attrs) {
allAttrNames.insert(state.symbols[attr.name]);
}
auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]);
state.error<EvalError>("attribute '%s' missing", state.symbols[name])
.addTrace(
getPos(),
HintFmt("while slicing an attribute set into a list")
)
.atPos(pos)
.withSuggestions(suggestions)
.withFrame(env, *this)
.debugThrow();
}
const_cast<Value * &>(v2) = attrIt->value;
}
}
}
void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
{

View file

@ -82,6 +82,29 @@ void ExprSelect::show(const SymbolTable & symbols, std::ostream & str) const
}
}
void ExprSlice::show(const SymbolTable & symbols, std::ostream & str) const
{
str << "(";
e->show(symbols, str);
str << ").";
if (sliceSet) {
str << "{ ";
} else {
str << "[ ";
}
for (auto & s : slice) {
str << "(";
str << symbols[s.first];
str << ") ";
}
if (sliceSet) {
str << "}";
} else {
str << "]";
}
}
void ExprOpHasAttr::show(const SymbolTable & symbols, std::ostream & str) const
{
str << "((";
@ -375,6 +398,14 @@ void ExprSelect::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
i.expr->bindVars(es, env);
}
void ExprSlice::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
e->bindVars(es, env);
}
void ExprOpHasAttr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)

View file

@ -191,6 +191,25 @@ struct ExprSelect : Expr
COMMON_METHODS
};
// nix-lang v2 exclusive
struct ExprSlice : Expr
{
PosIdx pos;
/** The expression attributes are being sliced. e.g. `foo` in `foo.[ bar baz ]`. */
std::unique_ptr<Expr> e;
/** Symbols to extract out of `e` */
std::vector<std::pair<Symbol, PosIdx>> slice;
/** Whether to slice into a set instead of a list */
bool sliceSet = false;
ExprSlice(const PosIdx & pos, std::unique_ptr<Expr> e) : pos(pos), e(std::move(e)) {}
PosIdx getPos() const override { return pos; }
COMMON_METHODS
};
struct ExprOpHasAttr : Expr
{
std::unique_ptr<Expr> e;

View file

@ -464,9 +464,22 @@ struct _expr {
> {};
struct _select {
/* Elements of the slices */
struct slice_items : p::list<attr, seps> { };
struct head : _simple {};
struct attr : semantic, seq<attrpath> {};
struct attr_or : semantic, must<select> {};
struct list_slice : semantic, seq<
one<'.'>, seps, one<'['>, seps,
opt<slice_items, seps>,
must<one<']'>>
> {};
struct set_slice : semantic, seq<
one<'.'>, seps, one<'{'>, seps,
opt<slice_items, seps>,
must<one<'}'>>
> {};
struct as_app_or : semantic, t::kw_or {};
};
struct _app {
@ -482,8 +495,14 @@ struct _expr {
sor<
seq<
one<'.'>, seps, _select::attr,
opt<seps, t::kw_or, seps, _select::attr_or>
opt<sor<
seq<seps, _select::list_slice>,
seq<seps, _select::set_slice>,
seq<seps, t::kw_or, seps, _select::attr_or>
>>
>,
_select::list_slice,
_select::set_slice,
_select::as_app_or
>
>

View file

@ -707,6 +707,14 @@ struct SelectState : SubexprState {
PosIdx pos;
ExprSelect * e = nullptr;
// Slicing attributes
std::vector<std::pair<AttrName, PosIdx>> attrs;
template <typename T>
void pushAttr(T && attr, PosIdx pos) { attrs.emplace_back(std::forward<T>(attr), pos); }
// This attribute is necessary as part of the signature for parsing attributes,
// however it is not needed in this context here
bool isAllSymbols = true;
};
template<> struct BuildAST<grammar::v2::expr::select::head> {
@ -727,6 +735,69 @@ template<> struct BuildAST<grammar::v2::expr::select::attr_or> {
}
};
template<> struct BuildAST<grammar::v2::expr::select::list_slice> {
static void apply0(SelectState & s, State & ps) {
// e may be null if there are no selectors between head and slice
if (s.e)
s.e = &s->pushExpr<ExprSelect>(s.pos, s.pos, s->popExprOnly(), AttrPath {}, nullptr);
ExprSlice * e = &s->pushExpr<ExprSlice>(s.pos, s.pos, s->popExprOnly());
for (auto & [i, iPos] : s.attrs) {
Symbol symbol;
if (i.symbol) {
symbol = i.symbol;
} else if (auto str = dynamic_cast<ExprString *>(i.expr.get())) {
symbol = ps.symbols.create(str->s);
} else {
throw ParseError({
.msg = HintFmt("dynamic attributes not allowed in slice"),
.pos = ps.positions[iPos]
});
}
e->slice.emplace_back(std::pair(symbol, iPos));
}
}
};
template<> struct BuildAST<grammar::v2::expr::select::set_slice> {
static void apply0(SelectState & s, State & ps) {
// e may be null if there are no selectors between head and slice
if (s.e)
s.e = &s->pushExpr<ExprSelect>(s.pos, s.pos, s->popExprOnly(), AttrPath {}, nullptr);
ExprSlice * e = &s->pushExpr<ExprSlice>(s.pos, s.pos, s->popExprOnly());
e->sliceSet = true;
// To check for duplicates
auto symbols = std::map<Symbol, PosIdx>();
for (auto & [i, iPos] : s.attrs) {
Symbol symbol;
if (i.symbol) {
symbol = i.symbol;
} else if (auto str = dynamic_cast<ExprString *>(i.expr.get())) {
symbol = ps.symbols.create(str->s);
} else {
throw ParseError({
.msg = HintFmt("dynamic attributes not allowed in slice"),
.pos = ps.positions[iPos]
});
}
/* Make sure there are no duplicates */
{
auto [prevItem, success] = symbols.insert({i.symbol, iPos});
if (!success)
ps.dupAttr(i.symbol, iPos, prevItem->second);
}
e->slice.emplace_back(std::pair(symbol, iPos));
}
}
};
template<> struct BuildAST<grammar::v2::expr::select::as_app_or> {
static void apply(const auto & in, SelectState & s, State & ps) {
std::vector<std::unique_ptr<Expr>> args(1);

View file

@ -187,6 +187,8 @@ constexpr std::array<ExperimentalFeatureDetails, numXpFeatures> xpFeatureDetails
Notable changes done compared to the stable Nix language:
- Added a pipe operator `|>`. See also the `pipe-operator` experimental feature
- Added set slicing. Use `.{attrs}` to create a subset containing `attrs`,
and `.[attrs]` to only take the attribute values as list.
- Operators cannot be overridden anymore through `__sub` and friends
- URL literals are completely removed from the language. `x:x` is now a function. See also the `url-literals` deprecated feature
- Floating point literals have been cleaned up.

View file

@ -0,0 +1 @@
[ [ ] [ 1 3 { f = 5; } 6 ] [ 2 2 2 1 1 1 ] [ ] [ 5 ] { } { a = 1; b = 2; c = 3; d = 4; } { } { f = 5; } ]

View file

@ -0,0 +1 @@
--extra-experimental-features nix-lang2

View file

@ -0,0 +1,21 @@
let
set = {
a = 1;
b = 2;
c = 3;
d = 4;
e = { f = 5; };
"g h" = 6;
};
in
[
set.[ ]
set.[ a c e "g h" ]
set.[ b b b a a a ]
set.e.[]
set.e.[ f ]
set.{ }
set.{ a b c d }
set.e.{ }
set.e.{ f }
]

View file

@ -0,0 +1,6 @@
error: attribute 'a' already defined at «stdin»:2:7
at «stdin»:2:9:
1| with {};
2| set.{ a a }
| ^
3|

View file

@ -0,0 +1 @@
--extra-experimental-features nix-lang2

View file

@ -0,0 +1,2 @@
with {};
set.{ a a }

View file

@ -0,0 +1,6 @@
error: dynamic attributes not allowed in slice
at «stdin»:2:7:
1| with {};
2| set.[ ${dynamic} ]
| ^
3|

View file

@ -0,0 +1 @@
--extra-experimental-features nix-lang2

View file

@ -0,0 +1,2 @@
with {};
set.[ ${dynamic} ]

View file

@ -0,0 +1 @@
(let set = { a = 1; b = 2; c = 3; d = 4; e = { f = 5; }; "g h" = 6; }; in [ ((set).[ ]) ((set).[ (a) (c) (e) ("g h") ]) ((set).[ (b) (b) (b) (a) (a) (a) ]) ((((set).e).).[ ]) ((((set).e).).[ (f) ]) ((set).{ }) ((set).{ (a) (b) (c) (d) }) ((((set).e).).{ }) ((((set).e).).{ (f) }) ])

View file

@ -0,0 +1 @@
--extra-experimental-features nix-lang2

View file

@ -0,0 +1 @@
eval-okay-slice.nix