/
CommandParser.cs
167 lines (150 loc) · 6.44 KB
/
CommandParser.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
using System;
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, 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;
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 (c == '\"')
{
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 (c == '\"')
{
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);
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());
}
}
}