@@ -146,14 +146,32 @@ module private ArgParserRuntime_BoolNegation =
146146 /// ` --rest value ` ; the key text is recorded as spelled.
147147 | KeySpaced of key : string
148148
149+ /// One value belonging to the positional stream. Scanning does not decide which sink
150+ /// receives it: the token's spelling determines only the * candidate* sinks, and the actual
151+ /// consumer is resolved after case selection. (For today's schemas, with at most one sink,
152+ /// the candidate set is that sink alone.)
153+ type PositionalEvent =
154+ {
155+ Value : string
156+ /// True for tokens which appeared after the ` -- ` separator.
157+ AfterSeparator : bool
158+ /// How the value was spelled; preserved exactly, for diagnostics and lossless
159+ /// reconstruction of argv.
160+ Form : PositionalForm
161+ /// The sinks which could consume this token: every sink for a bare token
162+ /// (including everything after ` -- ` , and flag-like tokens collected in Collect
163+ /// mode), the claimants of the key for a keyed one.
164+ Candidates : Set < int >
165+ }
166+
149167 /// The scan phase emits an ordered log of these; the typed layer folds over the log in order,
150168 /// so that e.g. conversion errors interleave with structural errors in argv order.
151169 [<RequireQualifiedAccess>]
152170 type ScanEvent =
153171 | Occurrence of ErasedOccurrence
154- /// A value routed to the positional sink (or to the leftover-args accumulator, for a
155- /// schema with no sink). ` afterSeparator ` is true for tokens which appeared after ` -- ` .
156- | Positional of value : string * afterSeparator : bool * form : PositionalForm
172+ /// A value belonging to the positional stream (or to the leftover-args accumulator,
173+ /// for a schema with no sink).
174+ | Positional of PositionalEvent
157175 | Error of ScanError
158176 /// A ` --help ` -shaped token was seen in key position; the token itself is recorded (the
159177 /// match is case-insensitive, so it may be e.g. "--HELP").
@@ -178,9 +196,10 @@ module private ArgParserRuntime_BoolNegation =
178196 type private ScanState =
179197 | AwaitingKey
180198 | AwaitingValue of leaf : ( ErasedLeaf * bool ) option * source : string
181- /// The positional sink's own key (` --rest ` ) was seen; the next token is its value,
182- /// consumed greedily (keyed positionals always take exactly one value).
183- | AwaitingPositionalValue of source : string
199+ /// A positional sink's own key (` --rest ` ) was seen; the next token is its value,
200+ /// consumed greedily (keyed positionals always take exactly one value). ` candidates `
201+ /// are the sinks claiming the key.
202+ | AwaitingPositionalValue of candidates : Set<int> * source : string
184203
185204 /// Match a full ` --key ` token (value part already split off) against the leaf table.
186205 /// Returns the leaf and whether the match was via the negated ` --no- ` form.
@@ -236,13 +255,29 @@ module private ArgParserRuntime_BoolNegation =
236255 | ErasedFlagLikeBehaviour.Reject -> false
237256 )
238257
239- /// Does this full ` --key ` token (value part already split off) name a positional sink?
240- let isPositionalKey ( key : string ) : bool =
258+ /// Every sink: the candidate set of a bare positional token.
259+ let allSinks : Set < int > =
260+ schema.Positionals |> List.map ( fun p -> p.Id) |> Set.ofList
261+
262+ /// The sinks claiming this full ` --key ` token (value part already split off); empty
263+ /// when the token does not name a positional sink at all.
264+ let positionalClaimants ( key : string ) : Set < int > =
241265 schema.Positionals
242- |> List.exists ( fun p ->
266+ |> List.filter ( fun p ->
243267 p.Forms
244268 |> List.exists ( fun form -> String.Equals ( key, " --" + form, StringComparison.OrdinalIgnoreCase))
245269 )
270+ |> List.map ( fun p -> p.Id)
271+ |> Set.ofList
272+
273+ let bareToken ( value : string ) ( afterSeparator : bool ) : ScanEvent =
274+ ScanEvent.Positional
275+ {
276+ Value = value
277+ AfterSeparator = afterSeparator
278+ Form = PositionalForm.Bare
279+ Candidates = allSinks
280+ }
246281
247282 /// Resolve a pending key which will receive no value (end of input, or ` -- ` next).
248283 let resolvePending ( state : ScanState ) : ScanEvent list =
@@ -262,22 +297,19 @@ module private ArgParserRuntime_BoolNegation =
262297 ]
263298 | ErasedArity.One -> [ ScanEvent.Error ( ScanError.TrailingKeyNoValue source) ]
264299 | ScanState.AwaitingValue ( None, source) -> [ ScanEvent.Error ( ScanError.TrailingKeyNoValue source) ]
265- | ScanState.AwaitingPositionalValue source -> [ ScanEvent.Error ( ScanError.TrailingKeyNoValue source) ]
300+ | ScanState.AwaitingPositionalValue (_, source) -> [ ScanEvent.Error ( ScanError.TrailingKeyNoValue source) ]
266301
267302 let rec go ( state : ScanState ) ( acc : ScanEvent list ) ( args : string list ) : ScanEvent list =
268303 match args with
269304 | [] -> List.rev acc @ resolvePending state
270305 | " --" :: rest ->
271- let positionals =
272- rest
273- |> List.map ( fun token -> ScanEvent.Positional ( token, true , PositionalForm.Bare))
274-
306+ let positionals = rest |> List.map ( fun token -> bareToken token true )
275307 List.rev acc @ resolvePending state @ ( ScanEvent.Separator :: positionals)
276308 | arg :: rest ->
277309 match state with
278310 | ScanState.AwaitingKey ->
279311 if not ( arg.StartsWith ( " --" , StringComparison.Ordinal)) then
280- go ScanState.AwaitingKey ( ScanEvent.Positional ( arg, false , PositionalForm.Bare ) :: acc) rest
312+ go ScanState.AwaitingKey ( bareToken arg false :: acc) rest
281313 elif String.Equals ( arg, " --help" , StringComparison.OrdinalIgnoreCase) then
282314 go ScanState.AwaitingKey ( ScanEvent.Help arg :: acc) rest
283315 else
@@ -299,16 +331,21 @@ module private ArgParserRuntime_BoolNegation =
299331
300332 go ScanState.AwaitingKey ( ScanEvent.Occurrence occurrence :: acc) rest
301333 | None ->
302- if isPositionalKey key then
303- go
304- ScanState.AwaitingKey
305- ( ScanEvent.Positional ( value, false , PositionalForm.KeyEquals key) :: acc)
306- rest
334+ let claimants = positionalClaimants key
335+
336+ if not ( Set.isEmpty claimants) then
337+ let event =
338+ ScanEvent.Positional
339+ {
340+ Value = value
341+ AfterSeparator = false
342+ Form = PositionalForm.KeyEquals key
343+ Candidates = claimants
344+ }
345+
346+ go ScanState.AwaitingKey ( event :: acc) rest
307347 elif collectFlagLike then
308- go
309- ScanState.AwaitingKey
310- ( ScanEvent.Positional ( arg, false , PositionalForm.Bare) :: acc)
311- rest
348+ go ScanState.AwaitingKey ( bareToken arg false :: acc) rest
312349 else
313350 go
314351 ScanState.AwaitingKey
@@ -318,8 +355,10 @@ module private ArgParserRuntime_BoolNegation =
318355 match matchLeaf schema.Leaves arg with
319356 | Some matched -> go ( ScanState.AwaitingValue ( Some matched, arg)) acc rest
320357 | None ->
321- if isPositionalKey arg then
322- go ( ScanState.AwaitingPositionalValue arg) acc rest
358+ let claimants = positionalClaimants arg
359+
360+ if not ( Set.isEmpty claimants) then
361+ go ( ScanState.AwaitingPositionalValue ( claimants, arg)) acc rest
323362 else
324363 go ( ScanState.AwaitingValue ( None, arg)) acc rest
325364 | ScanState.AwaitingValue ( Some ( leaf, negated), source) ->
@@ -349,17 +388,20 @@ module private ArgParserRuntime_BoolNegation =
349388 go ScanState.AwaitingKey ( ScanEvent.Occurrence occurrence :: acc) ( arg :: rest)
350389 | ScanState.AwaitingValue ( None, source) ->
351390 if collectFlagLike then
352- go
353- ScanState.AwaitingKey
354- ( ScanEvent.Positional ( source, false , PositionalForm.Bare) :: acc)
355- ( arg :: rest)
391+ go ScanState.AwaitingKey ( bareToken source false :: acc) ( arg :: rest)
356392 else
357393 go ScanState.AwaitingKey ( ScanEvent.Error ( ScanError.UnknownKey source) :: acc) ( arg :: rest)
358- | ScanState.AwaitingPositionalValue source ->
359- go
360- ScanState.AwaitingKey
361- ( ScanEvent.Positional ( arg, false , PositionalForm.KeySpaced source) :: acc)
362- rest
394+ | ScanState.AwaitingPositionalValue ( candidates, source) ->
395+ let event =
396+ ScanEvent.Positional
397+ {
398+ Value = arg
399+ AfterSeparator = false
400+ Form = PositionalForm.KeySpaced source
401+ Candidates = candidates
402+ }
403+
404+ go ScanState.AwaitingKey ( event :: acc) rest
363405
364406 go ScanState.AwaitingKey [] args
365407
@@ -594,7 +636,7 @@ module private ArgParserRuntime_BoolNegation =
594636 events
595637 |> List.choose ( fun event ->
596638 match event with
597- | ScanEvent.Positional ( value , _, _) -> Some value
639+ | ScanEvent.Positional positional -> Some positional.Value
598640 | _ -> None
599641 )
600642
@@ -1083,11 +1125,11 @@ module private ArgParserRuntime_BoolNegation =
10831125 | None -> stored.Add occurrence.LeafId |> ignore< bool>
10841126
10851127 consume rest
1086- | ScanEvent.Positional ( value , afterSeparator , _) ->
1128+ | ScanEvent.Positional positional ->
10871129 match schema.Positionals with
10881130 | [] -> consume rest
10891131 | _ :: _ ->
1090- match callbacks.StorePositional value afterSeparator with
1132+ match callbacks.StorePositional positional.Value positional.AfterSeparator with
10911133 | Some error -> errors.Add error
10921134 | None -> ()
10931135
0 commit comments