@@ -75,9 +75,11 @@ syntax (§5) are all PowerShell 7 semantics. The `pwsh` validation oracle
7575
7676## 2. Public API Surface
7777
78- The shared interface, AST records, and enums are defined in ** ` SPEC.md ` §2**
79- and are unchanged. PowerShell adds the following to namespace
80- ` ShellSyntaxTree ` ; everything else is internal.
78+ The shared interface, AST records, and enums are defined in ** ` SPEC.md ` §2** .
79+ The additive ` Clause.Elements ` , ` ClauseElement ` , and ` ClauseElementRole `
80+ provenance surface applies identically to both parsers. PowerShell adds the
81+ following parser types to namespace ` ShellSyntaxTree ` ; everything else is
82+ internal.
8183
8284``` csharp
8385namespace ShellSyntaxTree ;
@@ -117,14 +119,17 @@ public sealed class PwshParser : IShellParser
117119}
118120```
119121
120- Two shared types gain a change (see §3):
122+ The shared v0.2 AST gains the following changes (see §3):
121123
122124- ` VerbChain ` gains an additive ` string? CanonicalVerb ` field.
123125- ` VerbChain ` gains an additive ` bool IsDynamic ` field.
126+ - ` Clause ` gains the additive ` Elements ` provenance view shared with Bash;
127+ ` ClauseElement ` and ` ClauseElementRole ` define its entries.
124128- ` Clause.IsBashCWrapped ` is renamed ` Clause.IsCommandStringWrapped ` .
125129
126130** Versioning.** ` PwshParser ` , ` PwshParserOptions ` , ` ShellParserOptions ` ,
127- ` VerbChain.CanonicalVerb ` , and ` VerbChain.IsDynamic ` are additive. The
131+ ` VerbChain.CanonicalVerb ` , ` VerbChain.IsDynamic ` , ` Clause.Elements ` ,
132+ ` ClauseElement ` , and ` ClauseElementRole ` are additive. The
128133` Clause ` field rename and the ` BashParserOptions ` reparenting are
129134** breaking** ; ` SPEC.md ` Appendix A permits a breaking AST change on a ` 0.x `
130135minor bump when ` RELEASE_NOTES.md ` carries the old→new mapping and Netclaw is
@@ -136,9 +141,47 @@ never throws on a well-formed string, exactly like `BashParser`.
136141
137142## 3. AST Reference
138143
139- The AST records and enums are defined in ** ` SPEC.md ` §3** and are emitted
140- unchanged by ` PwshParser ` — a consumer walks a PowerShell ` ParsedCommand `
141- exactly as it walks a bash one. Two deltas:
144+ The AST records and enums are defined in ** ` SPEC.md ` §3** and are emitted by
145+ ` PwshParser ` under the same shared contract — a consumer walks a PowerShell
146+ ` ParsedCommand ` exactly as it walks a bash one. PowerShell has the following
147+ deltas and provenance rules:
148+
149+ ### ` Clause.Elements ` PowerShell rules
150+
151+ PowerShell parameters, native options, quoted/here-string values, and opaque
152+ dynamic regions each occupy their authored position in ` Clause.Elements ` .
153+ The leading call operator in ` & command ` and grouping parentheses are shell
154+ syntax rather than verb/argument/redirect leaves and do not appear.
155+
156+ Inline parameter forms remain one source element. For ` -Path:C:\repo ` , the
157+ element's ` Raw ` and ` Value ` describe the full parameter token while ` Kind ` ,
158+ ` IsPath ` , and ` Resolved ` describe the bound ` C:\repo ` value. Native
159+ ` --flag=value ` follows the same rule as Bash. Backtick escapes in an inline
160+ bound value are decoded before ` Value ` and path metadata are produced. Adjacent
161+ native fragments such as ` --data='@C:\payload file' ` form one element because
162+ PowerShell passes them to the executable as one argument. The complete
163+ contiguous fragment run is consumed. Resolver-sensitive syntax inside a
164+ single-quoted fragment mixed with expandable fragments safe-fails as
165+ ` DynamicSkip ` rather than being expanded.
166+
167+ Clauses recursively surfaced from ` pwsh -Command ` and
168+ ` pwsh -EncodedCommand ` retain inner ` Raw ` and ` Value ` but have null
169+ ` SourceStart ` and ` SourceLength ` : quote/backtick processing, script-block
170+ stripping, and base64 decoding do not provide a generally exact map into the
171+ outer ` ParsedCommand.Source ` .
172+ An outer redirect authored after a ` pwsh -Command ` or ` -EncodedCommand `
173+ payload remains on the surfaced wrapped clause with its exact outer source
174+ span; only decoded inner elements have null spans.
175+
176+ ` ClauseElement.Role ` and ` PrecedingVerbElementCount ` mirror the shared greedy
177+ native verb projection. They are AST coordinates, not native-executable
178+ semantic boundaries. A PowerShell consumer applies executable-specific grammar
179+ to the complete authored element order exactly as a Bash consumer does.
180+
181+ PowerShell cmdlet names, aliases, and parameter names remain
182+ case-insensitive. Native option spelling is ordinal and reuses the shared Bash
183+ native tables unchanged: PowerShell does not make a native executable's ` -c `
184+ and ` -C ` options equivalent.
142185
143186### ` VerbChain.CanonicalVerb ` (new, additive)
144187
@@ -672,11 +715,21 @@ positionals are paths," exactly as `SPEC.md` §7.
672715
673716Native commands reuse the bash per-verb rules table verbatim — ` git ` ,
674717` curl ` , ` tar ` , etc. behave identically to ` SPEC.md ` §7 (` curl ` / ` wget ` :
675- the first positional is a URL; the ` -o ` / ` -O ` value is a path). This
718+ the first positional is a URL; curl ` -o ` / ` -D ` values and Wget ` -o ` / ` -O `
719+ values are paths, while curl ` -d ` data is non-path unless ` @file ` requests a
720+ file read; ` @- ` denotes stdin). Tar ` -F ` / ` --info-script ` /
721+ ` --new-volume-script ` values execute commands and therefore safe-fail as
722+ ` DynamicSkip ` , not paths. This
676723includes hyphenated option names and the bash ` --flag=value ` split: the
677724flag and value surface as separate args, and a curated flag's value receives
678725the same path classification in both parsers. Native ` --flag:value ` has no
679- cmdlet-binding semantics and remains verbatim.
726+ cmdlet-binding semantics and remains verbatim. PowerShell still owns outer
727+ tokenization: spaced curl operands beginning with ` @ ` should be quoted because
728+ ` @name ` is splatting and bare ` @- ` is a parse error. Use forms such as
729+ ` -d "@request.json" ` / ` -d "@-" ` , or bind a file inline as
730+ ` --data=@request.json ` , so the native command receives one value. An equals
731+ prefix adjacent to a quoted value, such as ` --data='@C:\payload file' ` , is
732+ also one native argument and one clause element.
680733
681734---
682735
@@ -853,8 +906,8 @@ not just one quoted token. The parser handles all three real forms:
853906- ** Script block** — ` pwsh -Command { Remove-Item C:\tmp\x } ` . Parse the
854907 script-block * interior* (braces stripped) as a fresh ` ParsedCommand ` .
855908- ** Bare / multi-token** — ` pwsh -Command Remove-Item C:\tmp\x ` . Take the
856- verbatim source slice from the first token after ` -Command ` to the end of
857- the statement and parse * that* as a fresh ` ParsedCommand ` .
909+ verbatim source slice from the first token after ` -Command ` through the last
910+ command token and parse * that* as a fresh ` ParsedCommand ` .
858911
859912In every form the inner clauses surface inline, each with
860913` IsCommandStringWrapped = true ` . ** Not** recognizing the bare/multi-token
@@ -865,6 +918,12 @@ yields `IsUnparseable = true`** (e.g. a `-Command` payload that decodes to a
865918control-flow script), sets the outer ` ParsedCommand.IsUnparseable = true ` so
866919the whole command routes to safe-fail (` SPEC.md ` §10).
867920
921+ A terminal redirect belongs to the outer PowerShell invocation, not the child
922+ command string. The parser appends each such redirect to the last surfaced
923+ inner clause's ` Redirects ` and ` Elements ` ; its outer source span remains exact.
924+ Non-redirect arguments after a quoted, script-block, colon-bound, or encoded
925+ payload are not modeled and set ` IsUnparseable=true ` rather than disappearing.
926+
868927` pwsh -File script.ps1 ` is ** not** recursion — the file content is not
869928available to the parser. It parses as an ordinary clause with ` script.ps1 `
870929as a path arg.
@@ -911,6 +970,9 @@ leaves the required payload missing. Dynamic inline values remain opaque.
911970For a static payload, the parser consumes the outer expression clause and
912971surfaces the inner clauses inline with ` IsCommandStringWrapped = true ` . The
913972first inner clause takes the operator that preceded the outer expression.
973+ Surfaced ` Clause.Elements ` retain their inner raw and decoded values but have
974+ null source spans because their offsets cannot be mapped exactly into the
975+ outer ` ParsedCommand.Source ` .
914976The parse increments the same depth counter used by ` pwsh -Command ` and
915977` -EncodedCommand ` , and the payload passes through the same 64 KiB input cap.
916978
@@ -924,7 +986,10 @@ The parser never evaluates variables, interpolation, concatenation,
924986subexpressions, script blocks, arrays, or other computed expressions. When a
925987direct computed payload has a source expression, the outer expression clause
926988remains and the entire payload source slice becomes one
927- ` Arg { Kind=DynamicSkip, IsPath=false, Resolved=null } ` . Pipeline input,
989+ ` Arg { Kind=DynamicSkip, IsPath=false, Resolved=null } ` . Its authored
990+ ` Clause.Elements ` retain the expression verb, an optional separate ` -Command `
991+ parameter, and one source-aligned ` DynamicSkip ` payload region. An inline form
992+ such as ` -Command:$code ` remains one authored parameter element. Pipeline input,
928993missing payloads, and ambiguous parameter binding set
929994` ParsedCommand.IsUnparseable = true ` ; an incoming pipeline is dynamic even
930995when an explicit literal argument also appears. These rules prevent a clean,
0 commit comments