Skip to content

Commit 3bed6b4

Browse files
committed
Use pattern matching in tokenizer
Pattern matching is a particularly good fit here, as each case is data-driven and the flow is easy to read top-to-bottom.
1 parent 1c86eea commit 3bed6b4

File tree

1 file changed

+88
-123
lines changed

1 file changed

+88
-123
lines changed

src/CommandLine/Core/Tokenizer.cs

Lines changed: 88 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ public static Result<IEnumerable<Token>, Error> Tokenize(
2626
bool allowDashDash)
2727
{
2828
var errors = new List<Error>();
29-
Action<Error> onError = errors.Add;
29+
Action<string> onBadFormatToken = arg => errors.Add(new BadFormatTokenError(arg));
30+
Action<string> unknownOptionError = name => errors.Add(new UnknownOptionError(name));
31+
Action<string> doNothing = name => {};
32+
Action<string> onUnknownOption = ignoreUnknownArguments ? doNothing : unknownOptionError;
3033

3134
int consumeNext = 0;
3235
var tokens = new List<Token>();
@@ -36,154 +39,116 @@ public static Result<IEnumerable<Token>, Error> Tokenize(
3639
var enumerator = arguments.GetEnumerator();
3740
while (enumerator.MoveNext())
3841
{
39-
string arg = enumerator.Current;
40-
// TODO: Turn this into a switch statement with pattern matching
41-
if (arg == null)
42-
{
43-
continue;
44-
}
42+
switch (enumerator.Current) {
43+
case null:
44+
break;
4545

46-
if (consumeNext > 0)
47-
{
48-
addValue(arg);
49-
consumeNext = consumeNext - 1;
50-
continue;
51-
}
46+
case string arg when consumeNext > 0:
47+
addValue(arg);
48+
consumeNext = consumeNext - 1;
49+
break;
5250

53-
if (arg == "--")
54-
{
55-
if (allowDashDash)
56-
{
51+
case "--" when allowDashDash:
5752
consumeNext = System.Int32.MaxValue;
58-
continue;
59-
}
60-
else
61-
{
62-
addValue(arg);
63-
continue;
64-
}
65-
}
53+
break;
6654

67-
if (arg.StartsWith("--"))
68-
{
69-
if (arg.Contains("="))
70-
{
55+
case "--":
56+
addValue("--");
57+
break;
58+
59+
case "-":
60+
// A single hyphen is always a value (it usually means "read from stdin" or "write to stdout")
61+
addValue("-");
62+
break;
63+
64+
case string arg when arg.StartsWith("--") && arg.Contains("="):
7165
string[] parts = arg.Substring(2).Split(new char[] { '=' }, 2);
7266
if (String.IsNullOrWhiteSpace(parts[0]) || parts[0].Contains(" "))
7367
{
74-
onError(new BadFormatTokenError(arg));
75-
continue;
68+
onBadFormatToken(arg);
7669
}
7770
else
7871
{
79-
var name = parts[0];
80-
var tokenType = nameLookup(name);
81-
if (tokenType == NameLookupResult.NoOptionFound)
72+
switch(nameLookup(parts[0]))
8273
{
83-
if (ignoreUnknownArguments)
84-
{
85-
continue;
86-
}
87-
else
88-
{
89-
onError(new UnknownOptionError(name));
90-
continue;
91-
}
74+
case NameLookupResult.NoOptionFound:
75+
onUnknownOption(parts[0]);
76+
break;
77+
78+
default:
79+
addName(parts[0]);
80+
addValue(parts[1]);
81+
break;
9282
}
93-
addName(parts[0]);
94-
addValue(parts[1]);
95-
continue;
9683
}
97-
}
98-
else
99-
{
84+
break;
85+
86+
case string arg when arg.StartsWith("--"):
10087
var name = arg.Substring(2);
101-
var tokenType = nameLookup(name);
102-
if (tokenType == NameLookupResult.OtherOptionFound)
103-
{
104-
addName(name);
105-
consumeNext = 1;
106-
continue;
107-
}
108-
else if (tokenType == NameLookupResult.NoOptionFound)
88+
switch (nameLookup(name))
10989
{
110-
if (ignoreUnknownArguments)
111-
{
90+
case NameLookupResult.OtherOptionFound:
91+
addName(name);
92+
consumeNext = 1;
93+
break;
94+
95+
case NameLookupResult.NoOptionFound:
11296
// When ignoreUnknownArguments is true and AutoHelp is true, calling code is responsible for
11397
// setting up nameLookup so that it will return a known name for --help, so that we don't skip it here
114-
continue;
115-
}
116-
else
117-
{
118-
onError(new UnknownOptionError(name));
119-
continue;
120-
}
98+
onUnknownOption(name);
99+
break;
100+
101+
default:
102+
addName(name);
103+
break;
121104
}
122-
else
105+
break;
106+
107+
case string arg when arg.StartsWith("-"):
108+
// First option char that requires a value means we swallow the rest of the string as the value
109+
// But if there is no rest of the string, then instead we swallow the next argument
110+
string chars = arg.Substring(1);
111+
int len = chars.Length;
112+
if (len > 0 && Char.IsDigit(chars[0]))
123113
{
124-
addName(name);
114+
// Assume it's a negative number
115+
addValue(arg);
125116
continue;
126117
}
127-
}
128-
}
129-
130-
if (arg == "-")
131-
{
132-
// A single hyphen is always a value (it usually means "read from stdin" or "write to stdout")
133-
addValue(arg);
134-
continue;
135-
}
136-
137-
if (arg.StartsWith("-"))
138-
{
139-
// First option char that requires a value means we swallow the rest of the string as the value
140-
// But if there is no rest of the string, then instead we swallow the next argument
141-
string chars = arg.Substring(1);
142-
int len = chars.Length;
143-
if (len > 0 && Char.IsDigit(chars[0]))
144-
{
145-
// Assume it's a negative number
146-
addValue(arg);
147-
continue;
148-
}
149-
for (int i = 0; i < len; i++)
150-
{
151-
var s = new String(chars[i], 1);
152-
var tokenType = nameLookup(s);
153-
if (tokenType == NameLookupResult.OtherOptionFound)
118+
for (int i = 0; i < len; i++)
154119
{
155-
addName(s);
156-
if (i+1 < len)
157-
{
158-
addValue(chars.Substring(i+1));
159-
break;
160-
}
161-
else
120+
var s = new String(chars[i], 1);
121+
switch(nameLookup(s))
162122
{
163-
consumeNext = 1;
123+
case NameLookupResult.OtherOptionFound:
124+
addName(s);
125+
if (i+1 < len)
126+
{
127+
addValue(chars.Substring(i+1));
128+
i = len; // Can't use "break" inside a switch, so this breaks out of the loop
129+
}
130+
else
131+
{
132+
consumeNext = 1;
133+
}
134+
break;
135+
136+
case NameLookupResult.NoOptionFound:
137+
onUnknownOption(s);
138+
break;
139+
140+
default:
141+
addName(s);
142+
break;
164143
}
165144
}
166-
else if (tokenType == NameLookupResult.NoOptionFound)
167-
{
168-
if (ignoreUnknownArguments)
169-
{
170-
continue;
171-
}
172-
else
173-
{
174-
onError(new UnknownOptionError(s));
175-
}
176-
}
177-
else
178-
{
179-
addName(s);
180-
}
181-
}
182-
continue;
183-
}
145+
break;
184146

185-
// If we get this far, it's a plain value
186-
addValue(arg);
147+
case string arg:
148+
// If we get this far, it's a plain value
149+
addValue(arg);
150+
break;
151+
}
187152
}
188153

189154
return Result.Succeed<IEnumerable<Token>, Error>(tokens.AsEnumerable(), errors.AsEnumerable());

0 commit comments

Comments
 (0)