Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions IMPLEMENTATION_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ priorities.
roles, ancestry, completeness, nullable decoded spans, compatibility
operators, and exact shared `Clause` identity. The strict DTO rejects
unknown fields and always requires unparseable projections to be empty.
The PowerShell manifest now owns all 341 entries and round-trips exactly;
The PowerShell manifest now owns all 361 entries and round-trips exactly;
explicit false/null assertions remain opt-in and generator-preserved.
- [x] Deliver the first Bash `$()` substitution slice for supported
simple-command arguments and redirect targets. Direct tests and corpus
Expand Down Expand Up @@ -303,12 +303,25 @@ priorities.
transfers, and occurrence-specific redirect values remain fail closed.
Next add the Netclaw approval matrix before calling the Bash consumer
integration complete.
- [ ] Complete PowerShell `$()` discovery in `foreach` expressions and add the
Netclaw approval-matrix cases. The simple-command slice is delivered for
- [ ] Complete PowerShell `foreach` value and state analysis and add the
Netclaw approval-matrix cases. The structural slice now preserves literal
scalar/array and executable iterator forms, recursively parses bodies,
projects iterator and loop-body ancestry, survives decoded wrappers, and
fails closed on dynamic iterables, iterator/body state or
command-resolution mutation, malformed boundaries, and depth overflow.
Loop-body and current-scope post-loop occurrences intentionally remain
incomplete; isolated child-host loops do not taint their outer continuation.
Before publishing exact or finite values, add an explicit PowerShell
initial-runspace contract and wrapper-state metadata: ambient typed,
read-only, scoped, alias, function, and module state can change binding
assignment and command resolution, while child hosts inherit no fresh
state guarantee unless their own invocation proves it. Expand the design
corpus for cardinality, mutation, separators, wrappers, redirects, and
transition caps before tasks 7.3-7.7. The simple-command slice is delivered for
ordinary, adjacent, quoted, here-string, redirect, standalone,
call-operator, dynamic-identity, and host-wrapper positions, with
current-scope state propagation and bounded expression rejection pinned
by the 341-entry executable corpus.
by the 361-entry executable corpus.
- [ ] Deliver Bash `for ... in` and PowerShell `foreach` as the first two
language-specific vertical slices, then extract only the shared analysis
proven by both implementations.
Expand Down
18 changes: 14 additions & 4 deletions SPEC.POWERSHELL.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,15 +360,25 @@ quoted_string := single_quoted | double_quoted

PowerShell retains a statement-versus-pipeline distinction. `foreach` is a
language keyword only at statement position when followed by `(`;
`Get-ChildItem | foreach { ... }` remains command/alias syntax and its ordinary
script-block argument remains opaque.
`Get-ChildItem | foreach { ... }` and
`Write-Output x | foreach ($_)` remain command/alias syntax. An ordinary
script-block or bounded non-executing parenthesized argument remains opaque; it
is not reinterpreted as a loop body. `&&` and `||` join pipelines, not
control-flow statements, so
they cannot precede or follow `foreach`; `;` and newline remain legal statement
terminators.

