forked from lix-project/lix
libexpr/nix2: Add slicing operators
Change-Id: Iff104eb344992b41c03840ff330224c630e5cacf
This commit is contained in:
parent
b2201a05c6
commit
5b1e088dfd
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
>
|
||||
>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
1
tests/functional/lang/eval-okay-slice.exp
Normal file
1
tests/functional/lang/eval-okay-slice.exp
Normal 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; } ]
|
1
tests/functional/lang/eval-okay-slice.flags
Normal file
1
tests/functional/lang/eval-okay-slice.flags
Normal file
|
@ -0,0 +1 @@
|
|||
--extra-experimental-features nix-lang2
|
21
tests/functional/lang/eval-okay-slice.nix
Normal file
21
tests/functional/lang/eval-okay-slice.nix
Normal 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 }
|
||||
]
|
6
tests/functional/lang/parse-fail-slice-duplicate.err.exp
Normal file
6
tests/functional/lang/parse-fail-slice-duplicate.err.exp
Normal 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|
|
1
tests/functional/lang/parse-fail-slice-duplicate.flags
Normal file
1
tests/functional/lang/parse-fail-slice-duplicate.flags
Normal file
|
@ -0,0 +1 @@
|
|||
--extra-experimental-features nix-lang2
|
2
tests/functional/lang/parse-fail-slice-duplicate.nix
Normal file
2
tests/functional/lang/parse-fail-slice-duplicate.nix
Normal file
|
@ -0,0 +1,2 @@
|
|||
with {};
|
||||
set.{ a a }
|
6
tests/functional/lang/parse-fail-slice-dynamic.err.exp
Normal file
6
tests/functional/lang/parse-fail-slice-dynamic.err.exp
Normal file
|
@ -0,0 +1,6 @@
|
|||
error: dynamic attributes not allowed in slice
|
||||
at «stdin»:2:7:
|
||||
1| with {};
|
||||
2| set.[ ${dynamic} ]
|
||||
| ^
|
||||
3|
|
1
tests/functional/lang/parse-fail-slice-dynamic.flags
Normal file
1
tests/functional/lang/parse-fail-slice-dynamic.flags
Normal file
|
@ -0,0 +1 @@
|
|||
--extra-experimental-features nix-lang2
|
2
tests/functional/lang/parse-fail-slice-dynamic.nix
Normal file
2
tests/functional/lang/parse-fail-slice-dynamic.nix
Normal file
|
@ -0,0 +1,2 @@
|
|||
with {};
|
||||
set.[ ${dynamic} ]
|
1
tests/functional/lang/parse-okay-slice.exp
Normal file
1
tests/functional/lang/parse-okay-slice.exp
Normal 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) }) ])
|
1
tests/functional/lang/parse-okay-slice.flags
Normal file
1
tests/functional/lang/parse-okay-slice.flags
Normal file
|
@ -0,0 +1 @@
|
|||
--extra-experimental-features nix-lang2
|
1
tests/functional/lang/parse-okay-slice.nix
Symbolic link
1
tests/functional/lang/parse-okay-slice.nix
Symbolic link
|
@ -0,0 +1 @@
|
|||
eval-okay-slice.nix
|
Loading…
Reference in a new issue