mirror of
https://github.com/NixOS/nix.git
synced 2024-11-21 22:32:26 +00:00
libexpr: experimental pipe operators
This commit is contained in:
parent
874c1bdbbf
commit
e086d5d899
28
doc/manual/rl-next/pipe-operators.md
Normal file
28
doc/manual/rl-next/pipe-operators.md
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
synopsis: "Add `pipe-operators` experimental feature"
|
||||
prs:
|
||||
- 11131
|
||||
---
|
||||
|
||||
This is a draft implementation of [RFC 0148](https://github.com/NixOS/rfcs/pull/148).
|
||||
|
||||
The `pipe-operators` experimental feature adds [`<|` and `|>` operators][pipe operators] to the Nix language.
|
||||
*a* `|>` *b* is equivalent to the function application *b* *a*, and
|
||||
*a* `<|` *b* is equivalent to the function application *a* *b*.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
nix-repl> 1 |> builtins.add 2 |> builtins.mul 3
|
||||
9
|
||||
|
||||
nix-repl> builtins.add 1 <| builtins.mul 2 <| 3
|
||||
7
|
||||
```
|
||||
|
||||
`<|` and `|>` are right and left associative, respectively, and have lower precedence than any other operator.
|
||||
These properties may change in future releases.
|
||||
|
||||
See [the RFC](https://github.com/NixOS/rfcs/pull/148) for more examples and rationale.
|
||||
|
||||
[pipe operators]: @docroot@/language/operators.md#pipe-operators
|
@ -26,13 +26,17 @@
|
||||
| Logical conjunction (`AND`) | *bool* `&&` *bool* | left | 12 |
|
||||
| Logical disjunction (`OR`) | *bool* <code>\|\|</code> *bool* | left | 13 |
|
||||
| [Logical implication] | *bool* `->` *bool* | right | 14 |
|
||||
| [Pipe operator] (experimental) | *expr* `\|>` *func* | left | 15 |
|
||||
| [Pipe operator] (experimental) | *func* `<\|` *expr* | right | 15 |
|
||||
|
||||
[string]: ./types.md#type-string
|
||||
[path]: ./types.md#type-path
|
||||
[number]: ./types.md#type-float <!-- TODO(@rhendric, #10970): rationalize this -->
|
||||
[number]: ./types.md#type-float
|
||||
[list]: ./types.md#list
|
||||
[attribute set]: ./types.md#attribute-set
|
||||
|
||||
<!-- TODO(@rhendric, #10970): ^ rationalize number -> int/float -->
|
||||
|
||||
## Attribute selection
|
||||
|
||||
> **Syntax**
|
||||
@ -182,3 +186,34 @@ Equivalent to `!`*b1* `||` *b2*.
|
||||
|
||||
[Logical implication]: #logical-implication
|
||||
|
||||
## Pipe operators
|
||||
|
||||
- *a* `|>` *b* is equivalent to *b* *a*
|
||||
- *a* `<|` *b* is equivalent to *a* *b*
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> ```
|
||||
> nix-repl> 1 |> builtins.add 2 |> builtins.mul 3
|
||||
> 9
|
||||
>
|
||||
> nix-repl> builtins.add 1 <| builtins.mul 2 <| 3
|
||||
> 7
|
||||
> ```
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> This syntax is part of an
|
||||
> [experimental feature](@docroot@/contributing/experimental-features.md)
|
||||
> and may change in future releases.
|
||||
>
|
||||
> To use this syntax, make sure the
|
||||
> [`pipe-operators` experimental feature](@docroot@/contributing/experimental-features.md#xp-feature-pipe-operators)
|
||||
> is enabled.
|
||||
> For example, include the following in [`nix.conf`](@docroot@/command-ref/conf-file.md):
|
||||
>
|
||||
> ```
|
||||
> extra-experimental-features = pipe-operators
|
||||
> ```
|
||||
|
||||
[Pipe operator]: #pipe-operators
|
||||
|
@ -67,6 +67,14 @@ static StringToken unescapeStr(SymbolTable & symbols, char * s, size_t length)
|
||||
return {result, size_t(t - result)};
|
||||
}
|
||||
|
||||
static void requireExperimentalFeature(const ExperimentalFeature & feature, const Pos & pos)
|
||||
{
|
||||
if (!experimentalFeatureSettings.isEnabled(feature))
|
||||
throw ParseError(ErrorInfo{
|
||||
.msg = HintFmt("experimental Nix feature '%1%' is disabled; add '--extra-experimental-features %1%' to enable it", showExperimentalFeature(feature)),
|
||||
.pos = pos,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -119,6 +127,12 @@ or { return OR_KW; }
|
||||
\-\> { return IMPL; }
|
||||
\/\/ { return UPDATE; }
|
||||
\+\+ { return CONCAT; }
|
||||
\<\| { requireExperimentalFeature(Xp::PipeOperators, state->positions[CUR_POS]);
|
||||
return PIPE_FROM;
|
||||
}
|
||||
\|\> { requireExperimentalFeature(Xp::PipeOperators, state->positions[CUR_POS]);
|
||||
return PIPE_INTO;
|
||||
}
|
||||
|
||||
{ID} { yylval->id = {yytext, (size_t) yyleng}; return ID; }
|
||||
{INT} { errno = 0;
|
||||
|
@ -99,6 +99,14 @@ static void setDocPosition(const LexerState & lexerState, ExprLambda * lambda, P
|
||||
}
|
||||
}
|
||||
|
||||
static Expr * makeCall(PosIdx pos, Expr * fn, Expr * arg) {
|
||||
if (auto e2 = dynamic_cast<ExprCall *>(fn)) {
|
||||
e2->args.push_back(arg);
|
||||
return fn;
|
||||
}
|
||||
return new ExprCall(pos, fn, {arg});
|
||||
}
|
||||
|
||||
|
||||
%}
|
||||
|
||||
@ -123,6 +131,7 @@ static void setDocPosition(const LexerState & lexerState, ExprLambda * lambda, P
|
||||
|
||||
%type <e> start expr expr_function expr_if expr_op
|
||||
%type <e> expr_select expr_simple expr_app
|
||||
%type <e> expr_pipe_from expr_pipe_into
|
||||
%type <list> expr_list
|
||||
%type <attrs> binds
|
||||
%type <formals> formals
|
||||
@ -140,6 +149,7 @@ static void setDocPosition(const LexerState & lexerState, ExprLambda * lambda, P
|
||||
%token <path> PATH HPATH SPATH PATH_END
|
||||
%token <uri> URI
|
||||
%token IF THEN ELSE ASSERT WITH LET IN_KW REC INHERIT EQ NEQ AND OR IMPL OR_KW
|
||||
%token PIPE_FROM PIPE_INTO /* <| and |> */
|
||||
%token DOLLAR_CURLY /* == ${ */
|
||||
%token IND_STRING_OPEN IND_STRING_CLOSE
|
||||
%token ELLIPSIS
|
||||
@ -206,9 +216,21 @@ expr_function
|
||||
|
||||
expr_if
|
||||
: IF expr THEN expr ELSE expr { $$ = new ExprIf(CUR_POS, $2, $4, $6); }
|
||||
| expr_pipe_from
|
||||
| expr_pipe_into
|
||||
| expr_op
|
||||
;
|
||||
|
||||
expr_pipe_from
|
||||
: expr_op PIPE_FROM expr_pipe_from { $$ = makeCall(state->at(@2), $1, $3); }
|
||||
| expr_op PIPE_FROM expr_op { $$ = makeCall(state->at(@2), $1, $3); }
|
||||
;
|
||||
|
||||
expr_pipe_into
|
||||
: expr_pipe_into PIPE_INTO expr_op { $$ = makeCall(state->at(@2), $3, $1); }
|
||||
| expr_op PIPE_INTO expr_op { $$ = makeCall(state->at(@2), $3, $1); }
|
||||
;
|
||||
|
||||
expr_op
|
||||
: '!' expr_op %prec NOT { $$ = new ExprOpNot($2); }
|
||||
| '-' expr_op %prec NEGATE { $$ = new ExprCall(CUR_POS, new ExprVar(state->s.sub), {new ExprInt(0), $2}); }
|
||||
@ -233,13 +255,7 @@ expr_op
|
||||
;
|
||||
|
||||
expr_app
|
||||
: expr_app expr_select {
|
||||
if (auto e2 = dynamic_cast<ExprCall *>($1)) {
|
||||
e2->args.push_back($2);
|
||||
$$ = $1;
|
||||
} else
|
||||
$$ = new ExprCall(CUR_POS, $1, {$2});
|
||||
}
|
||||
: expr_app expr_select { $$ = makeCall(CUR_POS, $1, $2); }
|
||||
| expr_select
|
||||
;
|
||||
|
||||
|
@ -24,7 +24,7 @@ struct ExperimentalFeatureDetails
|
||||
* feature, we either have no issue at all if few features are not added
|
||||
* at the end of the list, or a proper merge conflict if they are.
|
||||
*/
|
||||
constexpr size_t numXpFeatures = 1 + static_cast<size_t>(Xp::VerifiedFetches);
|
||||
constexpr size_t numXpFeatures = 1 + static_cast<size_t>(Xp::PipeOperators);
|
||||
|
||||
constexpr std::array<ExperimentalFeatureDetails, numXpFeatures> xpFeatureDetails = {{
|
||||
{
|
||||
@ -294,6 +294,14 @@ constexpr std::array<ExperimentalFeatureDetails, numXpFeatures> xpFeatureDetails
|
||||
)",
|
||||
.trackingUrl = "https://github.com/NixOS/nix/milestone/48",
|
||||
},
|
||||
{
|
||||
.tag = Xp::PipeOperators,
|
||||
.name = "pipe-operators",
|
||||
.description = R"(
|
||||
Add `|>` and `<|` operators to the Nix language.
|
||||
)",
|
||||
.trackingUrl = "https://github.com/NixOS/nix/milestone/55",
|
||||
},
|
||||
}};
|
||||
|
||||
static_assert(
|
||||
|
@ -35,6 +35,7 @@ enum struct ExperimentalFeature
|
||||
ConfigurableImpureEnv,
|
||||
MountedSSHStore,
|
||||
VerifiedFetches,
|
||||
PipeOperators,
|
||||
};
|
||||
|
||||
/**
|
||||
|
5
tests/functional/lang/eval-fail-pipe-operators.err.exp
Normal file
5
tests/functional/lang/eval-fail-pipe-operators.err.exp
Normal file
@ -0,0 +1,5 @@
|
||||
error: experimental Nix feature 'pipe-operators' is disabled; add '--extra-experimental-features pipe-operators' to enable it
|
||||
at /pwd/lang/eval-fail-pipe-operators.nix:1:3:
|
||||
1| 1 |> 2
|
||||
| ^
|
||||
2|
|
1
tests/functional/lang/eval-fail-pipe-operators.nix
Normal file
1
tests/functional/lang/eval-fail-pipe-operators.nix
Normal file
@ -0,0 +1 @@
|
||||
1 |> 2
|
@ -34,6 +34,9 @@ int main (int argc, char **argv) {
|
||||
setEnv("_NIX_TEST_NO_SANDBOX", "1");
|
||||
#endif
|
||||
|
||||
// For pipe operator tests in trivial.cc
|
||||
experimentalFeatureSettings.set("experimental-features", "pipe-operators");
|
||||
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
@ -182,6 +182,60 @@ namespace nix {
|
||||
ASSERT_THAT(v, IsIntEq(15));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, forwardPipe) {
|
||||
auto v = eval("1 |> builtins.add 2 |> builtins.mul 3");
|
||||
ASSERT_THAT(v, IsIntEq(9));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, backwardPipe) {
|
||||
auto v = eval("builtins.add 1 <| builtins.mul 2 <| 3");
|
||||
ASSERT_THAT(v, IsIntEq(7));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, forwardPipeEvaluationOrder) {
|
||||
auto v = eval("1 |> null |> (x: 2)");
|
||||
ASSERT_THAT(v, IsIntEq(2));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, backwardPipeEvaluationOrder) {
|
||||
auto v = eval("(x: 1) <| null <| 2");
|
||||
ASSERT_THAT(v, IsIntEq(1));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, differentPipeOperatorsDoNotAssociate) {
|
||||
ASSERT_THROW(eval("(x: 1) <| 2 |> (x: 3)"), ParseError);
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, differentPipeOperatorsParensLeft) {
|
||||
auto v = eval("((x: 1) <| 2) |> (x: 3)");
|
||||
ASSERT_THAT(v, IsIntEq(3));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, differentPipeOperatorsParensRight) {
|
||||
auto v = eval("(x: 1) <| (2 |> (x: 3))");
|
||||
ASSERT_THAT(v, IsIntEq(1));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, forwardPipeLowestPrecedence) {
|
||||
auto v = eval("false -> true |> (x: !x)");
|
||||
ASSERT_THAT(v, IsFalse());
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, backwardPipeLowestPrecedence) {
|
||||
auto v = eval("(x: !x) <| false -> true");
|
||||
ASSERT_THAT(v, IsFalse());
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, forwardPipeStrongerThanElse) {
|
||||
auto v = eval("if true then 1 else 2 |> 3");
|
||||
ASSERT_THAT(v, IsIntEq(1));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, backwardPipeStrongerThanElse) {
|
||||
auto v = eval("if true then 1 else 2 <| 3");
|
||||
ASSERT_THAT(v, IsIntEq(1));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, bindOr) {
|
||||
auto v = eval("{ or = 1; }");
|
||||
ASSERT_THAT(v, IsAttrsOfSize(1));
|
||||
|
Loading…
Reference in New Issue
Block a user