```text
pwsh_script(stop) := pwsh_statement (statement_sep pwsh_statement)*
pwsh_script(stop) := pwsh_statement (statement_terminator pwsh_statement)*
pwsh_statement := pwsh_foreach
| pwsh_while
| pwsh_if
| pwsh_pipeline
| pwsh_and_or

statement_terminator := ";" | NEWLINE
pwsh_and_or := pwsh_pipeline
(("&&" | "||") pwsh_pipeline)*
pwsh_pipeline := pipeline_element ("|" pipeline_element)*

pwsh_foreach := "foreach" "(" variable "in" foreach_expression ")"
script_block_body
Expand Down
14 changes: 11 additions & 3 deletions openspec/changes/v0-3-structured-shell-analysis/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -1297,14 +1297,22 @@ evidence.
PowerShell retains its statement-versus-pipeline distinction and contextual
keyword rules. In particular, `foreach` is a language keyword only at a
statement position when followed by `(`; `Get-ChildItem | foreach { ... }`
continues to treat `foreach` as command or alias syntax.
and `Write-Output x | foreach ($_)` continue to treat `foreach` as command or
alias syntax when the parenthesized argument is proved non-executing. `&&` and
`||` join pipelines rather than arbitrary control-flow
statements, so only `;` and newline terminate a statement around `foreach`.

```text
pwsh_script(stop) := pwsh_statement (statement_sep pwsh_statement)*
pwsh_script(stop) := pwsh_statement (statement_terminator pwsh_statement)*
pwsh_statement := pwsh_foreach
| pwsh_while
| pwsh_if
| pwsh_pipeline
| pwsh_and_or

statement_terminator := ";" | NEWLINE
pwsh_and_or := pwsh_pipeline
(("&&" | "||") pwsh_pipeline)*
pwsh_pipeline := pipeline_element ("|" pipeline_element)*

pwsh_foreach := "foreach" "(" variable "in" foreach_expression ")"
script_block_body
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,17 @@ without treating the script block as one opaque argument.
- **THEN** the iterable preserves the two literal array elements
- **THEN** the body contains one simple command for `Remove-Item`

#### Scenario: Foreach remains contextual in a pipeline command slot
- **WHEN** PowerShell parses `Write-Output x | foreach ($_)`
- **THEN** `foreach` remains an ordinary command or alias pipeline stage
- **THEN** no loop node is invented
- **THEN** the parenthesized argument remains opaque

#### Scenario: Foreach requires a statement boundary
- **WHEN** PowerShell parses a `foreach` statement adjacent to another command
- **THEN** `;` or newline may terminate the statement
- **THEN** `|`, `&&`, and `||` do not admit the `foreach` statement as a pipeline element

### Requirement: Shared loop structure does not erase shell grammar
`ForEachSyntax` SHALL preserve the normalized binding name, the authored
binding source, the raw iterable source fragment, commands discovered in the
Expand Down
13 changes: 11 additions & 2 deletions openspec/changes/v0-3-structured-shell-analysis/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,17 @@

## 7. PowerShell Foreach Vertical Slice

- [ ] 7.1 Parse PowerShell `foreach` with literal scalar and array iterables into the locked structural nodes.
- [ ] 7.2 Emit iterator and loop-body occurrences plus conservative compatibility clauses.
- [x] 7.1 Parse PowerShell `foreach` with literal scalar and array iterables into the locked structural nodes.
- Direct tests pin exact spans, nested structure, decoded-wrapper nullable
spans, contextual alias collisions, statement boundaries, malformed forms,
and the shared structural-depth cap.
- [x] 7.2 Emit iterator and loop-body occurrences plus conservative compatibility clauses.
- Iterator pipelines and direct `$()` are recursively visible with authored
roles and ancestry. Every loop-body occurrence remains incomplete until
tasks 7.3 and 7.4 prove binding values and runspace state; recognized
iterator/body state or command-resolution mutation and dynamic invocation
fail atomically. Current-scope continuations after a loop remain incomplete;
isolated child-host loops do not taint their outer continuation.
- [ ] 7.3 Derive exact and finite string domains without treating pipeline objects as literal strings.
- [ ] 7.4 Propagate PowerShell scope and location state according to the locked statement semantics.
- [ ] 7.5 Cover aliases, cmdlets, native commands, nested loops, pipelines, script blocks, and wrapper boundaries.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,11 +283,10 @@ private static bool TryDetectKeywordAnomaly(IReadOnlyList<PwshToken> tokens, out
{
// `foreach (` is the loop keyword; `foreach {` is the
// ForEach-Object alias (§6.3 collision rule).
// The structural coordinator owns the supported statement
// form and rejects malformed headers atomically.
if (NextSignificantIsOpenParen(tokens, i))
{
reason = "control-flow keyword 'foreach' is not supported in v0.2";
return true;
}
continue;
}
else
{
Expand Down
Loading