I've been playing around with C# 7 and created this if-less and switch
-only command-line parser. What do you think of it? Can it be made more C# 7 or otherwise improved? Is the new switch
a good alternative to the old if/else
?
It splits a string by |=|:
(space, equal-sign, colon) into tokens (unless escaped) and removes quotes from quoted parts.
public static void Parse7(string text)
{
var escapableChars = new HashSet<char> { '\\', '"', '=', ':' };
var separators = new HashSet<char> { ' ', '=', ':' };
var tokens = new List<string>();
var token = new StringBuilder();
var escapeMode = false;
var quoted = false;
bool IsUnquotedSeparator(char c) => separators.Contains(c) && !quoted;
bool IsTokenEmpty() => token.Length == 0;
foreach (var c in text ?? throw new ArgumentNullException(nameof(text)))
{
switch (c)
{
case '\\' when !escapeMode:
escapeMode = true;
// Don't eat escape-char yet.
break;
case '"':
quoted = !quoted;
// Don't eat quotes.
break;
default:
switch (escapeMode)
{
case true:
switch (escapableChars.Contains(c))
{
case false:
// Eat escape-char because it doesn't escape anything.
token.Append('\\');
break;
}
// Eat escaped-char.
token.Append(c);
escapeMode = false;
break;
default:
switch (IsUnquotedSeparator(c))
{
case true when !IsTokenEmpty():
// Eat token.
tokens.Add(token.ToString());
token.Clear();
break;
case true:
// Don't eat separators.
break;
default:
// Eat any other char.
token.Append(c);
break;
}
break;
}
break;
}
}
switch (IsTokenEmpty())
{
case false:
// Eat the last token.
tokens.Add(token.ToString());
break;
}
tokens.Dump(); // LINQPad
}
Example:
var cmd = @"foo.exe --bar=""baz"" --qux --baar\=baaz --quux";
Parse7(cmd);
Result:
foo.exe --bar baz --qux --baar=baaz --quux
For comparison purposes this is the classic version:
public static void Parse(string text)
{
var escapableChars = new HashSet<char> { '\\', '"', '=', ':' };
var separators = new HashSet<char> { ' ', '=', ':' };
var tokens = new List<string>();
var token = new StringBuilder();
var escapeMode = false;
var quoted = false;
foreach (var c in text)
{
if (c == '\\' && !escapeMode)
{
escapeMode = true;
// Don't eat escape-char yet.
continue;
}
if (escapeMode)
{
if (escapableChars.Contains(c)) token.Append(c);
else token.Append('\\').Append(c);
escapeMode = false;
// Escape-char already eaten.
continue;
}
if (c == '"')
{
quoted = !quoted;
// Don't eat quotes.
continue;
}
if (separators.Contains(c) && !quoted)
{
tokens.Add(token.ToString());
token.Clear();
}
else
{
token.Append(c);
}
}
if (token.Length > 0) tokens.Add(token.ToString());
tokens.Dump(); // LINQPad
}
using
statement was shown as inactive and unreachable. \$\endgroup\$