You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

CommandParser.cs 7.7 kB

9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.Immutable;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace Discord.Commands
  7. {
  8. internal static class CommandParser
  9. {
  10. private enum ParserPart
  11. {
  12. None,
  13. Parameter,
  14. QuotedParameter
  15. }
  16. public static async Task<ParseResult> ParseArgsAsync(CommandInfo command, ICommandContext context, bool ignoreExtraArgs, IServiceProvider services, string input, int startPos, IReadOnlyDictionary<char, char> aliasMap)
  17. {
  18. ParameterInfo curParam = null;
  19. StringBuilder argBuilder = new StringBuilder(input.Length);
  20. int endPos = input.Length;
  21. var curPart = ParserPart.None;
  22. int lastArgEndPos = int.MinValue;
  23. var argList = ImmutableArray.CreateBuilder<TypeReaderResult>();
  24. var paramList = ImmutableArray.CreateBuilder<TypeReaderResult>();
  25. bool isEscaping = false;
  26. char c, matchQuote = '\0';
  27. // local helper functions
  28. bool IsOpenQuote(IReadOnlyDictionary<char, char> dict, char ch)
  29. {
  30. // return if the key is contained in the dictionary if it is populated
  31. if (dict.Count != 0)
  32. return dict.ContainsKey(ch);
  33. // or otherwise if it is the default double quote
  34. return c == '\"';
  35. }
  36. char GetMatch(IReadOnlyDictionary<char, char> dict, char ch)
  37. {
  38. // get the corresponding value for the key, if it exists
  39. // and if the dictionary is populated
  40. if (dict.Count != 0 && dict.TryGetValue(c, out var value))
  41. return value;
  42. // or get the default pair of the default double quote
  43. return '\"';
  44. }
  45. for (int curPos = startPos; curPos <= endPos; curPos++)
  46. {
  47. if (curPos < endPos)
  48. c = input[curPos];
  49. else
  50. c = '\0';
  51. //If this character is escaped, skip it
  52. if (isEscaping)
  53. {
  54. if (curPos != endPos)
  55. {
  56. argBuilder.Append(c);
  57. isEscaping = false;
  58. continue;
  59. }
  60. }
  61. //Are we escaping the next character?
  62. if (c == '\\' && (curParam == null || !curParam.IsRemainder))
  63. {
  64. isEscaping = true;
  65. continue;
  66. }
  67. //If we're processing an remainder parameter, ignore all other logic
  68. if (curParam != null && curParam.IsRemainder && curPos != endPos)
  69. {
  70. argBuilder.Append(c);
  71. continue;
  72. }
  73. //If we're not currently processing one, are we starting the next argument yet?
  74. if (curPart == ParserPart.None)
  75. {
  76. if (char.IsWhiteSpace(c) || curPos == endPos)
  77. continue; //Skip whitespace between arguments
  78. else if (curPos == lastArgEndPos)
  79. return ParseResult.FromError(CommandError.ParseFailed, "There must be at least one character of whitespace between arguments.");
  80. else
  81. {
  82. if (curParam == null)
  83. curParam = command.Parameters.Count > argList.Count ? command.Parameters[argList.Count] : null;
  84. if (curParam != null && curParam.IsRemainder)
  85. {
  86. argBuilder.Append(c);
  87. continue;
  88. }
  89. if (IsOpenQuote(aliasMap, c))
  90. {
  91. curPart = ParserPart.QuotedParameter;
  92. matchQuote = GetMatch(aliasMap, c);
  93. continue;
  94. }
  95. curPart = ParserPart.Parameter;
  96. }
  97. }
  98. //Has this parameter ended yet?
  99. string argString = null;
  100. if (curPart == ParserPart.Parameter)
  101. {
  102. if (curPos == endPos || char.IsWhiteSpace(c))
  103. {
  104. argString = argBuilder.ToString();
  105. lastArgEndPos = curPos;
  106. }
  107. else
  108. argBuilder.Append(c);
  109. }
  110. else if (curPart == ParserPart.QuotedParameter)
  111. {
  112. if (c == matchQuote)
  113. {
  114. argString = argBuilder.ToString(); //Remove quotes
  115. lastArgEndPos = curPos + 1;
  116. }
  117. else
  118. argBuilder.Append(c);
  119. }
  120. if (argString != null)
  121. {
  122. if (curParam == null)
  123. {
  124. if (command.IgnoreExtraArgs)
  125. break;
  126. else
  127. return ParseResult.FromError(CommandError.BadArgCount, "The input text has too many parameters.");
  128. }
  129. var typeReaderResult = await curParam.ParseAsync(context, argString, services).ConfigureAwait(false);
  130. if (!typeReaderResult.IsSuccess && typeReaderResult.Error != CommandError.MultipleMatches)
  131. return ParseResult.FromError(typeReaderResult);
  132. if (curParam.IsMultiple)
  133. {
  134. paramList.Add(typeReaderResult);
  135. curPart = ParserPart.None;
  136. }
  137. else
  138. {
  139. argList.Add(typeReaderResult);
  140. curParam = null;
  141. curPart = ParserPart.None;
  142. }
  143. argBuilder.Clear();
  144. }
  145. }
  146. if (curParam != null && curParam.IsRemainder)
  147. {
  148. var typeReaderResult = await curParam.ParseAsync(context, argBuilder.ToString(), services).ConfigureAwait(false);
  149. if (!typeReaderResult.IsSuccess)
  150. return ParseResult.FromError(typeReaderResult);
  151. argList.Add(typeReaderResult);
  152. }
  153. if (isEscaping)
  154. return ParseResult.FromError(CommandError.ParseFailed, "Input text may not end on an incomplete escape.");
  155. if (curPart == ParserPart.QuotedParameter)
  156. return ParseResult.FromError(CommandError.ParseFailed, "A quoted parameter is incomplete");
  157. //Add missing optionals
  158. for (int i = argList.Count; i < command.Parameters.Count; i++)
  159. {
  160. var param = command.Parameters[i];
  161. if (param.IsMultiple)
  162. continue;
  163. if (!param.IsOptional)
  164. return ParseResult.FromError(CommandError.BadArgCount, "The input text has too few parameters.");
  165. argList.Add(TypeReaderResult.FromSuccess(param.DefaultValue));
  166. }
  167. return ParseResult.FromSuccess(argList.ToImmutable(), paramList.ToImmutable());
  168. }
  169. }
  170. }