private TokenParseResult TryParseNamedArgument(string argument, string argumentPrefix, NamedArgumentType namedArgType, out IReadOnlyList <ArgumentAndValue> parsedArgs)
        {
            var prefixLength = argumentPrefix.Length;

            Debug.Assert(argument.Length >= prefixLength);

            // Figure out where the argument name ends.
            var endIndex = argument.IndexOfAny(_argumentSet.Attribute.ArgumentValueSeparators, prefixLength);

            // Special case: check for '+' and '-' for booleans.
            if (endIndex < 0 && argument.Length >= 2)
            {
                var lastArgumentChar = argument[argument.Length - 1];
                if (ArgumentNameTerminators.Any(t => lastArgumentChar.Equals(t)))
                {
                    endIndex = argument.Length - 1;
                }
            }

            // If we don't have a separator or terminator, then consume the full string.
            if (endIndex < 0)
            {
                endIndex = argument.Length;
            }

            // Extract the argument name(s), separate from the prefix
            // or optional argument value.
            var options = argument.Substring(prefixLength, endIndex - prefixLength);

            // Extract the option argument (a.k.a. value), if there is one.
            string optionArgument = null;

            if (argument.Length > prefixLength + options.Length)
            {
                // If there's an argument value separator, then extract the value after the separator.
                if (_argumentSet.Attribute.ArgumentValueSeparators.Any(sep => argument[prefixLength + options.Length] == sep))
                {
                    optionArgument = argument.Substring(prefixLength + options.Length + 1);
                }

                // Otherwise, it might be a terminator; extract the rest of the string.
                else
                {
                    optionArgument = argument.Substring(prefixLength + options.Length);
                }
            }

            // Now try to figure out how many names are present.
            if (namedArgType == NamedArgumentType.ShortName &&
                (_argumentSet.Attribute.AllowMultipleShortNamesInOneToken || _argumentSet.Attribute.AllowElidingSeparatorAfterShortName))
            {
                Debug.Assert(_argumentSet.Attribute.ShortNamesAreOneCharacterLong);

                // Since short names are one character long, we parse them one at a
                // time, preparing for multiple arguments in this one token.
                var args = new List <ArgumentAndValue>();
                for (var index = 0; index < options.Length; ++index)
                {
                    // Try parsing it as a short name; bail immediately if we find an invalid
                    // one.
                    var possibleShortName = new string(options[index], 1);
                    if (!_argumentSet.TryGetNamedArgument(possibleShortName, out ArgumentDefinition arg))
                    {
                        parsedArgs = null;
                        return(TokenParseResult.UnknownNamedArgument(namedArgType, possibleShortName));
                    }

                    // If this parsed as a short name that takes a required option argument,
                    // and we didn't see an option argument, and we allow mushing together
                    // short names and their option arguments, then try parsing the rest of
                    // this token as an option argument.
                    var lastChar = index == options.Length - 1;
                    if (arg.RequiresOptionArgument &&
                        _argumentSet.Attribute.AllowElidingSeparatorAfterShortName &&
                        optionArgument == null &&
                        !lastChar)
                    {
                        optionArgument = options.Substring(index + 1);
                        index          = options.Length - 1;
                        lastChar       = true;
                    }

                    if (!_argumentSet.Attribute.AllowMultipleShortNamesInOneToken &&
                        args.Count > 0)
                    {
                        parsedArgs = null;
                        return(TokenParseResult.UnknownNamedArgument());
                    }

                    args.Add(new ArgumentAndValue
                    {
                        Arg   = arg,
                        Value = lastChar ? optionArgument : null
                    });
                }

                parsedArgs = args;
            }
            else
            {
                // Try to look up the argument by name.
                if (!_argumentSet.TryGetNamedArgument(options, out ArgumentDefinition arg))
                {
                    parsedArgs = null;
                    return(TokenParseResult.UnknownNamedArgument(namedArgType, options));
                }

                parsedArgs = new[] { new ArgumentAndValue
                                     {
                                         Arg   = arg,
                                         Value = optionArgument
                                     } };
            }

            // If the last named argument we saw in this token required an
            // option argument to go with it, then yield that information
            // so it can be used by the caller (e.g. in completion generation).
            var lastArg = parsedArgs.GetLastOrDefault();

            if (lastArg != null &&
                lastArg.Arg.RequiresOptionArgument &&
                string.IsNullOrEmpty(lastArg.Value))
            {
                return(TokenParseResult.RequiresOptionArgument(lastArg.Arg));
            }

            return(TokenParseResult.Ready);
        }
        private TokenParseResult TryParseNextNamedArgument(IReadOnlyList <string> args, int index, string longNameArgumentPrefix, string shortNameArgumentPrefix, object destination, out int argsConsumed)
        {
            argsConsumed = 1;

            var argument = args[index];
            var result   = TokenParseResult.UnknownNamedArgument();

            IReadOnlyList <ArgumentAndValue> parsedArgs = null;

            if (result.IsUnknown && longNameArgumentPrefix != null)
            {
                result = TryParseNamedArgument(argument, longNameArgumentPrefix, NamedArgumentType.LongName, out parsedArgs);
            }
            if (result.IsUnknown && shortNameArgumentPrefix != null)
            {
                result = TryParseNamedArgument(argument, shortNameArgumentPrefix, NamedArgumentType.ShortName, out parsedArgs);
            }

            // If our policy allows a named argument's value to be placed
            // in the following token, and if we're missing a required
            // value, and if there's at least one more token, then try
            // to parse the next token as the current argument's value.
            if (result.State == TokenParseResultState.RequiresOptionArgument &&
                _argumentSet.Attribute.AllowNamedArgumentValueAsSucceedingToken &&
                index + 1 < args.Count)
            {
                var lastParsedArg = parsedArgs.GetLast();

                Debug.Assert(lastParsedArg.Arg.RequiresOptionArgument);
                Debug.Assert(!lastParsedArg.Arg.TakesRestOfLine);
                Debug.Assert(string.IsNullOrEmpty(parsedArgs.GetLast().Value));

                ++index;
                ++argsConsumed;

                lastParsedArg.Value = args[index];
                result = TokenParseResult.Ready;
            }

            if (!result.IsReady)
            {
                ReportUnrecognizedArgument(result, argument);
                return(result);
            }

            foreach (var parsedArg in parsedArgs)
            {
                // TODO: Obviate the need to use string.Empty here.
                var argValue = parsedArg.Value ?? string.Empty;

                if (parsedArg.Arg.TakesRestOfLine)
                {
                    if (!parsedArg.Arg.TrySetRestOfLine(argValue, args.Skip(index + 1), destination))
                    {
                        result = TokenParseResult.FailedParsing;
                        continue;
                    }

                    argsConsumed = args.Count - index; // skip the rest of the line
                }
                else
                {
                    if (!TryParseAndStore(parsedArg.Arg, argValue, destination))
                    {
                        result = TokenParseResult.FailedParsing;
                        continue;
                    }
                }
            }

            return(result);
        }