From f77e7052d23b4b0a3f2f2c0cdb5d2d1b3d9925d6 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 12 Jun 2019 13:33:38 -0700 Subject: [PATCH 01/15] Pipeline chain operators --- 1-Draft/RFC0000-Chain-Operators.md | 493 +++++++++++++++++++++++++++++ 1 file changed, 493 insertions(+) create mode 100644 1-Draft/RFC0000-Chain-Operators.md diff --git a/1-Draft/RFC0000-Chain-Operators.md b/1-Draft/RFC0000-Chain-Operators.md new file mode 100644 index 00000000..bd08cd92 --- /dev/null +++ b/1-Draft/RFC0000-Chain-Operators.md @@ -0,0 +1,493 @@ +--- +RFC: +Author: Robert Holt +Status: Draft +Area: Pipeline Chain Operators +Comments Due: 2019-07-12 0000 +Plan to implement: Yes +--- + +# Pipeline Chain Operators + +POSIX shells have what may be referred to as *AND-OR lists*, +what the [Open Group's Shell Command Language specification](https://pubs.opengroup.org/onlinepubs/007904875/utilities/xcu_chap02.html#tag_02_09_03) +describes as: + +> a sequence of one or more pipelines separated by the operators `&&` and `||` + +Semantically, these operators look at the exit code of the left-hand side pipeline +and conditionally execute the right-hand side pipeline. +`&&` executes the right-hand side if the left-hand side "succeeded", +and `||` executes the right-hand side if the left-hand side did not "succeed". + +Some examples in bash: + +```bash +echo 'Success' && echo 'Second success' # Executes both left-hand and right-hand command +echo 'Success' || echo 'Second success' # Executes only the left-hand command +false && echo 'Second success' # Executes only the left-hand command (false always "fails") +false || echo 'Second success' # Executes both left-hand and right-hand commands +false && echo foo || echo bar # Writes only "bar" to stdout (operators are left-associative) +true || echo foo && echo bar # Writes only "bar" to stdout +``` + +Also from the Shell Command Language specification: + +> A ';' or <newline> terminator shall cause the preceding AND-OR list to be executed sequentially; +> an '&' shall cause asynchronous execution of the preceding AND-OR list. + +Meaning that the entire AND-OR list is backgrounded: + +```bash +true && echo 'Success' & # The list `true && echo 'Success'` is executed in the background +``` + +This RFC proposes adding AND-OR lists to PowerShell, using the same `&&` and `||`. +Because "AND-OR list" is neither intuitive nor common as a terminology, +instead the term **pipeline chains** is proposed. + +## Motivation + +> As a PowerShell user, I can chain commands with `&&` and `||` +> so that I have an ergonomic syntax to conditionally invoke side-effectful commands. + +## User Experience + +Example of user experience with example code/script. +Include example of input and output. + +```powershell +Get-Example +``` + +```output +Hello World +``` + +## Specification + +### Grammar + +Pipeline chains will be implemented using the already reserved `&&` and `||` operators. +These operators will have the following grammar: + +```none +statement: + | ... # Other statements + | variable_expression "=" statement # Assignment + | pipeline_chain ["&"] # Pipeline chains + +pipeline_chain: + | pipeline + | pipeline_chain "&&" [newlines] pipeline + | pipeline_chain "||" [newlines] pipeline +``` + +#### Optional pipeline chaining + +A pipeline chain is a chain of one or more pipelines +and takes the place of a pipeline in the current PowerShell syntax. + +In the degenerate case, a pipeline chain of a single pipeline +will work exactly as pipelines currently do in PowerShell, +so there is no breakage to the existing grammar. + +#### Line continuation + +After a pipeline chain operator, +any newlines will be skipped in anticipation of the following pipeline. + +For example, the following would be a single pipeline chain: + +```powershell +cmd1 && + cmd2 || + cmd3 +``` + +If the end of file is reached after a pipeline chain operator, +incomplete input will be reported so that integrating tools +will know to keep prompting for input. + +#### Left-associativity + +Like in POSIX shells, pipeline chain operators will be left associative, +meaning chain operators will group from left to right. + +For example, given the following pipeline chain: + +```powershell +cmd1 && cmd2 || cmd3 +``` + +This will be grouped as: + +```none +[[cmd1 && cmd2] || cmd3] +``` + +As a syntax tree, this would look like: + +```none + "||" + / \ + "&&" "cmd3" + / \ + "cmd1" "cmd2" +``` + +With the syntax tree deepening on the left as more operators are chained. + +Semantically, this means that `cmd1 && cmd2` would be evaluated first, +and its result used to govern the evaluation of `|| cmd3`. + +#### Higher precedence than `&` and `;` + +Pipeline chain operators will have higher precedence than +pipeline background operators (`&`) +or statement separators (`;`). + +For example: + +```none +cmd1 && cmd2 & +``` + +Will bind as: + +```none +[[cmd1 && cmd2] &] +``` + +Having the syntax tree: + +```none + + / \ + "&&" "&" + / \ + "cmd1" "cmd2" +``` + +The consequence of this will be that an entire pipeline chain +can be sent to a background job for evaluation, +rather than individual pipelines within it. + +### Semantics + +#### Pipeline "success" + +The `&&` and `||` operators proceed based on the "success" +of the previous pipeline. + +The marker of command success proposed is the `$?` automatic variable. +This is proposed because: + +- It builds off an existing PowerShell concept +- It applies to both native commands and cmdlets/functions +- Diverging from this would create inconsistency +- Changes to the behaviour of `$?` would be reasonably expected to change here + +That is, there are the following direct equivalents: + +```none +cmd1 && cmd2 + + | + v + +$(cmd1; if ($?) { cmd2 }) +``` + +```none +cmd1 || cmd2 + + | + v + +$(cmd1; if (-not $?) { cmd2 }) +``` + +```none +cmd1 && cmd2 || cmd3 + + | + v + +$(cmd1; if ($?) { cmd2 }; if (-not $?) { cmd3 }) +# Note that cmd1 failing runs cmd3 +``` + +Also see the [alternate proposals section](#alternate-proposals-and-considerations). + +#### Pipeline output + +The output of a pipeline chain is the concatenation of all its pipelines, +in the sequence of their output: + +```powershell +$x = 'a','b','c' | Write-Output && Write-Output 'd' +$x # 'a','b','c','d' +``` + +```powershell +$x = 'a','b','c' | Write-Output && Write-Error 'Bad' || Write-Output 'd' +# Writes the error record 'Bad' +$x # 'a','b','c','d' +``` + +Commands that fail will have any pipeline output +emitted before evaluation of the chain operator. + +For example: + +```powershell +$x = cmd1 && cmd2 +``` + +If `cmd1` fails but emits output, `$x` will hold that value. + +#### Error semantics + +Errors will have the same semantics as the equivalent +`cmd1; if ($?) { cmd2 }` syntax. + +Terminating errors will terminate the entire pipeline chain. + +Non-terminating errors will cause the immediate pipeline to continue, +and the pipeline chain will evaluate as normal based on the value of `$?`. + +### Other notes + +#### New Abstract Syntax Tree (AST) types + +The new AST type, `PipelineChainAst`, +will be created to represent a pipeline chain. +This will inherit from `PipelineBaseAst` +and may occur anywhere a `PipelineBaseAst` does in a PowerShell AST. + +`ICustomAstVisitor2` and `AstVisitor2` would be extended to deal with this AST, +and .NET Core 3's new default interface implementation feature would be +leveraged to ensure this does not break things +as previous syntactic introductions have been forced to. + +#### Statements may not be chained + +PowerShell has a notable divergence from POSIX shells: + +- In POSIX shells, a pipeline is composed of statements +- In PowerShell, a pipeline is a kind of statement + +This means that bash allows the following: + +```bash +if [ -n $VAR ]; then echo 'IF'; fi || echo 'AFTER-IF' +``` + +In fact, this can be evaluated in the background: + +```bash +if [ -n $VAR ]; then echo 'IF'; fi || echo 'AFTER-IF' & +``` + +However, in PowerShell, pipelines are subordinate to statements. +The proposed implementation of chain operators precludes use of +`&&` and `||` between statements like `if` or `while`, +or constructions like `$x = cmd1 && $y = cmd2 && $x + $y`. + +Also see the [alternate proposals section](#alternate-proposals-and-considerations). + +## Related Material + +- Original issue: [PowerShell/PowerShell #3241](https://github.com/PowerShell/PowerShell/issues/3241). + +- Work-in-progress implementation: [PowerShell/PowerShell #9849](https://github.com/PowerShell/PowerShell/pull/9849). + +- [Current handling of `&&` and `||` in the parser](https://github.com/PowerShell/PowerShell/blob/af1de9e88d28014438ff3414e82298e5b14f6e81/src/System.Management.Automation/engine/parser/Parser.cs#L5846-L5860). + +## Alternate Proposals and Considerations + +### `&&` and `||` as a statement separator + +To be more bash-like, an alternative implementation might treat +`&&` and `||` with the same precedence as `;`, as a way to separate statements. + +This would allow assignment within chains, like: + +```powershell +$x = cmd1 && $y = cmd2 && $x + $y +``` + +Other possibilities would include: + +```powershell +if ($condition) { cmd1 } && while ($anotherCondition) { cmd2 } +``` + +```powershell +foreach ($v in 1..100) +{ + cmd1 $v && break +} +``` + +#### Background operator changes + +In bash, background operators are lower precedence than chain separators +(`&` has the same precedence as `;`). +In PowerShell, background operators are higher precedence. + +Making chain operators apply to any statement would lead to the question +of the precedence of the background operator. + +The example: + +```powershell +cmd1 && cmd2 & +``` + +Could either be `[[cmd1 && cmd2] &]` or `[cmd1 && [cmd2 &]]`. + +Having chain operators apply to statements would mean the second case +is the correct one, unless changes are made to the background operator precedence (which would also allow `if ($condition) { cmd1 } &)` for example). + +The use of this is possibly less than the use of being able to background +an entire pipeline chain; `cmd1 & && cmd2` would be equivalent to +`cmd1 &; cmd2` since a backgrounded pipeline +will never fail in the invoking context. + +#### Reasons against + +- Pipelines have an established concept of "success" compared to statements +- Background operators become less useful with respect to chains unless their + syntax is changed in a significant way + +### Allowing control flow statements at the end of chains + +A compromise to the above is to only allow "control flow statements" +at the end of chains. + +For example: + +```powershell +cmd1 || throw "cmd1 failed" +``` + +```powershell +function Invoke-Command +{ + cmd1 && return + + cmd2 +} +``` + +```powershell +foreach ($v in 1..100) +{ + cmd1 $v && break +} +``` + +These would only be allowed *at the end* of chains because: + +- Control flow makes any invocation after the statement unreachable +- `return` and `throw` allow pipelines as subordinate expressions, + meaning it would be grammatically impossible to use those statements mid-chain. + See below for more details. + +#### Reasons against + +- `throw` and `return` currently allow a subordinate pipeline: + + ```powershell + throw "Bad" + ``` + + ```powershell + throw 1,2,3 | Write-Output + ``` + + ```powershell + throw "Useless" & # Throws a background job... + ``` + + If we keep the principle that anywhere a pipeline is allowed, + a pipeline chain is now allowed, then constructions like this are possible: + + ```powershell + cmd1 || throw "Bad" && cmd3 + ``` + + However the grouping is this: + + ```none + [cmd1 || throw ["Bad" && cmd3]] + ``` + + This could lead to confusing semantic corner cases + +- The compromise nature of this approach means the PowerShell Language + becomes more complicated and arguably less consistent, + both conceptually and in terms of maintenance. + +### Different evaluations of command "success" + +The current proposal is to simply use `$?` to determine chain continuation. + +Alternatives include: + +- `$LASTEXITCODE` +- Whether errors have been written +- A new custom semantics + +A problem is that exit code semantics assume +that a non-zero exit code means command failure. +POSIX shells also make this assumption, +but the convention may not be as widespread on Windows platforms. + +#### Reasons against + +- `$LASTEXITCODE` is specific to native commands + and use with cmdlets and functions may lead to unexpected results. + For example: + + ```powershell + cmd_that_fails + Write-Output "SUCCESS" && Write-Output "ALSO SUCCESS" + ``` + + Will never write `"ALSO SUCCESS"` if `$LASTEXITCODE` is used. + +- Commands can fail without writing an error + and some utilities will write to standard error without having failed + (`time` being a good example on *nix). + +- Having chain operators use their own failure semantics + would create more conceptual complexity + and be inconsistent with the established `$?` concept. + +- Allowing configurability of the success determination would + likely only make sense with a per-command configuration. + This would already be achieved simply with the proposed `$?` + semantics using a wrapper function. + +### Terminology + +We should have a unified terminology +to describe these operators in PowerShell +for use with: + +- The experimental feature name +- The about_ topic +- Web searchability + +This RFC proposes **Pipeline chain operators**. +Other possibilities are: + +- AND-OR lists +- Command chain operators +- Command control operators +- Bash control operators +- Control operators +- Short circuit operators \ No newline at end of file From d2af15665329d71f05b364f719b4c65fa4e16cf9 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 12 Jun 2019 13:46:47 -0700 Subject: [PATCH 02/15] Add UX --- 1-Draft/RFC0000-Chain-Operators.md | 74 ++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/1-Draft/RFC0000-Chain-Operators.md b/1-Draft/RFC0000-Chain-Operators.md index bd08cd92..6c93e427 100644 --- a/1-Draft/RFC0000-Chain-Operators.md +++ b/1-Draft/RFC0000-Chain-Operators.md @@ -53,17 +53,83 @@ instead the term **pipeline chains** is proposed. ## User Experience -Example of user experience with example code/script. -Include example of input and output. +```powershell +Write-Output "Hello" && Write-Output "Hello again" +``` + +```output +Hello +Hello again +``` + +```powershell +Write-Output "Hello" || Write-Output "Hello again" +``` + +```output +Hello +``` + +```powershell +Write-Error "Bad" && Write-Output "Hello again" +``` + +```output +Write-Error "Bad" : Bad ++ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException ++ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException +``` + +```powershell +Write-Error "Bad" || Write-Output "Hello again" +``` + +```output +Write-Error "Bad" : Bad ++ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException ++ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException + +Hello again +``` + +Using `echo` as a convenient example of a successful command +and `false` as an example of an unsuccesful command. + +```powershell +echo "Hello" && Write-Output "Hi" +``` + +```output +Hello +Hi +``` + +```powershell +false && Write-Output "Reached" +``` + +```output +``` ```powershell -Get-Example +false || Write-Output "Reached" ``` ```output -Hello World +Reached ``` +```powershell +false || Write-Output "Command failed" && Write-Output "Backup" +``` + +```output +Command failed +Backup +``` + +Also see: [intended test cases for implementation](https://github.com/PowerShell/PowerShell/blob/f8b899e42c86957a1b58273be0029f40a67bf1b6/test/powershell/Language/Operators/BashControlOperator.Tests.ps1). + ## Specification ### Grammar From ef375bc26f3b73846336dfd597fc1b12b79a4bf9 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 12 Jun 2019 13:49:16 -0700 Subject: [PATCH 03/15] *nix --- 1-Draft/RFC0000-Chain-Operators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/1-Draft/RFC0000-Chain-Operators.md b/1-Draft/RFC0000-Chain-Operators.md index 6c93e427..4e6a29cd 100644 --- a/1-Draft/RFC0000-Chain-Operators.md +++ b/1-Draft/RFC0000-Chain-Operators.md @@ -527,7 +527,7 @@ but the convention may not be as widespread on Windows platforms. - Commands can fail without writing an error and some utilities will write to standard error without having failed - (`time` being a good example on *nix). + (`time` being a good example on \*nix). - Having chain operators use their own failure semantics would create more conceptual complexity From bebefa80699c70a0ad42dd1fb96590de9a6b5eb5 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 12 Jun 2019 13:58:36 -0700 Subject: [PATCH 04/15] Explain ambiguity --- 1-Draft/RFC0000-Chain-Operators.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/1-Draft/RFC0000-Chain-Operators.md b/1-Draft/RFC0000-Chain-Operators.md index 4e6a29cd..219ab3c6 100644 --- a/1-Draft/RFC0000-Chain-Operators.md +++ b/1-Draft/RFC0000-Chain-Operators.md @@ -422,6 +422,13 @@ an entire pipeline chain; `cmd1 & && cmd2` would be equivalent to `cmd1 &; cmd2` since a backgrounded pipeline will never fail in the invoking context. +Such a change to the background operator precedence +would cause ambiguity with uses like `return cmd1 &` and `throw cmd1 &`, +where the background operator would currently apply to the pipeline under the keyword. +To not break PowerShell's existing semantics, +special behaviour would need to be defined for `return $expr &`. + + #### Reasons against - Pipelines have an established concept of "success" compared to statements From 66e9e3c9f074fb62f45e1593917798bd8920edf2 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 12 Jun 2019 13:59:55 -0700 Subject: [PATCH 05/15] Newline --- 1-Draft/RFC0000-Chain-Operators.md | 1 - 1 file changed, 1 deletion(-) diff --git a/1-Draft/RFC0000-Chain-Operators.md b/1-Draft/RFC0000-Chain-Operators.md index 219ab3c6..ef7b3677 100644 --- a/1-Draft/RFC0000-Chain-Operators.md +++ b/1-Draft/RFC0000-Chain-Operators.md @@ -428,7 +428,6 @@ where the background operator would currently apply to the pipeline under the ke To not break PowerShell's existing semantics, special behaviour would need to be defined for `return $expr &`. - #### Reasons against - Pipelines have an established concept of "success" compared to statements From 9738de783a66fb601a85816a44e9fd32552a83c2 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 12 Jun 2019 16:41:29 -0700 Subject: [PATCH 06/15] Make examples clearer --- 1-Draft/RFC0000-Chain-Operators.md | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/1-Draft/RFC0000-Chain-Operators.md b/1-Draft/RFC0000-Chain-Operators.md index ef7b3677..99b06307 100644 --- a/1-Draft/RFC0000-Chain-Operators.md +++ b/1-Draft/RFC0000-Chain-Operators.md @@ -53,37 +53,53 @@ instead the term **pipeline chains** is proposed. ## User Experience +#### Example 1 + ```powershell Write-Output "Hello" && Write-Output "Hello again" ``` +Output: + ```output Hello Hello again ``` +#### Example 2 + ```powershell Write-Output "Hello" || Write-Output "Hello again" ``` +Output: + ```output Hello ``` +#### Example 3 + ```powershell Write-Error "Bad" && Write-Output "Hello again" ``` +Output: + ```output Write-Error "Bad" : Bad + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException ``` +#### Example 4 + ```powershell Write-Error "Bad" || Write-Output "Hello again" ``` +Output: + ```output Write-Error "Bad" : Bad + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException @@ -92,6 +108,8 @@ Write-Error "Bad" : Bad Hello again ``` +#### Example 5 + Using `echo` as a convenient example of a successful command and `false` as an example of an unsuccesful command. @@ -99,30 +117,44 @@ and `false` as an example of an unsuccesful command. echo "Hello" && Write-Output "Hi" ``` +Output: + ```output Hello Hi ``` +#### Example 6 + ```powershell false && Write-Output "Reached" ``` +Output: + ```output ``` +#### Example 7 + ```powershell false || Write-Output "Reached" ``` +Output: + ```output Reached ``` +#### Example 8 + ```powershell false || Write-Output "Command failed" && Write-Output "Backup" ``` +Output: + ```output Command failed Backup From af43ffe6c4346323b971ca990542d28e9141cb36 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 12 Jun 2019 16:45:50 -0700 Subject: [PATCH 07/15] Add throw example --- 1-Draft/RFC0000-Chain-Operators.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/1-Draft/RFC0000-Chain-Operators.md b/1-Draft/RFC0000-Chain-Operators.md index 99b06307..12b7d481 100644 --- a/1-Draft/RFC0000-Chain-Operators.md +++ b/1-Draft/RFC0000-Chain-Operators.md @@ -351,6 +351,20 @@ Errors will have the same semantics as the equivalent `cmd1; if ($?) { cmd2 }` syntax. Terminating errors will terminate the entire pipeline chain. +But output already emitted by earlier pipelines in the chain +will be output by the chain before terminating: + +```powershell +try +{ + $x = cmd1 && $(throw "Bad") +} +catch +{ +} + +$x # Has values from cmd1 +``` Non-terminating errors will cause the immediate pipeline to continue, and the pipeline chain will evaluate as normal based on the value of `$?`. From a4292e8a1e980c0b32fef92ad1a2d80544999717 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Wed, 24 Jul 2019 08:06:45 -0700 Subject: [PATCH 08/15] Add more examples --- 1-Draft/RFC0000-Chain-Operators.md | 675 ++++++++++++++++++++++++++--- 1 file changed, 616 insertions(+), 59 deletions(-) diff --git a/1-Draft/RFC0000-Chain-Operators.md b/1-Draft/RFC0000-Chain-Operators.md index 12b7d481..27d2fde9 100644 --- a/1-Draft/RFC0000-Chain-Operators.md +++ b/1-Draft/RFC0000-Chain-Operators.md @@ -9,6 +9,8 @@ Plan to implement: Yes # Pipeline Chain Operators +## Background + POSIX shells have what may be referred to as *AND-OR lists*, what the [Open Group's Shell Command Language specification](https://pubs.opengroup.org/onlinepubs/007904875/utilities/xcu_chap02.html#tag_02_09_03) describes as: @@ -31,75 +33,244 @@ false && echo foo || echo bar # Writes only "bar" to stdout (operators are left- true || echo foo && echo bar # Writes only "bar" to stdout ``` -Also from the Shell Command Language specification: +Similarly, `cmd.exe` also supports `&&` and `||`, which it terms *conditional processing symbols*. +From the [Command shell overview page](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-xp/bb490954(v=technet.10)#using-multiple-commands-and-conditional-processing-symbols): + +> When you run multiple commands with conditional processing symbols, +> the commands to the right of the conditional processing symbol act +> based upon the results of the command to the left of the conditional processing symbol. +> +> ... +> +> `&&`: Use to run the command following `&&` only if the command preceding the symbol is successful. +> Cmd.exe runs the first command, +> and then runs the second command only if the first command completed successfully. +> +> ... +> +> `||`: Use to run the command following `||` only if the command preceding `||` fails. +> Cmd.exe runs the first command, +> and then runs the second command only if the first command did not complete successfully +> (receives an error code greater than zero). + +Despite the wording here, `&&` has a lower precedence than `|` in `cmd.exe` +and can be used to sequence pipelines: + +```cmd +dir C:\ | sort && echo 'done' +``` -> A ';' or <newline> terminator shall cause the preceding AND-OR list to be executed sequentially; -> an '&' shall cause asynchronous execution of the preceding AND-OR list. +Historically, these operators have been reserved for implementation in PowerShell for some time. +Since PowerShell v2, including a `&&` token in a PowerShell script results in the following parse error: -Meaning that the entire AND-OR list is backgrounded: +> The token '&&' is not a valid statement separator in this version. -```bash -true && echo 'Success' & # The list `true && echo 'Success'` is executed in the background -``` +Despite the error message implying the intent to separate statements, +[the code to parse these operators](https://github.com/PowerShell/PowerShell/blob/6f0dacddc1b6ddc47a886f3943b56725d3d2e2f4/src/System.Management.Automation/engine/parser/Parser.cs#L5859-L5871) +occurs *within* the pipeline parsing logic, +possibly implying the intent to implement them as *command separators* (within a pipeline). + +## Proposal outline -This RFC proposes adding AND-OR lists to PowerShell, using the same `&&` and `||`. -Because "AND-OR list" is neither intuitive nor common as a terminology, -instead the term **pipeline chains** is proposed. +This RFC proposes: + +- The addition of `&&` and `||` as *pipeline chain operators* to PowerShell. +- That `&&` and `||` may be used between PowerShell pipelines to conditionally sequence them. +- That `$?` (PowerShell's execution success indicator) be used to determine the sequence from pipeline to pipeline. +- That such sequences of pipelines using `&&` and `||` be called **pipeline chains**. +- To also allow control flow statements (`throw`, `break`, `continue`, `return` and `exit`) at the end of such chains ## Motivation -> As a PowerShell user, I can chain commands with `&&` and `||` +> As a PowerShell user, I can chain commands and pipelines with `&&` and `||` > so that I have an ergonomic syntax to conditionally invoke side-effectful commands. +The chief motivation of pipeline chain operators is to make native commands +(i.e. commands run by invoking an executable as a subprocess) +simpler to use and sequence, as they are in other shells. + +Often these commands perform some action, +emit some informational output (and/or error output) and return an exit code. + +The aim of chain operators is to make the action success as easy to process as the output, +providing a convenient way to manipulate control flow around command outcome rather than output. +This is also the motivation behind allowing control flow statements at the end of pipelines. + ## User Experience -#### Example 1 +Also see: [test cases for implementation](https://github.com/PowerShell/PowerShell/blob/0b700828f22824c29eac70f8db1d2bf504b212d1/test/powershell/Language/Operators/PipelineChainOperator.Tests.ps1). + + +Pipeline chain operators are intended to behave +as if pipelines were written as a sequence of statements conditioned on `$?`: ```powershell -Write-Output "Hello" && Write-Output "Hello again" +cmd1 && cmd2 || cmd3 +``` + +should be the same as + +```powershell +cmd1 +if ($?) { cmd2 } +if (-not $?) { cmd3 } +``` + +### Native commands + +In these examples: + +- `echo` is a native command that writes its argument as output and returns an exit code of 0 +- `error` is a native command that writes its argument as output and returns an exit code of 1 + +```powershell +echo 'Hello' && echo 'Again' +``` + +```output +Hello +Again +``` + +--- + +```powershell +echo 'Hello' && error 'Bad' +``` + +```output +Hello +Bad +``` + +--- + +```powershell +error 'Bad' && echo 'Hello' +``` + +```output +Bad +``` + +--- + +```powershell +error 'Bad' || echo 'Hello' ``` -Output: +```output +Bad +Hello +``` + +--- + +```powershell +echo 'Hello' || echo 'Again' +``` + +```output +Hello +``` + +--- + +```powershell +error 'Bad' || error 'Very bad' +``` + +```output +Bad +Very bad +``` + +--- + +```powershell +echo 'Hi' || echo 'Message' && echo '2nd message' +``` + +```output +Hi +``` + +--- + +```powershell +error 'Bad' || echo 'Message' && echo '2nd message' +``` + +```output +Bad +Message +2nd message +``` + +--- + +```powershell +echo 'Hi' && error 'Bad' || echo 'Message' +``` + +``` +Hi +Bad +Message +``` + +--- + +### Cmdlets and Functions + +Cmdlets and functions work just like native commands, +except they don't set `$LASTEXITCODE` +and have other ways of expressing error conditions. + +Here the same principle applies as with native commands; +the statements proceed as if the next is +wrapped in `if ($?) { ... }`. + +```powershell +Write-Output "Hello" && Write-Output "Hello again" +``` ```output Hello Hello again ``` -#### Example 2 +--- ```powershell Write-Output "Hello" || Write-Output "Hello again" ``` -Output: - ```output Hello ``` -#### Example 3 +--- ```powershell Write-Error "Bad" && Write-Output "Hello again" ``` -Output: - ```output Write-Error "Bad" : Bad + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException ``` -#### Example 4 +`Write-Error` here emits a non-terminating error +and so we proceed to evaluate `$?`. + +--- ```powershell Write-Error "Bad" || Write-Output "Hello again" ``` -Output: - ```output Write-Error "Bad" : Bad + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException @@ -108,59 +279,400 @@ Write-Error "Bad" : Bad Hello again ``` -#### Example 5 +```powershell +echo 'Hi' && Write-Output 'Hello' +``` -Using `echo` as a convenient example of a successful command -and `false` as an example of an unsuccesful command. +```output +Hi +Hello +``` + +--- + +```powershell +error 'Bad' && Write-Output 'Hello' +``` + +``` +Bad +``` + +--- + +```powershell +Write-Error 'Bad' || echo 'Message' +``` + +```output +Bad +Message +``` + +--- + +### Pipelines + +Pipeline chains allow whole pipelines between chain operators. +As above, when a pipeline ends other than with a terminating error, +`$?` determines the chain logic. + +The whole pipeline on the left-hand side of an operator +will be evaluated before evaluating chain condition +and then right-hand side. ```powershell -echo "Hello" && Write-Output "Hi" +1,2,3 | ForEach-Object { $_ + 1 } && Write-Output 'Hello' +``` + +```output +2 +3 +4 +Hello ``` -Output: +--- + +```powershell +1,2,3 | ForEach-Object { if ($_ -eq 2) { Write-Error 'Bad' } else { $_ } } && Write-Output 'Hello' +``` ```output +1 +1,2,3 | ForEach-Object { if ($_ -eq 2) { Write-Error 'Bad' } else { $_ } } && Write-Output 'Hello' : Bad ++ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException ++ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException + +3 Hello -Hi ``` -#### Example 6 +--- + +```powershell +function FailInProcess +{ + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline)] + $Value + ) + + process + { + if ($_ -eq 3) + { + $err = Write-Error 'Bad' 2>&1 + $PSCmdlet.WriteError($err) + return + } + + $PSCmdlet.WriteObject($_) + } +} + +1,2,3,4 | FailInProcess && Write-Output 'Succeeded' +``` + +```output +1 +2 +FailInProcess : Bad +At line:22 char:11 ++ 1,2,3,4 | FailInProcess && Write-Output 'Succeeded' ++ ~~~~~~~~~~~~~ ++ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException ++ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,FailInProcess + +4 +``` + +(Even though the process block keeps going, `$?` is false) + +--- + +### Terminating errors and error handling + +Terminating errors supercede chain sequencing, +just as they would in a semicolon-separated sequence of statements. + +Uncaught errors will terminate the script. ```powershell -false && Write-Output "Reached" +function ThrowBad +{ + throw 'Bad' +} + +ThrowBad && Write-Output 'Success' ``` -Output: +```output +Bad +At line:3 char:5 ++ throw 'Bad' + ~~~~~~~~~~~ ++ CategoryInfo : OperationStopped: (Bad:String) [], RuntimeException ++ FullyQualifiedErrorId : Bad +``` + +--- + +This is the same with cmdlet terminating errors. + +```powershell +function ThrowTerminating +{ + [CmdletBinding()] + param() + + $err = Write-Error 'Bad' 2>&1 + $PSCmdlet.ThrowTerminatingError($err) +} + +ThrowTerminating && Write-Output 'Success' +``` ```output +ThrowTerminating : Bad +At line:1 char:1 ++ ThrowTerminating && Write-Output 'Success' ++ ~~~~~~~~~~~~~~~~ ++ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException ++ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,ThrowTerminating ``` -#### Example 7 +--- + +When the error is caught, +the flow of control abandons the chain for the catch as expected. ```powershell -false || Write-Output "Reached" +function ThrowBad +{ + throw 'Bad' +} + +try +{ + ThrowBad && Write-Output 'Success' +} +catch +{ + Write-Output $_.FullyQualifiedErrorId +} ``` -Output: +```output +Bad +``` + +--- + +When the error is suppressed with an error action preference, +the result is again based on `$?`. + +```powershell +function ThrowTerminating +{ + [CmdletBinding()] + param() + + Write-Output 'In ThrowTerminating' + $ex = [System.Exception]::new('Bad') + $errId = 'Bad' + $errCat = 'NotSpecified' + $err = [System.Management.Automation.ErrorRecord]::new($ex, $errId, $errCat, $null) + $PSCmdlet.ThrowTerminatingError($err) +} + +ThrowTerminating -ErrorAction Ignore && Write-Output 'Success' +``` ```output -Reached +In ThrowTerminating ``` -#### Example 8 +--- ```powershell -false || Write-Output "Command failed" && Write-Output "Backup" +function ThrowTerminating +{ + [CmdletBinding()] + param() + + Write-Output 'In ThrowTerminating' + $ex = [System.Exception]::new('Bad') + $errId = 'Bad' + $errCat = 'NotSpecified' + $err = [System.Management.Automation.ErrorRecord]::new($ex, $errId, $errCat, $null) + $PSCmdlet.ThrowTerminatingError($err) +} + +ThrowTerminating -ErrorAction Ignore || Write-Output 'Success' ``` -Output: +```output +In ThrowTerminating +Success +``` + +--- + +If traps are set, they will continue or break the pipeline chain as configured. + +```powershell +trap +{ + Write-Output 'TRAP' + break +} + +function ThrowTerminating +{ + [CmdletBinding()] + param() + + Write-Output 'In ThrowTerminating' + $ex = [System.Exception]::new('Bad') + $errId = 'Bad' + $errCat = 'NotSpecified' + $err = [System.Management.Automation.ErrorRecord]::new($ex, $errId, $errCat, $null) + $PSCmdlet.ThrowTerminatingError($err) +} + +ThrowTerminating && Write-Output 'Success' +``` ```output -Command failed -Backup +In ThrowTerminating +TRAP +ThrowTerminating : Bad +At line:20 char:1 ++ ThrowTerminating && Write-Output 'Success' ++ ~~~~~~~~~~~~~~~~ ++ CategoryInfo : NotSpecified: (:) [ThrowTerminating], Exception ++ FullyQualifiedErrorId : Bad,ThrowTerminating + ``` -Also see: [intended test cases for implementation](https://github.com/PowerShell/PowerShell/blob/f8b899e42c86957a1b58273be0029f40a67bf1b6/test/powershell/Language/Operators/BashControlOperator.Tests.ps1). +--- + +```powershell +trap +{ + Write-Output 'TRAP' + continue +} + +function ThrowTerminating +{ + [CmdletBinding()] + param() + + Write-Output 'In ThrowTerminating' + $ex = [System.Exception]::new('Bad') + $errId = 'Bad' + $errCat = 'NotSpecified' + $err = [System.Management.Automation.ErrorRecord]::new($ex, $errId, $errCat, $null) + $PSCmdlet.ThrowTerminatingError($err) +} + +ThrowTerminating && Write-Output 'Success' +``` + +```output +In ThrowTerminating +TRAP +``` + +--- + +```powershell +trap +{ + Write-Output 'TRAP' + continue +} + +function ThrowTerminating +{ + [CmdletBinding()] + param() + + Write-Output 'In ThrowTerminating' + $ex = [System.Exception]::new('Bad') + $errId = 'Bad' + $errCat = 'NotSpecified' + $err = [System.Management.Automation.ErrorRecord]::new($ex, $errId, $errCat, $null) + $PSCmdlet.ThrowTerminatingError($err) +} + +ThrowTerminating || Write-Output 'Success' +``` + +```output +In ThrowTerminating +TRAP +Success +``` + +--- + +### Assignment + +Because pipeline chains are statements, they can be assigned from. + +```powershell +$x = Write-Output 'Hi' && Write-Output 'Hello' +$x +``` + +```output +Hi +Hello +``` + +--- + +With pipeline chains, if it would be written to the pipeline, +it's captured by assignment. +This is true even if something in the chain later throws. + +```powershell +try +{ + $x = Write-Output 'Hi' && throw 'Bad' +} +catch +{ + $_.FullyQualifiedErrorId +} +$x +``` + +```output +Bad +Hi +``` + +(Compare this to `$x = $(1;2; throw 'Bad')`.) + +--- + +### Flow control statements + +Finally, pipeline chains can use flow control statements. + +```powershell +``` + +```output +``` + +--- + +--- ## Specification @@ -227,11 +739,11 @@ This will be grouped as: As a syntax tree, this would look like: ```none - "||" - / \ - "&&" "cmd3" - / \ - "cmd1" "cmd2" + || + / \ + && cmd3 + / \ + cmd1 cmd2 ``` With the syntax tree deepening on the left as more operators are chained. @@ -260,11 +772,11 @@ Will bind as: Having the syntax tree: ```none - - / \ - "&&" "&" - / \ - "cmd1" "cmd2" + + / \ + && & + / \ + cmd1 cmd2 ``` The consequence of this will be that an entire pipeline chain @@ -413,18 +925,62 @@ Also see the [alternate proposals section](#alternate-proposals-and-consideratio - Original issue: [PowerShell/PowerShell #3241](https://github.com/PowerShell/PowerShell/issues/3241). -- Work-in-progress implementation: [PowerShell/PowerShell #9849](https://github.com/PowerShell/PowerShell/pull/9849). +- Implementation: [PowerShell/PowerShell #9849](https://github.com/PowerShell/PowerShell/pull/9849). - [Current handling of `&&` and `||` in the parser](https://github.com/PowerShell/PowerShell/blob/af1de9e88d28014438ff3414e82298e5b14f6e81/src/System.Management.Automation/engine/parser/Parser.cs#L5846-L5860). ## Alternate Proposals and Considerations -### `&&` and `||` as a statement separator +### Command vs pipeline vs statement separation + +An important syntactic and semantic question is +what level `&&` and `||` operate at +(using brackets to denote syntactic groupings in `$x = cmd1 | cmd2 && cmd3`): + +- Between commands, where they occur within pipelines (`$x = [[cmd1] | [cmd2 && cmd3]]`) +- Between pipelines, where they occur within statements (`$x = [[cmd1 | cmd2] && cmd3]]`) +- Between statements (`[$x = [cmd1 | cmd2]] && [cmd3]`) + +In the POSIX shell, `&&` and `||` separate pipelines, +but pipelines encompass all statements. +Statelines like `if`, `for` and `case` are *compound commands* +where between keywords like `if` and `fi`, +everything is considered a single command. + +So for example the following is possible: + +```sh +if [ -e ./file.txt ]; then echo 'File exists'; fi && echo 'File does not exist' | cat - +``` + +(This always prints `File does not exist`, since the `if` is always considered to succeed.) + +In `cmd.exe`, the more template-driven approach means that +`if` and `for` being commands treat `&&` as part of the argument: + +```cmd +>for %i in (1 2 3) do echo %i && echo 'done' +``` + +```output +>echo 1 && echo 'done' +1 +'done' + +>echo 2 && echo 'done' +2 +'done' + +>echo 3 && echo 'done' +3 +'done' +``` -To be more bash-like, an alternative implementation might treat -`&&` and `||` with the same precedence as `;`, as a way to separate statements. +In PowerShell, unlike in the POSIX shell, +all pipelines are statements but not all statements are pipelines. +This means we must choose between separating statements and separating pipelines. -This would allow assignment within chains, like: +Allowing `&&` and `||` between statements in PowerShell might look like: ```powershell $x = cmd1 && $y = cmd2 && $x + $y @@ -476,9 +1032,10 @@ special behaviour would need to be defined for `return $expr &`. #### Reasons against -- Pipelines have an established concept of "success" compared to statements +- Pipelines have an established concept of "success", + whereas other statements generally do not. - Background operators become less useful with respect to chains unless their - syntax is changed in a significant way + syntax is changed in a significant way. ### Allowing control flow statements at the end of chains From 824c1e66677affebbddbcc4afdaba93f19818a27 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Thu, 25 Jul 2019 14:21:57 -0700 Subject: [PATCH 09/15] Final edits --- 1-Draft/RFC0000-Chain-Operators.md | 279 +++++++++++++++++++++-------- 1 file changed, 208 insertions(+), 71 deletions(-) diff --git a/1-Draft/RFC0000-Chain-Operators.md b/1-Draft/RFC0000-Chain-Operators.md index 27d2fde9..bef334b2 100644 --- a/1-Draft/RFC0000-Chain-Operators.md +++ b/1-Draft/RFC0000-Chain-Operators.md @@ -79,6 +79,7 @@ This RFC proposes: - That `$?` (PowerShell's execution success indicator) be used to determine the sequence from pipeline to pipeline. - That such sequences of pipelines using `&&` and `||` be called **pipeline chains**. - To also allow control flow statements (`throw`, `break`, `continue`, `return` and `exit`) at the end of such chains + when the chain could be used as a statement (not just a pipeline). ## Motivation @@ -656,7 +657,7 @@ Bad Hi ``` -(Compare this to `$x = $(1;2; throw 'Bad')`.) +(Compare this to `$x = . { 1;2; throw 'Bad' }`.) --- @@ -664,14 +665,117 @@ Hi Finally, pipeline chains can use flow control statements. +Here we assume a native command `exitwith`, +which requires its first argument to be an integer, +prints that argument to the console +and returns it as an exit code: + +``` +> exitwith 0 +0 +> $LASTEXITCODE +0 +``` + +``` +> exitwith 42 +42 +> $LASTEXITCODE +42 +``` + +--- + +Here, `|| break` means the loop will stop early +when `exitwith` returns a non-zero exit code (`exitwith 1`). + +```powershell +for ($i = 0; $i -lt 10; $i++) +{ + exitwith $i || break +} +``` + +```output +0 +1 +``` + +--- + +Here the loop continues when `exitwith` succeeds: + +```powershell +$codes = 0,0,1,0 +foreach ($i in $codes) +{ + exitwith $i && continue + Write-Information 'Cleaning up' +} +``` + +```output +0 +0 +1 +Cleaning up +0 +``` + +--- + +When a native command succeeds, +that might mean we have a useful value to return. + ```powershell +function Get-Thing +{ + exitwith 0 && return 'SUCCESS' + + return 'FAILURE' +} + +Get-Thing ``` ```output +0 +SUCCESS ``` --- +Alternatively, a native command may fail +but we want to use an exception for that. + +```powershell +exitwith 1 || throw 'ERROR!' +``` + +```output +1 + +ERROR! +At line:1 char:14 ++ exitwith 1 || throw 'ERROR!' ++ ~~~~~~~~~~~~~~ ++ CategoryInfo : OperationStopped: (ERROR!:String) [], RuntimeException ++ FullyQualifiedErrorId : ERROR! +``` + +--- + +In dire circumstances, you might actually want to exit. + +```powershell +exitwith 1 || exit 1 +``` + +```output +1 +# PowerShell exits... +``` + --- ## Specification @@ -689,8 +793,8 @@ statement: pipeline_chain: | pipeline - | pipeline_chain "&&" [newlines] pipeline - | pipeline_chain "||" [newlines] pipeline + | pipeline_chain [newline] "&&" [newlines] pipeline + | pipeline_chain [newline] "||" [newlines] pipeline ``` #### Optional pipeline chaining @@ -715,6 +819,15 @@ cmd1 && cmd3 ``` +Following on from the recent pipeline pre-continuation addition to PowerShell, +the following is also proposed: + +```powershell +cmd1 + && cmd2 + || cmd3 +``` + If the end of file is reached after a pipeline chain operator, incomplete input will be reported so that integrating tools will know to keep prompting for input. @@ -783,6 +896,55 @@ The consequence of this will be that an entire pipeline chain can be sent to a background job for evaluation, rather than individual pipelines within it. +#### Pipeline chains vs statement chains + +Currently, there are circumstances in PowerShell +where a pipeline can be used as one of any statements, +such as after assignment or in a subexpression. +There are also places where a only a pipeline can be used, +such as in an `if` condition or in a subpipeline. + +To accomodate this difference +and the inclusion of flow control statements on the ends of pipelines, +there is a proposed distinction between chains that must be pipelines +and chains that may be used as statements, +with the former being *pipeline chains* +and the latter being *statement chains*. + +In effect there is no user experience difference other than +not being able to use `return`/`continue`/`break`/`throw`/`exit` +at the ends of pipelines used in pipeline-specific scenarios. + +For example: + +```powershell +if ('Thing' && throw 'Bad') +{ + 'Hi' +} +``` + +will not recognise `throw` as a keyword. + +To match `if (throw 'Bad') { ... }`, the proposed behaviour is to parse it as a command, +so that invoking the above gives: + +```output +throw : The term 'throw' is not recognized as the name of a cmdlet, function, script file, or operable program. +Check the spelling of the name, or if a path was included, verify that the path is correct and try again. +At line:1 char:10 ++ if (1 && throw 'Bad') { 'Hi' } ++ ~~~~~ ++ CategoryInfo : ObjectNotFound: (throw:String) [], CommandNotFoundException ++ FullyQualifiedErrorId : CommandNotFoundException +``` + +These control flow statements would only be allowed *at the end* of chains because: + +- Control flow makes any invocation after the statement unreachable +- `return` and `throw` allow pipelines as subordinate expressions, + meaning it would be grammatically impossible to use those statements mid-chain. + ### Semantics #### Pipeline "success" @@ -806,7 +968,7 @@ cmd1 && cmd2 | v -$(cmd1; if ($?) { cmd2 }) +. { cmd1; if ($?) { cmd2 } } ``` ```none @@ -815,7 +977,7 @@ cmd1 || cmd2 | v -$(cmd1; if (-not $?) { cmd2 }) +. { cmd1; if (-not $?) { cmd2 } } ``` ```none @@ -824,7 +986,7 @@ cmd1 && cmd2 || cmd3 | v -$(cmd1; if ($?) { cmd2 }; if (-not $?) { cmd3 }) +. { cmd1; if ($?) { cmd2 }; if (-not $?) { cmd3 } } # Note that cmd1 failing runs cmd3 ``` @@ -869,7 +1031,7 @@ will be output by the chain before terminating: ```powershell try { - $x = cmd1 && $(throw "Bad") + $x = cmd1 && throw "Bad" } catch { @@ -885,12 +1047,20 @@ and the pipeline chain will evaluate as normal based on the value of `$?`. #### New Abstract Syntax Tree (AST) types -The new AST type, `PipelineChainAst`, -will be created to represent a pipeline chain. -This will inherit from `PipelineBaseAst` -and may occur anywhere a `PipelineBaseAst` does in a PowerShell AST. +Two new AST types are proposed: + +- `PipelineChainAst`, which refers to a pipeline chain + that can be used anywhere where a pipeline could be currently. + This inherits from `PipelineBaseAst`. +- `StatementChainAst`, which refers to a pipeline chain + used anywhere a statement could be currently. + This inherits from `StatementAst` -`ICustomAstVisitor2` and `AstVisitor2` would be extended to deal with this AST, +The separation of these ASTs means it remains impossible +to create certain constructions using AST constructors +that the parser would not allow. + +`ICustomAstVisitor2` and `AstVisitor2` would be extended to deal with these ASTs, and .NET Core 3's new default interface implementation feature would be leveraged to ensure this does not break things as previous syntactic introductions have been forced to. @@ -1037,74 +1207,41 @@ special behaviour would need to be defined for `return $expr &`. - Background operators become less useful with respect to chains unless their syntax is changed in a significant way. -### Allowing control flow statements at the end of chains - -A compromise to the above is to only allow "control flow statements" -at the end of chains. - -For example: - -```powershell -cmd1 || throw "cmd1 failed" -``` - -```powershell -function Invoke-Command -{ - cmd1 && return - - cmd2 -} -``` - -```powershell -foreach ($v in 1..100) -{ - cmd1 $v && break -} -``` - -These would only be allowed *at the end* of chains because: - -- Control flow makes any invocation after the statement unreachable -- `return` and `throw` allow pipelines as subordinate expressions, - meaning it would be grammatically impossible to use those statements mid-chain. - See below for more details. - -#### Reasons against +### Not allowing control flow statements at the end of chains -- `throw` and `return` currently allow a subordinate pipeline: +The main proposal suggests adding control flow +statements to the end of pipeline chains. - ```powershell - throw "Bad" - ``` +This introduces complications: - ```powershell - throw 1,2,3 | Write-Output - ``` +- A pipeline chain can be both over and under a `return`: - ```powershell - throw "Useless" & # Throws a background job... - ``` + ```powershell + cmd1 && return cmd2 && cmd3 + ``` - If we keep the principle that anywhere a pipeline is allowed, - a pipeline chain is now allowed, then constructions like this are possible: + groups as - ```powershell - cmd1 || throw "Bad" && cmd3 - ``` + ```text + cmd1 && [return [cmd2 && cmd3]] + ``` - However the grouping is this: +- A `throw` can do the same, + but the proposal is to explicitly disallow this: - ```none - [cmd1 || throw ["Bad" && cmd3]] - ``` + ```powershell + cmd1 && throw 'a' && 'b' + ``` - This could lead to confusing semantic corner cases + ```output + At line:1 char:19 + + cmd1 && throw 'a' && 'b' + + ~~ + + Pipeline chain operators '&&' and '||' may not be used after 'throw'. + ``` -- The compromise nature of this approach means the PowerShell Language - becomes more complicated and arguably less consistent, - both conceptually and in terms of maintenance. + This is proposed since `throw` stringifies its given value, + making a construction like the above much less useful than for `return`. ### Different evaluations of command "success" @@ -1165,4 +1302,4 @@ Other possibilities are: - Command control operators - Bash control operators - Control operators -- Short circuit operators \ No newline at end of file +- Short circuit operators From 250fa3193b9f4398115df0745799dc5cc65e9bac Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Thu, 25 Jul 2019 15:00:34 -0700 Subject: [PATCH 10/15] Update wording --- 1-Draft/RFC0000-Chain-Operators.md | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/1-Draft/RFC0000-Chain-Operators.md b/1-Draft/RFC0000-Chain-Operators.md index bef334b2..56827bdb 100644 --- a/1-Draft/RFC0000-Chain-Operators.md +++ b/1-Draft/RFC0000-Chain-Operators.md @@ -965,8 +965,8 @@ That is, there are the following direct equivalents: ```none cmd1 && cmd2 - | - v + | + v . { cmd1; if ($?) { cmd2 } } ``` @@ -974,8 +974,8 @@ cmd1 && cmd2 ```none cmd1 || cmd2 - | - v + | + v . { cmd1; if (-not $?) { cmd2 } } ``` @@ -983,8 +983,8 @@ cmd1 || cmd2 ```none cmd1 && cmd2 || cmd3 - | - v + | + v . { cmd1; if ($?) { cmd2 }; if (-not $?) { cmd3 } } # Note that cmd1 failing runs cmd3 @@ -1243,6 +1243,23 @@ This introduces complications: This is proposed since `throw` stringifies its given value, making a construction like the above much less useful than for `return`. +This also complicates the grammar and the AST, since: + +- We have to complexify logic about stamtents vs pipelines and control flow logic +- More AST types may be needed to prevent bad AST constructions + +Not including control flow operators in pipelines would mean: + +- The grammar is simplified +- We only need one AST type +- There's no confusing embedding of chains over and under a `return` + +#### Reasons against + +- Control flow manipulation with native commands is currently not ergonomic +- A construction like `cmd || throw 'Failed'` is very handy +- Bad or confusing cases are corner cases, unlikely to be hit under normal usage + ### Different evaluations of command "success" The current proposal is to simply use `$?` to determine chain continuation. From 6f81d31a4e3b1aa21ec07b1c519cd1fc894f4de4 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 23 Sep 2019 22:40:54 +0530 Subject: [PATCH 11/15] Update 1-Draft/RFC0000-Chain-Operators.md Co-Authored-By: Joey Aiello --- 1-Draft/RFC0000-Chain-Operators.md | 1 + 1 file changed, 1 insertion(+) diff --git a/1-Draft/RFC0000-Chain-Operators.md b/1-Draft/RFC0000-Chain-Operators.md index 56827bdb..ea56a2f5 100644 --- a/1-Draft/RFC0000-Chain-Operators.md +++ b/1-Draft/RFC0000-Chain-Operators.md @@ -194,6 +194,7 @@ echo 'Hi' || echo 'Message' && echo '2nd message' ```output Hi +2nd message ``` --- From 874a6296c926d31130e603ce759f73b6c446fb7c Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Mon, 30 Sep 2019 11:13:41 -0700 Subject: [PATCH 12/15] Remove statement chains --- 1-Draft/RFC0000-Chain-Operators.md | 526 +++++++++++------------------ 1 file changed, 203 insertions(+), 323 deletions(-) diff --git a/1-Draft/RFC0000-Chain-Operators.md b/1-Draft/RFC0000-Chain-Operators.md index 56827bdb..cab679fd 100644 --- a/1-Draft/RFC0000-Chain-Operators.md +++ b/1-Draft/RFC0000-Chain-Operators.md @@ -78,8 +78,6 @@ This RFC proposes: - That `&&` and `||` may be used between PowerShell pipelines to conditionally sequence them. - That `$?` (PowerShell's execution success indicator) be used to determine the sequence from pipeline to pipeline. - That such sequences of pipelines using `&&` and `||` be called **pipeline chains**. -- To also allow control flow statements (`throw`, `break`, `continue`, `return` and `exit`) at the end of such chains - when the chain could be used as a statement (not just a pipeline). ## Motivation @@ -95,7 +93,6 @@ emit some informational output (and/or error output) and return an exit code. The aim of chain operators is to make the action success as easy to process as the output, providing a convenient way to manipulate control flow around command outcome rather than output. -This is also the motivation behind allowing control flow statements at the end of pipelines. ## User Experience @@ -124,6 +121,8 @@ In these examples: - `echo` is a native command that writes its argument as output and returns an exit code of 0 - `error` is a native command that writes its argument as output and returns an exit code of 1 +#### Simple successful command chain + ```powershell echo 'Hello' && echo 'Again' ``` @@ -135,6 +134,8 @@ Again --- +#### Simple error after successful command + ```powershell echo 'Hello' && error 'Bad' ``` @@ -146,6 +147,8 @@ Bad --- +#### Error followed by command in success case + ```powershell error 'Bad' && echo 'Hello' ``` @@ -156,6 +159,8 @@ Bad --- +#### Error followed by command in failure case + ```powershell error 'Bad' || echo 'Hello' ``` @@ -167,6 +172,8 @@ Hello --- +#### Command followed by command in failure case + ```powershell echo 'Hello' || echo 'Again' ``` @@ -177,6 +184,8 @@ Hello --- +#### Error followed by error in failure case + ```powershell error 'Bad' || error 'Very bad' ``` @@ -188,16 +197,21 @@ Very bad --- +#### Composite chain: 1st succeeds, 2nd is skipped, 3rd is run + ```powershell echo 'Hi' || echo 'Message' && echo '2nd message' ``` ```output Hi +2nd message ``` --- +#### Composite chain: 1st fails, 2nd is run, 3rd is run + ```powershell error 'Bad' || echo 'Message' && echo '2nd message' ``` @@ -210,6 +224,8 @@ Message --- +#### Composite chain: 1st succeeds, 2nd fails, 3rd is run + ```powershell echo 'Hi' && error 'Bad' || echo 'Message' ``` @@ -222,7 +238,7 @@ Message --- -### Cmdlets and Functions +### Cmdlets and Functions Cmdlets and functions work just like native commands, except they don't set `$LASTEXITCODE` @@ -232,6 +248,8 @@ Here the same principle applies as with native commands; the statements proceed as if the next is wrapped in `if ($?) { ... }`. +#### Simple cmdlet chain: success then success + ```powershell Write-Output "Hello" && Write-Output "Hello again" ``` @@ -243,6 +261,8 @@ Hello again --- +#### Simple cmdlet chain: success otherwise success + ```powershell Write-Output "Hello" || Write-Output "Hello again" ``` @@ -253,6 +273,8 @@ Hello --- +#### Simple cmdlet chain: error then success + ```powershell Write-Error "Bad" && Write-Output "Hello again" ``` @@ -268,6 +290,8 @@ and so we proceed to evaluate `$?`. --- +#### Simple cmdlet chain: error otherwise success + ```powershell Write-Error "Bad" || Write-Output "Hello again" ``` @@ -280,6 +304,10 @@ Write-Error "Bad" : Bad Hello again ``` +--- + +#### Simple command chain: native success then cmdlet success + ```powershell echo 'Hi' && Write-Output 'Hello' ``` @@ -291,16 +319,20 @@ Hello --- +#### Simple command chain: native error then cmdlet success + ```powershell error 'Bad' && Write-Output 'Hello' ``` -``` +```output Bad ``` --- +#### Simple command chain: cmdlet error otherwise native success + ```powershell Write-Error 'Bad' || echo 'Message' ``` @@ -322,6 +354,8 @@ The whole pipeline on the left-hand side of an operator will be evaluated before evaluating chain condition and then right-hand side. +#### Succeeding pipeline on the left hand side of a chain + ```powershell 1,2,3 | ForEach-Object { $_ + 1 } && Write-Output 'Hello' ``` @@ -335,8 +369,41 @@ Hello --- +#### Non-terminating error in pipeline + +When some input fails while processing a pipeline, +that sets `$?` for that command invocation +and the pipeline chain proceeds accordingly. + ```powershell -1,2,3 | ForEach-Object { if ($_ -eq 2) { Write-Error 'Bad' } else { $_ } } && Write-Output 'Hello' +function Test-NonTerminatingError +{ + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline)] + $Input + ) + + process + { + if ($Input -ne 2) + { + return $Input + } + + # Write a non-terminating error when $Input is 2 + # Note that Write-Error will not set $? for the caller here + + $exception = [System.Exception]::new('Bad') + $errorId = 'Bad' + $errorCategory = 'InvalidData' + $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, $errorId, $errorCategory, $null) + + $PSCmdlet.WriteError($errorRecord) + } +} + +1,2,3 | Test-NonTerminatingError && Write-Output 'Hello' ``` ```output @@ -346,101 +413,140 @@ Hello + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException 3 -Hello +``` + +#### Non-terminating error with `||` + +```powershell +1,2,3 | Test-NonTerminatingError || Write-Output 'Problem!' +``` + +```output +1 +1,2,3 | ForEach-Object { if ($_ -eq 2) { Write-Error 'Bad' } else { $_ } } && Write-Output 'Hello' : Bad ++ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException ++ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException + +3 +Problem! ``` --- +#### Pipeline-terminating error in a chain + ```powershell -function FailInProcess +function Test-PipelineTerminatingError { [CmdletBinding()] param( [Parameter(ValueFromPipeline)] - $Value + $Input ) process { - if ($_ -eq 3) + if ($Input -ne 2) { - $err = Write-Error 'Bad' 2>&1 - $PSCmdlet.WriteError($err) - return + return $Input } - $PSCmdlet.WriteObject($_) + # Write a non-terminating error when $Input is 2 + + $exception = [System.Exception]::new('Bad') + $errorId = 'Bad' + $errorCategory = 'InvalidData' + $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, $errorId, $errorCategory, $null) + + # Note the use of ThrowTerminatingError() rather than WriteError() + $PSCmdlet.ThrowTerminatingError($errorRecord) } } -1,2,3,4 | FailInProcess && Write-Output 'Succeeded' +1,2,3 | Test-PipelineTerminatingError && Write-Output 'Succeeded' ``` ```output 1 -2 -FailInProcess : Bad -At line:22 char:11 -+ 1,2,3,4 | FailInProcess && Write-Output 'Succeeded' -+ ~~~~~~~~~~~~~ -+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException -+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,FailInProcess +Test-PipelineTerminatingError : Bad +At line:1 char:9 ++ 1,2,3 | Test-PipelineTerminatingError && Write-Output 'Succeeded' ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++ CategoryInfo : InvalidData: (:) [Test-PipelineTerminatingError], Exception ++ FullyQualifiedErrorId : Bad,Test-PipelineTerminatingError -4 ``` -(Even though the process block keeps going, `$?` is false) +Note that unlike with the non-terminating error, +the pipeline does not proceed to process `3`. ---- +#### Pipeline termination is not chain termination -### Terminating errors and error handling +```powershell +1,2,3 | Test-PipelineTerminatingError || Write-Output 'Failed' +``` -Terminating errors supercede chain sequencing, -just as they would in a semicolon-separated sequence of statements. +```output +1 +Test-PipelineTerminatingError : Bad +At line:1 char:9 ++ 1,2,3 | Test-PipelineTerminatingError || Write-Output 'Failed' ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++ CategoryInfo : InvalidData: (:) [Test-PipelineTerminatingError], Exception ++ FullyQualifiedErrorId : Bad,Test-PipelineTerminatingError -Uncaught errors will terminate the script. +Failed +``` + +Here, the pipeline to the left of `||` is terminated, +but the chain continues since `||` is used and `$?` is false, +meaning `Write-Output 'Failed'` is executed. + +#### Interaction with `try`/`catch` + +If an error is caught from within a pipeline chain, +the chain will be abandoned for the catch block. ```powershell -function ThrowBad +try { - throw 'Bad' + 1,2,3 | Test-PipelineTerminatingError || Write-Output 'Failed' +} +catch +{ + Write-Output "Caught error" } - -ThrowBad && Write-Output 'Success' ``` ```output -Bad -At line:3 char:5 -+ throw 'Bad' + ~~~~~~~~~~~ -+ CategoryInfo : OperationStopped: (Bad:String) [], RuntimeException -+ FullyQualifiedErrorId : Bad +1 +Caught error ``` --- -This is the same with cmdlet terminating errors. +### Script-terminating errors and error handling + +Script-terminating errors supercede chain sequencing, +just as they would in a semicolon-separated sequence of statements. + +Uncaught errors will terminate the script. ```powershell -function ThrowTerminating +function ThrowBad { - [CmdletBinding()] - param() - - $err = Write-Error 'Bad' 2>&1 - $PSCmdlet.ThrowTerminatingError($err) + throw 'Bad' } -ThrowTerminating && Write-Output 'Success' +ThrowBad || Write-Output 'Failed' ``` ```output -ThrowTerminating : Bad -At line:1 char:1 -+ ThrowTerminating && Write-Output 'Success' -+ ~~~~~~~~~~~~~~~~ -+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException -+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,ThrowTerminating +Bad +At line:3 char:5 ++ throw 'Bad' + ~~~~~~~~~~~ ++ CategoryInfo : OperationStopped: (Bad:String) [], RuntimeException ++ FullyQualifiedErrorId : Bad ``` --- @@ -456,7 +562,7 @@ function ThrowBad try { - ThrowBad && Write-Output 'Success' + ThrowBad || Write-Output 'Failed' } catch { @@ -470,56 +576,6 @@ Bad --- -When the error is suppressed with an error action preference, -the result is again based on `$?`. - -```powershell -function ThrowTerminating -{ - [CmdletBinding()] - param() - - Write-Output 'In ThrowTerminating' - $ex = [System.Exception]::new('Bad') - $errId = 'Bad' - $errCat = 'NotSpecified' - $err = [System.Management.Automation.ErrorRecord]::new($ex, $errId, $errCat, $null) - $PSCmdlet.ThrowTerminatingError($err) -} - -ThrowTerminating -ErrorAction Ignore && Write-Output 'Success' -``` - -```output -In ThrowTerminating -``` - ---- - -```powershell -function ThrowTerminating -{ - [CmdletBinding()] - param() - - Write-Output 'In ThrowTerminating' - $ex = [System.Exception]::new('Bad') - $errId = 'Bad' - $errCat = 'NotSpecified' - $err = [System.Management.Automation.ErrorRecord]::new($ex, $errId, $errCat, $null) - $PSCmdlet.ThrowTerminatingError($err) -} - -ThrowTerminating -ErrorAction Ignore || Write-Output 'Success' -``` - -```output -In ThrowTerminating -Success -``` - ---- - If traps are set, they will continue or break the pipeline chain as configured. ```powershell @@ -609,13 +665,13 @@ function ThrowTerminating $PSCmdlet.ThrowTerminatingError($err) } -ThrowTerminating || Write-Output 'Success' +ThrowTerminating || Write-Output 'Continued' ``` ```output In ThrowTerminating TRAP -Success +Continued ``` --- @@ -636,9 +692,9 @@ Hello --- -With pipeline chains, if it would be written to the pipeline, -it's captured by assignment. -This is true even if something in the chain later throws. +With pipeline chains, as with any other PowerShell statements, +assignment either succeeds or does not; +there is no concept of partial success. ```powershell try @@ -647,134 +703,17 @@ try } catch { - $_.FullyQualifiedErrorId + Write-Output "Error: $($_.FullyQualifiedErrorId)" } -$x +Write-Output "`$x: $x" ``` ```output -Bad -Hi +Error: Bad +$x: ``` -(Compare this to `$x = . { 1;2; throw 'Bad' }`.) - ---- - -### Flow control statements - -Finally, pipeline chains can use flow control statements. - -Here we assume a native command `exitwith`, -which requires its first argument to be an integer, -prints that argument to the console -and returns it as an exit code: - -``` -> exitwith 0 -0 -> $LASTEXITCODE -0 -``` - -``` -> exitwith 42 -42 -> $LASTEXITCODE -42 -``` - ---- - -Here, `|| break` means the loop will stop early -when `exitwith` returns a non-zero exit code (`exitwith 1`). - -```powershell -for ($i = 0; $i -lt 10; $i++) -{ - exitwith $i || break -} -``` - -```output -0 -1 -``` - ---- - -Here the loop continues when `exitwith` succeeds: - -```powershell -$codes = 0,0,1,0 -foreach ($i in $codes) -{ - exitwith $i && continue - Write-Information 'Cleaning up' -} -``` - -```output -0 -0 -1 -Cleaning up -0 -``` - ---- - -When a native command succeeds, -that might mean we have a useful value to return. - -```powershell -function Get-Thing -{ - exitwith 0 && return 'SUCCESS' - - return 'FAILURE' -} - -Get-Thing -``` - -```output -0 -SUCCESS -``` - ---- - -Alternatively, a native command may fail -but we want to use an exception for that. - -```powershell -exitwith 1 || throw 'ERROR!' -``` - -```output -1 - -ERROR! -At line:1 char:14 -+ exitwith 1 || throw 'ERROR!' -+ ~~~~~~~~~~~~~~ -+ CategoryInfo : OperationStopped: (ERROR!:String) [], RuntimeException -+ FullyQualifiedErrorId : ERROR! -``` - ---- - -In dire circumstances, you might actually want to exit. - -```powershell -exitwith 1 || exit 1 -``` - -```output -1 -# PowerShell exits... -``` +(Compare this to `$x = . { 'Hi'; throw 'Bad' }`.) --- @@ -896,55 +835,6 @@ The consequence of this will be that an entire pipeline chain can be sent to a background job for evaluation, rather than individual pipelines within it. -#### Pipeline chains vs statement chains - -Currently, there are circumstances in PowerShell -where a pipeline can be used as one of any statements, -such as after assignment or in a subexpression. -There are also places where a only a pipeline can be used, -such as in an `if` condition or in a subpipeline. - -To accomodate this difference -and the inclusion of flow control statements on the ends of pipelines, -there is a proposed distinction between chains that must be pipelines -and chains that may be used as statements, -with the former being *pipeline chains* -and the latter being *statement chains*. - -In effect there is no user experience difference other than -not being able to use `return`/`continue`/`break`/`throw`/`exit` -at the ends of pipelines used in pipeline-specific scenarios. - -For example: - -```powershell -if ('Thing' && throw 'Bad') -{ - 'Hi' -} -``` - -will not recognise `throw` as a keyword. - -To match `if (throw 'Bad') { ... }`, the proposed behaviour is to parse it as a command, -so that invoking the above gives: - -```output -throw : The term 'throw' is not recognized as the name of a cmdlet, function, script file, or operable program. -Check the spelling of the name, or if a path was included, verify that the path is correct and try again. -At line:1 char:10 -+ if (1 && throw 'Bad') { 'Hi' } -+ ~~~~~ -+ CategoryInfo : ObjectNotFound: (throw:String) [], CommandNotFoundException -+ FullyQualifiedErrorId : CommandNotFoundException -``` - -These control flow statements would only be allowed *at the end* of chains because: - -- Control flow makes any invocation after the statement unreachable -- `return` and `throw` allow pipelines as subordinate expressions, - meaning it would be grammatically impossible to use those statements mid-chain. - ### Semantics #### Pipeline "success" @@ -1024,43 +914,27 @@ If `cmd1` fails but emits output, `$x` will hold that value. Errors will have the same semantics as the equivalent `cmd1; if ($?) { cmd2 }` syntax. -Terminating errors will terminate the entire pipeline chain. -But output already emitted by earlier pipelines in the chain -will be output by the chain before terminating: - -```powershell -try -{ - $x = cmd1 && throw "Bad" -} -catch -{ -} +Non-terminating and pipeline-terminating errors will cause the immediate pipeline to continue, +and the pipeline chain will evaluate as normal based on the value of `$?`. -$x # Has values from cmd1 -``` +Script-terminating errors will terminate the entire pipeline chain, +unless a `trap { continue }` is used. -Non-terminating errors will cause the immediate pipeline to continue, -and the pipeline chain will evaluate as normal based on the value of `$?`. +While output from a chain that later throws a script-terminating error +will be written to the pipeline, +it will not be assigned to a variable. +This is consistent with existing assignment semantics in PowerShell. ### Other notes #### New Abstract Syntax Tree (AST) types -Two new AST types are proposed: - -- `PipelineChainAst`, which refers to a pipeline chain - that can be used anywhere where a pipeline could be currently. - This inherits from `PipelineBaseAst`. -- `StatementChainAst`, which refers to a pipeline chain - used anywhere a statement could be currently. - This inherits from `StatementAst` +A single new AST leaf type is proposed, `PipelineChainAst`, +which refers to a pipeline chain +that can be used anywhere where a pipeline could be currently. +This inherits from `PipelineBaseAst`. -The separation of these ASTs means it remains impossible -to create certain constructions using AST constructors -that the parser would not allow. - -`ICustomAstVisitor2` and `AstVisitor2` would be extended to deal with these ASTs, +`ICustomAstVisitor2` and `AstVisitor2` would be extended to deal with this new AST type, and .NET Core 3's new default interface implementation feature would be leveraged to ensure this does not break things as previous syntactic introductions have been forced to. @@ -1207,10 +1081,16 @@ special behaviour would need to be defined for `return $expr &`. - Background operators become less useful with respect to chains unless their syntax is changed in a significant way. -### Not allowing control flow statements at the end of chains +### Allowing control flow statements at the end of chains + +An original addendum to the pipeline chain proposal +was to allow adding control flow statements at the end of pipeline chains: -The main proposal suggests adding control flow -statements to the end of pipeline chains. +- `cmd1 && return 'Done'` +- `cmd2 || throw 'Error'` +- `cmd3 && break` +- `cmd4 || continue` +- `cmd5 || exit 1` This introduces complications: @@ -1226,21 +1106,13 @@ This introduces complications: cmd1 && [return [cmd2 && cmd3]] ``` -- A `throw` can do the same, - but the proposal is to explicitly disallow this: +- A `throw` can do the same: ```powershell cmd1 && throw 'a' && 'b' ``` - ```output - At line:1 char:19 - + cmd1 && throw 'a' && 'b' - + ~~ - + Pipeline chain operators '&&' and '||' may not be used after 'throw'. - ``` - - This is proposed since `throw` stringifies its given value, + This is especially unhelpful since `throw` stringifies its given value, making a construction like the above much less useful than for `return`. This also complicates the grammar and the AST, since: @@ -1248,11 +1120,19 @@ This also complicates the grammar and the AST, since: - We have to complexify logic about stamtents vs pipelines and control flow logic - More AST types may be needed to prevent bad AST constructions -Not including control flow operators in pipelines would mean: +By keeping control flow statements directly out of pipeline chains: - The grammar is simplified - We only need one AST type -- There's no confusing embedding of chains over and under a `return` +- There's no confusing embedding of chains over and under a `return`/`throw`/`exit` + +Control flow statements can still be used by embedding them into a subexpression: + +- `cmd1 && $(return 'Done')` +- `cmd2 || $(throw 'Error')` +- `cmd3 && $(break)` +- `cmd4 || $(continue)` +- `cmd5 || $(exit 1)` #### Reasons against From b76e0792d29b6bea7a9d0d33e14d65aeab3002de Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Mon, 30 Sep 2019 11:17:24 -0700 Subject: [PATCH 13/15] Remove line pre-continuations --- 1-Draft/RFC0000-Chain-Operators.md | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/1-Draft/RFC0000-Chain-Operators.md b/1-Draft/RFC0000-Chain-Operators.md index cab679fd..b08c421e 100644 --- a/1-Draft/RFC0000-Chain-Operators.md +++ b/1-Draft/RFC0000-Chain-Operators.md @@ -732,8 +732,8 @@ statement: pipeline_chain: | pipeline - | pipeline_chain [newline] "&&" [newlines] pipeline - | pipeline_chain [newline] "||" [newlines] pipeline + | pipeline_chain "&&" [newlines] pipeline + | pipeline_chain "||" [newlines] pipeline ``` #### Optional pipeline chaining @@ -758,15 +758,6 @@ cmd1 && cmd3 ``` -Following on from the recent pipeline pre-continuation addition to PowerShell, -the following is also proposed: - -```powershell -cmd1 - && cmd2 - || cmd3 -``` - If the end of file is reached after a pipeline chain operator, incomplete input will be reported so that integrating tools will know to keep prompting for input. From 688996fba0c2b29eb1ae87fcaacc5dc679bc7ce5 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Mon, 30 Sep 2019 11:21:40 -0700 Subject: [PATCH 14/15] Update test link --- 1-Draft/RFC0000-Chain-Operators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/1-Draft/RFC0000-Chain-Operators.md b/1-Draft/RFC0000-Chain-Operators.md index b08c421e..edfeb368 100644 --- a/1-Draft/RFC0000-Chain-Operators.md +++ b/1-Draft/RFC0000-Chain-Operators.md @@ -96,7 +96,7 @@ providing a convenient way to manipulate control flow around command outcome rat ## User Experience -Also see: [test cases for implementation](https://github.com/PowerShell/PowerShell/blob/0b700828f22824c29eac70f8db1d2bf504b212d1/test/powershell/Language/Operators/PipelineChainOperator.Tests.ps1). +Also see: [test cases for implementation](https://github.com/PowerShell/PowerShell/blob/7457d526258988a632e8dada08486ee9785c6c3c/test/powershell/Language/Operators/PipelineChainOperator.Tests.ps1). Pipeline chain operators are intended to behave From 2c160c2773bf6510331af95728f0ced7b7b9a65f Mon Sep 17 00:00:00 2001 From: Joey Aiello Date: Mon, 14 Oct 2019 12:31:33 -0700 Subject: [PATCH 15/15] Prepare Pipeline Chain Operators (RFC0046) for acceptance --- .../RFC0046-Chain-Operators.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename 1-Draft/RFC0000-Chain-Operators.md => 4-Experimental-Accepted/RFC0046-Chain-Operators.md (95%) diff --git a/1-Draft/RFC0000-Chain-Operators.md b/4-Experimental-Accepted/RFC0046-Chain-Operators.md similarity index 95% rename from 1-Draft/RFC0000-Chain-Operators.md rename to 4-Experimental-Accepted/RFC0046-Chain-Operators.md index edfeb368..cb5b47e3 100644 --- a/1-Draft/RFC0000-Chain-Operators.md +++ b/4-Experimental-Accepted/RFC0046-Chain-Operators.md @@ -1,7 +1,7 @@ --- -RFC: +RFC: RFC0046 Author: Robert Holt -Status: Draft +Status: Experimental-Accepted Area: Pipeline Chain Operators Comments Due: 2019-07-12 0000 Plan to implement: Yes