In your simple scenario where the syntax will always be [Command] [Space separated numeric argument list]
you could consider the following approach:
Implement a Dictionary<string, Func<IEnumerable<int>, Command>>
where the key would be the command's reserved word and the value a delegate that would take the parsed string arguments (I've taken the liberty of assuming they will all be integers) and build the appropiate command. These delegates could point to static factory methods implemented in Command
similar to how Linq.Expressions
is built:
public abstract class Command
{
private const int validMoveArgumentCount = 2;
private const int validStopArgumentCount = 1;
public static Command Move(IEnumerable<int> args)
{
if (args == null)
throw new ArgumentNullException(nameof(args));
var arguments = args.ToArray();
var argumentCount = arguments.Length;
if (argumentCount != validMoveArgumentCount)
throw new SyntaxErrorException("Invalid number of arguments in Move command. Expected {validMoveArgumentCount}, received {argumentCount}.");
return new MoveCommand(arguments[0], arguments[1]);
}
public static Command Stop(IEnumerable<int> args)
{
if (args == null)
throw new ArgumentNullException(nameof(args));
var arguments = args.ToArray();
var argumentCount = arguments.Length;
if (argumentCount != validStopArgumentCount)
throw new SyntaxErrorException("Invalid number of arguments in Stop command. Expected {validStopArgumentCount}, received {argumentCount}.");
return new StopCommand(arguments[0]);
}
public abstract void Update();
...
private class MoveCommand: Command { ... }
private class StopCommand: Command { ... }
}
Do note a pattern I really enjoy a lot; nested private classes that derive from the containing abstract class. This is a neat way of completely hiding the implementation of the concrete commands and controlling precisely who can instantiate them.
You'd need to have a initialization method where you build up the dictionary, something along the lines:
var commandDictionary = new Dictionary<string, Func<IEnumerable<int>, Command>>();
commandDictionary["move"] = args => Command.Move(args);
commandDictionary["stop"] = args => Command.Stop(args);
This is essentially where you wire up your parsing logic.
And now when parsing each line of your commands input, you'd do something similar to (oversimplifying of couse):
private Command ParseCommand(string commandLine)
{
Debug.Assert(!string.IsNullOrWhiteSpace(commandLine));
var words = commandLine.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (commandDictionary.ContainsKey(words[0]))
return commandDictionary[words[0]](words.Skip(1).ParseIntegerArguments());
throw new SyntaxErrorException($"Unrecognized command '{words[0]}'.");
}
private static IEnumerable<int> ParseIntegerArguments(IEnumerable<string> args)
{
Debug.Assert(args != null);
foreach (var arg in args)
{
int parsedArgument;
if (!int.TryParse(arg, out parsedArgument))
throw new SyntaxErrorException("Invalid argument '{arg}'");
yield return parsedArgument;
}
}
All that said, here you are not using a switch statement but you are building up a dictionary. At the end of the day, you somehow have to define what commands are valid and how you are going to process them. Maybe with this approach, adding new commands is a little cleaner, but thats up to personal taste.
Also worth mentioning that, for simplicity's sake, I'm throwing exceptions when encountering incorrect syntax. This wouldn't be the way I'd do it if I were seriously implementing a similar parser because, as Eric Lippert would put it, I'd be throwing particularly vexing exceptions. I'd probably just parse the whole thing as best as possible (error recovery is pretty trivial in this particular case) adding descriptive error objects to a diagnosticsBag
(some IList<ParsingError>
) when appropiate and then producing an aggregated parsing error report.