mirror of
https://github.com/NixOS/nix.git
synced 2024-11-25 08:12:29 +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 conjunction (`AND`) | *bool* `&&` *bool* | left | 12 |
|
||||||
| Logical disjunction (`OR`) | *bool* <code>\|\|</code> *bool* | left | 13 |
|
| Logical disjunction (`OR`) | *bool* <code>\|\|</code> *bool* | left | 13 |
|
||||||
| [Logical implication] | *bool* `->` *bool* | right | 14 |
|
| [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
|
[string]: ./types.md#type-string
|
||||||
[path]: ./types.md#type-path
|
[path]: ./types.md#type-path
|
||||||
[number]: ./types.md#type-float <!-- TODO(@rhendric, #10970): rationalize this -->
|
[number]: ./types.md#type-float
|
||||||
[list]: ./types.md#list
|
[list]: ./types.md#list
|
||||||
[attribute set]: ./types.md#attribute-set
|
[attribute set]: ./types.md#attribute-set
|
||||||
|
|
||||||
|
<!-- TODO(@rhendric, #10970): ^ rationalize number -> int/float -->
|
||||||
|
|
||||||
## Attribute selection
|
## Attribute selection
|
||||||
|
|
||||||
> **Syntax**
|
> **Syntax**
|
||||||
@ -182,3 +186,34 @@ Equivalent to `!`*b1* `||` *b2*.
|
|||||||
|
|
||||||
[Logical implication]: #logical-implication
|
[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)};
|
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 IMPL; }
|
||||||
\/\/ { return UPDATE; }
|
\/\/ { return UPDATE; }
|
||||||
\+\+ { return CONCAT; }
|
\+\+ { 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; }
|
{ID} { yylval->id = {yytext, (size_t) yyleng}; return ID; }
|
||||||
{INT} { errno = 0;
|
{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> start expr expr_function expr_if expr_op
|
||||||
%type <e> expr_select expr_simple expr_app
|
%type <e> expr_select expr_simple expr_app
|
||||||
|
%type <e> expr_pipe_from expr_pipe_into
|
||||||
%type <list> expr_list
|
%type <list> expr_list
|
||||||
%type <attrs> binds
|
%type <attrs> binds
|
||||||
%type <formals> formals
|
%type <formals> formals
|
||||||
@ -140,6 +149,7 @@ static void setDocPosition(const LexerState & lexerState, ExprLambda * lambda, P
|
|||||||
%token <path> PATH HPATH SPATH PATH_END
|
%token <path> PATH HPATH SPATH PATH_END
|
||||||
%token <uri> URI
|
%token <uri> URI
|
||||||
%token IF THEN ELSE ASSERT WITH LET IN_KW REC INHERIT EQ NEQ AND OR IMPL OR_KW
|
%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 DOLLAR_CURLY /* == ${ */
|
||||||
%token IND_STRING_OPEN IND_STRING_CLOSE
|
%token IND_STRING_OPEN IND_STRING_CLOSE
|
||||||
%token ELLIPSIS
|
%token ELLIPSIS
|
||||||
@ -206,9 +216,21 @@ expr_function
|
|||||||
|
|
||||||
expr_if
|
expr_if
|
||||||
: IF expr THEN expr ELSE expr { $$ = new ExprIf(CUR_POS, $2, $4, $6); }
|
: IF expr THEN expr ELSE expr { $$ = new ExprIf(CUR_POS, $2, $4, $6); }
|
||||||
|
| expr_pipe_from
|
||||||
|
| expr_pipe_into
|
||||||
| expr_op
|
| 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
|
||||||
: '!' expr_op %prec NOT { $$ = new ExprOpNot($2); }
|
: '!' expr_op %prec NOT { $$ = new ExprOpNot($2); }
|
||||||
| '-' expr_op %prec NEGATE { $$ = new ExprCall(CUR_POS, new ExprVar(state->s.sub), {new ExprInt(0), $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_app expr_select {
|
: expr_app expr_select { $$ = makeCall(CUR_POS, $1, $2); }
|
||||||
if (auto e2 = dynamic_cast<ExprCall *>($1)) {
|
|
||||||
e2->args.push_back($2);
|
|
||||||
$$ = $1;
|
|
||||||
} else
|
|
||||||
$$ = new ExprCall(CUR_POS, $1, {$2});
|
|
||||||
}
|
|
||||||
| expr_select
|
| expr_select
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ struct ExperimentalFeatureDetails
|
|||||||
* feature, we either have no issue at all if few features are not added
|
* 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.
|
* 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 = {{
|
constexpr std::array<ExperimentalFeatureDetails, numXpFeatures> xpFeatureDetails = {{
|
||||||
{
|
{
|
||||||
@ -294,6 +294,14 @@ constexpr std::array<ExperimentalFeatureDetails, numXpFeatures> xpFeatureDetails
|
|||||||
)",
|
)",
|
||||||
.trackingUrl = "https://github.com/NixOS/nix/milestone/48",
|
.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(
|
static_assert(
|
||||||
|
@ -35,6 +35,7 @@ enum struct ExperimentalFeature
|
|||||||
ConfigurableImpureEnv,
|
ConfigurableImpureEnv,
|
||||||
MountedSSHStore,
|
MountedSSHStore,
|
||||||
VerifiedFetches,
|
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");
|
setEnv("_NIX_TEST_NO_SANDBOX", "1");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// For pipe operator tests in trivial.cc
|
||||||
|
experimentalFeatureSettings.set("experimental-features", "pipe-operators");
|
||||||
|
|
||||||
::testing::InitGoogleTest(&argc, argv);
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
return RUN_ALL_TESTS();
|
return RUN_ALL_TESTS();
|
||||||
}
|
}
|
||||||
|
@ -182,6 +182,60 @@ namespace nix {
|
|||||||
ASSERT_THAT(v, IsIntEq(15));
|
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) {
|
TEST_F(TrivialExpressionTest, bindOr) {
|
||||||
auto v = eval("{ or = 1; }");
|
auto v = eval("{ or = 1; }");
|
||||||
ASSERT_THAT(v, IsAttrsOfSize(1));
|
ASSERT_THAT(v, IsAttrsOfSize(1));
|
||||||
|
Loading…
Reference in New Issue
Block a user