@@ -290,6 +290,25 @@ struct LenientParser {
290290 }
291291 }
292292
293+ /// Helper function to check if there's a terminator between the current option and target origin
294+ private func hasTerminatorBetween(
295+ _ originElement: InputOrigin . Element ,
296+ _ targetOrigin: InputOrigin . Element
297+ ) -> Bool {
298+ guard case . argumentIndex( let currentIndex) = originElement,
299+ case . argumentIndex( let targetIndex) = targetOrigin
300+ else { return false }
301+
302+ // Check if there's a terminator between current position and target position
303+ let terminatorIndex = inputArguments. elements. firstIndex { element in
304+ element. isTerminator
305+ && element. index. inputIndex > currentIndex. inputIndex
306+ && element. index. inputIndex < targetIndex. inputIndex
307+ }
308+
309+ return terminatorIndex != nil
310+ }
311+
293312 mutating func parseValue(
294313 _ argument: ArgumentDefinition ,
295314 _ parsed: ParsedArgument ,
@@ -315,10 +334,11 @@ struct LenientParser {
315334 try update ( origins, parsed. name, String ( value) , & result)
316335 usedOrigins. formUnion ( origins)
317336 } else if let ( origin2, value) = inputArguments. popNextElementIfValue (
318- after: originElement)
337+ after: originElement) ,
338+ !hasTerminatorBetween( originElement, origin2)
319339 {
320340 // Use `popNextElementIfValue(after:)` to handle cases where short option
321- // labels are combined
341+ // labels are combined, but only if there's no terminator between them
322342 let origins = origin. inserting ( origin2)
323343 try update ( origins, parsed. name, value, & result)
324344 usedOrigins. formUnion ( origins)
@@ -341,10 +361,11 @@ struct LenientParser {
341361 try update ( origins, parsed. name, String ( value) , & result)
342362 usedOrigins. formUnion ( origins)
343363 } else if let ( origin2, value) = inputArguments. popNextValue (
344- after: originElement)
364+ after: originElement) ,
365+ !hasTerminatorBetween( originElement, origin2)
345366 {
346367 // Use `popNext(after:)` to handle cases where short option
347- // labels are combined
368+ // labels are combined, but only if there's no terminator between them
348369 let origins = origin. inserting ( origin2)
349370 try update ( origins, parsed. name, value, & result)
350371 usedOrigins. formUnion ( origins)
@@ -366,16 +387,16 @@ struct LenientParser {
366387 let origins = origin. inserting ( origin2)
367388 try update ( origins, parsed. name, String ( value) , & result)
368389 usedOrigins. formUnion ( origins)
369- } else {
370- guard
371- let ( origin2, value) = inputArguments. popNextElementAsValue (
372- after: originElement)
373- else {
374- throw errorForMissingValue ( originElement, parsed)
375- }
390+ } else if let ( origin2, value) = inputArguments. popNextElementAsValue (
391+ after: originElement) ,
392+ !hasTerminatorBetween( originElement, origin2)
393+ {
394+ // Only consume if there's no terminator between option and value
376395 let origins = origin. inserting ( origin2)
377396 try update ( origins, parsed. name, value, & result)
378397 usedOrigins. formUnion ( origins)
398+ } else {
399+ throw errorForMissingValue ( originElement, parsed)
379400 }
380401
381402 case . allRemainingInput:
@@ -468,112 +489,20 @@ struct LenientParser {
468489 _ result: inout ParsedValues ,
469490 _ usedOrigins: inout InputOrigin
470491 ) throws {
471- let origin = InputOrigin ( elements: [ originElement] )
472-
473- // Helper function to check if there's a terminator between the current option and potential values
474- func hasTerminatorBefore( _ targetOrigin: InputOrigin . Element ) -> Bool {
475- guard case . argumentIndex( let currentIndex) = originElement,
476- case . argumentIndex( let targetIndex) = targetOrigin
477- else { return false }
478-
479- // Check if there's a terminator between current position and target position
480- let terminatorIndex = inputArguments. elements. firstIndex { element in
481- element. isTerminator
482- && element. index. inputIndex > currentIndex. inputIndex
483- && element. index. inputIndex < targetIndex. inputIndex
484- }
485-
486- return terminatorIndex != nil
487- }
488-
489- // Try to find a value using the same logic as parseValue, but don't throw if missing
490- switch argument. parsingStrategy {
491- case . default:
492- // Try to get a value for this option
493- if let value = parsed. value {
494- // This was `--foo=bar` style:
495- try unaryHandler ( origin, parsed. name, value, & result)
496- usedOrigins. formUnion ( origin)
497- } else if argument. allowsJoinedValue,
498- let ( origin2, value) = inputArguments. extractJoinedElement (
499- at: originElement)
500- {
501- // Found a joined argument
502- let origins = origin. inserting ( origin2)
503- try unaryHandler ( origins, parsed. name, String ( value) , & result)
504- usedOrigins. formUnion ( origins)
505- } else if let ( origin2, value) = inputArguments. popNextElementIfValue (
506- after: originElement) ,
507- !hasTerminatorBefore( origin2)
508- {
509- // Use `popNextElementIfValue(after:)` to handle cases where short option
510- // labels are combined - only consume if it's actually a value, not another flag
511- // and there's no terminator between the option and the value
512- let origins = origin. inserting ( origin2)
513- try unaryHandler ( origins, parsed. name, value, & result)
514- usedOrigins. formUnion ( origins)
515- } else {
516- // No value found or terminator blocks access - use as flag
517- try nullaryHandler ( origin, parsed. name, & result)
518- usedOrigins. formUnion ( origin)
519- }
520-
521- case . scanningForValue:
522- // Similar to default, but more aggressive about finding values
523- if let value = parsed. value {
524- try unaryHandler ( origin, parsed. name, value, & result)
525- usedOrigins. formUnion ( origin)
526- } else if argument. allowsJoinedValue,
527- let ( origin2, value) = inputArguments. extractJoinedElement (
528- at: originElement)
529- {
530- let origins = origin. inserting ( origin2)
531- try unaryHandler ( origins, parsed. name, String ( value) , & result)
532- usedOrigins. formUnion ( origins)
533- } else if let ( origin2, value) = inputArguments. popNextValue (
534- after: originElement) ,
535- !hasTerminatorBefore( origin2)
536- {
537- // Only consume if there's no terminator between option and value
538- let origins = origin. inserting ( origin2)
539- try unaryHandler ( origins, parsed. name, value, & result)
540- usedOrigins. formUnion ( origins)
541- } else {
542- // No value found or terminator blocks access - use as flag
543- try nullaryHandler ( origin, parsed. name, & result)
544- usedOrigins. formUnion ( origin)
545- }
546-
547- case . unconditional:
548- // Use an attached value if it exists, otherwise try to consume next element
549- if let value = parsed. value {
550- try unaryHandler ( origin, parsed. name, value, & result)
551- usedOrigins. formUnion ( origin)
552- } else if argument. allowsJoinedValue,
553- let ( origin2, value) = inputArguments. extractJoinedElement (
554- at: originElement)
555- {
556- let origins = origin. inserting ( origin2)
557- try unaryHandler ( origins, parsed. name, String ( value) , & result)
558- usedOrigins. formUnion ( origins)
559- } else if let ( origin2, value) = inputArguments. popNextElementAsValue (
560- after: originElement) ,
561- !hasTerminatorBefore( origin2)
562- {
563- // Only consume if there's no terminator between option and value
564- let origins = origin. inserting ( origin2)
565- try unaryHandler ( origins, parsed. name, value, & result)
566- usedOrigins. formUnion ( origins)
567- } else {
568- // No value found or terminator blocks access - use as flag
492+ do {
493+ // Try to parse as a unary value first using the main parseValue logic
494+ try parseValue ( argument, parsed, originElement, unaryHandler, & result, & usedOrigins)
495+ } catch let error as ParserError {
496+ switch error {
497+ case . missingValueForOption, . missingValueOrUnknownCompositeOption:
498+ // Fall back to flag behavior when no value is available
499+ let origin = InputOrigin ( elements: [ originElement] )
569500 try nullaryHandler ( origin, parsed. name, & result)
570501 usedOrigins. formUnion ( origin)
502+ default :
503+ // Re-throw other parser errors
504+ throw error
571505 }
572-
573- case . upToNextOption, . allRemainingInput, . postTerminator, . allUnrecognized:
574- // For other parsing strategies, fall back to flag behavior for now
575- try nullaryHandler ( origin, parsed. name, & result)
576- usedOrigins. formUnion ( origin)
577506 }
578507 }
579508
0 commit comments