Currently, when handling parsing errors, there is no way to know what parameter caused the error. This change makes the CommandParser create the parsing error with the current parameter info when ParseAsync() fails. It is then available through the ErrorParameter of the ParseResult.
199 lines
7.9 KiB
C#
199 lines
7.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Discord.Commands
|
|
{
|
|
internal static class CommandParser
|
|
{
|
|
private enum ParserPart
|
|
{
|
|
None,
|
|
Parameter,
|
|
QuotedParameter
|
|
}
|
|
public static async Task<ParseResult> ParseArgsAsync(CommandInfo command, ICommandContext context, bool ignoreExtraArgs, IServiceProvider services, string input, int startPos, IReadOnlyDictionary<char, char> aliasMap)
|
|
{
|
|
ParameterInfo curParam = null;
|
|
StringBuilder argBuilder = new StringBuilder(input.Length);
|
|
int endPos = input.Length;
|
|
var curPart = ParserPart.None;
|
|
int lastArgEndPos = int.MinValue;
|
|
var argList = ImmutableArray.CreateBuilder<TypeReaderResult>();
|
|
var paramList = ImmutableArray.CreateBuilder<TypeReaderResult>();
|
|
bool isEscaping = false;
|
|
char c, matchQuote = '\0';
|
|
|
|
// local helper functions
|
|
bool IsOpenQuote(IReadOnlyDictionary<char, char> dict, char ch)
|
|
{
|
|
// return if the key is contained in the dictionary if it is populated
|
|
if (dict.Count != 0)
|
|
return dict.ContainsKey(ch);
|
|
// or otherwise if it is the default double quote
|
|
return c == '\"';
|
|
}
|
|
|
|
char GetMatch(IReadOnlyDictionary<char, char> dict, char ch)
|
|
{
|
|
// get the corresponding value for the key, if it exists
|
|
// and if the dictionary is populated
|
|
if (dict.Count != 0 && dict.TryGetValue(c, out var value))
|
|
return value;
|
|
// or get the default pair of the default double quote
|
|
return '\"';
|
|
}
|
|
|
|
for (int curPos = startPos; curPos <= endPos; curPos++)
|
|
{
|
|
if (curPos < endPos)
|
|
c = input[curPos];
|
|
else
|
|
c = '\0';
|
|
|
|
//If we're processing an remainder parameter, ignore all other logic
|
|
if (curParam != null && curParam.IsRemainder && curPos != endPos)
|
|
{
|
|
argBuilder.Append(c);
|
|
continue;
|
|
}
|
|
|
|
//If this character is escaped, skip it
|
|
if (isEscaping)
|
|
{
|
|
if (curPos != endPos)
|
|
{
|
|
// if this character matches the quotation mark of the end of the string
|
|
// means that it should be escaped
|
|
// but if is not, then there is no reason to escape it then
|
|
if (c != matchQuote)
|
|
{
|
|
// if no reason to escape the next character, then re-add \ to the arg
|
|
argBuilder.Append('\\');
|
|
}
|
|
|
|
argBuilder.Append(c);
|
|
isEscaping = false;
|
|
continue;
|
|
}
|
|
}
|
|
//Are we escaping the next character?
|
|
if (c == '\\' && (curParam == null || !curParam.IsRemainder))
|
|
{
|
|
isEscaping = true;
|
|
continue;
|
|
}
|
|
|
|
//If we're not currently processing one, are we starting the next argument yet?
|
|
if (curPart == ParserPart.None)
|
|
{
|
|
if (char.IsWhiteSpace(c) || curPos == endPos)
|
|
continue; //Skip whitespace between arguments
|
|
else if (curPos == lastArgEndPos)
|
|
return ParseResult.FromError(CommandError.ParseFailed, "There must be at least one character of whitespace between arguments.");
|
|
else
|
|
{
|
|
if (curParam == null)
|
|
curParam = command.Parameters.Count > argList.Count ? command.Parameters[argList.Count] : null;
|
|
|
|
if (curParam != null && curParam.IsRemainder)
|
|
{
|
|
argBuilder.Append(c);
|
|
continue;
|
|
}
|
|
|
|
if (IsOpenQuote(aliasMap, c))
|
|
{
|
|
curPart = ParserPart.QuotedParameter;
|
|
matchQuote = GetMatch(aliasMap, c);
|
|
continue;
|
|
}
|
|
curPart = ParserPart.Parameter;
|
|
}
|
|
}
|
|
|
|
//Has this parameter ended yet?
|
|
string argString = null;
|
|
if (curPart == ParserPart.Parameter)
|
|
{
|
|
if (curPos == endPos || char.IsWhiteSpace(c))
|
|
{
|
|
argString = argBuilder.ToString();
|
|
lastArgEndPos = curPos;
|
|
}
|
|
else
|
|
argBuilder.Append(c);
|
|
}
|
|
else if (curPart == ParserPart.QuotedParameter)
|
|
{
|
|
if (c == matchQuote)
|
|
{
|
|
argString = argBuilder.ToString(); //Remove quotes
|
|
lastArgEndPos = curPos + 1;
|
|
}
|
|
else
|
|
argBuilder.Append(c);
|
|
}
|
|
|
|
if (argString != null)
|
|
{
|
|
if (curParam == null)
|
|
{
|
|
if (command.IgnoreExtraArgs)
|
|
break;
|
|
else
|
|
return ParseResult.FromError(CommandError.BadArgCount, "The input text has too many parameters.");
|
|
}
|
|
|
|
var typeReaderResult = await curParam.ParseAsync(context, argString, services).ConfigureAwait(false);
|
|
if (!typeReaderResult.IsSuccess && typeReaderResult.Error != CommandError.MultipleMatches)
|
|
return ParseResult.FromError(typeReaderResult, curParam);
|
|
|
|
if (curParam.IsMultiple)
|
|
{
|
|
paramList.Add(typeReaderResult);
|
|
|
|
curPart = ParserPart.None;
|
|
}
|
|
else
|
|
{
|
|
argList.Add(typeReaderResult);
|
|
|
|
curParam = null;
|
|
curPart = ParserPart.None;
|
|
}
|
|
argBuilder.Clear();
|
|
}
|
|
}
|
|
|
|
if (curParam != null && curParam.IsRemainder)
|
|
{
|
|
var typeReaderResult = await curParam.ParseAsync(context, argBuilder.ToString(), services).ConfigureAwait(false);
|
|
if (!typeReaderResult.IsSuccess)
|
|
return ParseResult.FromError(typeReaderResult, curParam);
|
|
argList.Add(typeReaderResult);
|
|
}
|
|
|
|
if (isEscaping)
|
|
return ParseResult.FromError(CommandError.ParseFailed, "Input text may not end on an incomplete escape.");
|
|
if (curPart == ParserPart.QuotedParameter)
|
|
return ParseResult.FromError(CommandError.ParseFailed, "A quoted parameter is incomplete.");
|
|
|
|
//Add missing optionals
|
|
for (int i = argList.Count; i < command.Parameters.Count; i++)
|
|
{
|
|
var param = command.Parameters[i];
|
|
if (param.IsMultiple)
|
|
continue;
|
|
if (!param.IsOptional)
|
|
return ParseResult.FromError(CommandError.BadArgCount, "The input text has too few parameters.");
|
|
argList.Add(TypeReaderResult.FromSuccess(param.DefaultValue));
|
|
}
|
|
|
|
return ParseResult.FromSuccess(argList.ToImmutable(), paramList.ToImmutable());
|
|
}
|
|
}
|
|
}
|