Created
January 28, 2018 13:25
-
-
Save Chris-Johnston/37b37e70246562c27f0a42d6bd8eda74 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 class QuotationPair | |
{ | |
public QuotationPair(char left, char? right = null) | |
{ | |
Left = left; | |
Right = right ?? left; | |
} | |
public char Left { get; set; } | |
public char Right { get; set; } | |
} | |
private static List<QuotationPair> aliases = new List<QuotationPair>() | |
{ | |
new QuotationPair('"'), new QuotationPair('<', '>') | |
}; | |
/// <summary> | |
/// Checks to see if the supplied character is a quotation mark | |
/// from either the default " character, or the list of aliases | |
/// if they are provided. | |
/// </summary> | |
/// <param name="c"></param> | |
private static bool findQuotationChar(char c, bool leftSide, out QuotationPair matched) | |
{ | |
if (aliases == null) | |
{ | |
matched = new QuotationPair('\"'); | |
return c == '\"'; | |
} | |
var q = aliases.Find(x => x.Left == c && leftSide || x.Right == c && !leftSide); | |
matched = q; | |
return q != null; | |
//if(leftSide) | |
//{ | |
//} | |
// return aliases.Exists(x => x.Left == c); | |
//return aliases.Exists(x => x.Right == c); | |
} | |
public static async Task<ParseResult> ParseArgsAsync(CommandInfo command, ICommandContext context, bool ignoreExtraArgs, IServiceProvider services, string input, int startPos) | |
{ | |
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; | |
QuotationPair matchingQuotation = null; | |
for (int curPos = startPos; curPos <= endPos; curPos++) | |
{ | |
if (curPos < endPos) | |
c = input[curPos]; | |
else | |
c = '\0'; | |
//If this character is escaped, skip it | |
if (isEscaping) | |
{ | |
if (curPos != endPos) | |
{ | |
argBuilder.Append(c); | |
isEscaping = false; | |
continue; | |
} | |
} | |
//Are we escaping the next character? | |
if (c == '\\' && (curParam == null || !curParam.IsRemainder)) | |
{ | |
isEscaping = true; | |
continue; | |
} | |
//If we're processing an remainder parameter, ignore all other logic | |
if (curParam != null && curParam.IsRemainder && curPos != endPos) | |
{ | |
argBuilder.Append(c); | |
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 (findQuotationChar(c, true, out matchingQuotation)) | |
{ | |
curPart = ParserPart.QuotedParameter; | |
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 (findQuotationChar(c, false, out matchingQuotation)) | |
if( matchingQuotation != null && matchingQuotation.Right == c) | |
{ | |
argString = argBuilder.ToString(); //Remove quotes | |
lastArgEndPos = curPos + 1; | |
} | |
else | |
argBuilder.Append(c); | |
} | |
if (argString != null) | |
{ | |
if (curParam == null) | |
{ | |
if (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); | |
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); | |
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()); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment