public async Task <Tuple <Command, IResult> > Execute(IUserMessage message, string input, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { var searchResult = Search(message, input); if (!searchResult.IsSuccess) { return(new Tuple <Command, IResult>(null, searchResult)); } var commands = searchResult.Commands; for (int i = commands.Count - 1; i >= 0; i--) { var preconditionResult = await commands[i].CheckPreconditions(message); if (!preconditionResult.IsSuccess) { if (commands.Count == 1) { return(new Tuple <Command, IResult>(null, searchResult)); } else { continue; } } var parseResult = await commands[i].Parse(message, searchResult, preconditionResult); if (!parseResult.IsSuccess) { if (parseResult.Error == CommandError.MultipleMatches) { TypeReaderValue[] argList, paramList; switch (multiMatchHandling) { case MultiMatchHandling.Best: argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToArray(); paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToArray(); parseResult = ParseResult.FromSuccess(argList, paramList); break; } } if (!parseResult.IsSuccess) { if (commands.Count == 1) { return(new Tuple <Command, IResult>(null, parseResult)); } else { continue; } } } return(new Tuple <Command, IResult>(commands[i], await commands[i].Execute(message, parseResult))); } return(new Tuple <Command, IResult>(null, SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload."))); }
/// <summary> /// Executes the command. /// </summary> /// <param name="context">The context of the command.</param> /// <param name="input">The command string.</param> /// <param name="services">The service to be used in the command's dependency injection.</param> /// <param name="multiMatchHandling">The handling mode when multiple command matches are found.</param> /// <returns> /// A task that represents the asynchronous execution operation. The task result contains the result of the /// command execution. /// </returns> public async Task <IResult> ExecuteAsync(ICommandContext context, string input, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { services = services ?? EmptyServiceProvider.Instance; var searchResult = Search(input); if (!searchResult.IsSuccess) { await _commandExecutedEvent.InvokeAsync(Optional.Create <CommandInfo>(), context, searchResult).ConfigureAwait(false); return(searchResult); } var commands = searchResult.Commands; var preconditionResults = new Dictionary <CommandMatch, PreconditionResult>(); foreach (var match in commands) { preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, services).ConfigureAwait(false); } var successfulPreconditions = preconditionResults .Where(x => x.Value.IsSuccess) .ToArray(); if (successfulPreconditions.Length == 0) { //All preconditions failed, return the one from the highest priority command var bestCandidate = preconditionResults .OrderByDescending(x => x.Key.Command.Priority) .FirstOrDefault(x => !x.Value.IsSuccess); await _commandExecutedEvent.InvokeAsync(bestCandidate.Key.Command, context, bestCandidate.Value).ConfigureAwait(false); return(bestCandidate.Value); } //If we get this far, at least one precondition was successful. var parseResultsDict = new Dictionary <CommandMatch, ParseResult>(); foreach (var pair in successfulPreconditions) { var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, services).ConfigureAwait(false); if (parseResult.Error == CommandError.MultipleMatches) { IReadOnlyList <TypeReaderValue> argList, paramList; switch (multiMatchHandling) { case MultiMatchHandling.Best: argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); parseResult = ParseResult.FromSuccess(argList, paramList); break; } } parseResultsDict[pair.Key] = parseResult; } // Calculates the 'score' of a command given a parse result float CalculateScore(CommandMatch match, ParseResult parseResult) { float argValuesScore = 0, paramValuesScore = 0; if (match.Command.Parameters.Count > 0) { var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; argValuesScore = argValuesSum / match.Command.Parameters.Count; paramValuesScore = paramValuesSum / match.Command.Parameters.Count; } var totalArgsScore = (argValuesScore + paramValuesScore) / 2; return(match.Command.Priority + totalArgsScore * 0.99f); } //Order the parse results by their score so that we choose the most likely result to execute var parseResults = parseResultsDict .OrderByDescending(x => CalculateScore(x.Key, x.Value)); var successfulParses = parseResults .Where(x => x.Value.IsSuccess) .ToArray(); if (successfulParses.Length == 0) { //All parses failed, return the one from the highest priority command, using score as a tie breaker var bestMatch = parseResults .FirstOrDefault(x => !x.Value.IsSuccess); await _commandExecutedEvent.InvokeAsync(bestMatch.Key.Command, context, bestMatch.Value).ConfigureAwait(false); return(bestMatch.Value); } //If we get this far, at least one parse was successful. Execute the most likely overload. var chosenOverload = successfulParses[0]; var result = await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, services).ConfigureAwait(false); if (!result.IsSuccess && !(result is RuntimeResult)) // succesful results raise the event in CommandInfo#ExecuteInternalAsync (have to raise it there b/c deffered execution) { await _commandExecutedEvent.InvokeAsync(chosenOverload.Key.Command, context, result); } return(result); }
public static async Task <ParseResult> ParseArgsAsync(CommandInfo command, ICommandContext context, bool ignoreExtraArgs, IServiceProvider services, string input, int startPos, IReadOnlyDictionary <char, char> aliasMap) { 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, matchQuote = '\0'; // local helper functions bool IsOpenQuote(IReadOnlyDictionary <char, char> dict, char ch) { // return if the key is contained in the dictionary if it is populated if (dict.Count != 0) { return(dict.ContainsKey(ch)); } // or otherwise if it is the default double quote return(c == '\"'); } char GetMatch(IReadOnlyDictionary <char, char> dict, char ch) { // get the corresponding value for the key, if it exists // and if the dictionary is populated if (dict.Count != 0 && dict.TryGetValue(c, out var value)) { return(value); } // or get the default pair of the default double quote return('\"'); } 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 (IsOpenQuote(aliasMap, c)) { curPart = ParserPart.QuotedParameter; matchQuote = GetMatch(aliasMap, c); 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 == matchQuote) { 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())); }
public async Task <IResult> ExecuteAsync(ICommandContext context, string input, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { services = services ?? EmptyServiceProvider.Instance; var searchResult = Search(context, input); if (!searchResult.IsSuccess) { return(searchResult); } var commands = searchResult.Commands; for (int i = 0; i < commands.Count; i++) { var preconditionResult = await commands[i].CheckPreconditionsAsync(context, services).ConfigureAwait(false); if (!preconditionResult.IsSuccess) { if (commands.Count == 1) { return(preconditionResult); } else { continue; } } var parseResult = await commands[i].ParseAsync(context, searchResult, preconditionResult).ConfigureAwait(false); if (!parseResult.IsSuccess) { if (parseResult.Error == CommandError.MultipleMatches) { IReadOnlyList <TypeReaderValue> argList, paramList; switch (multiMatchHandling) { case MultiMatchHandling.Best: argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); parseResult = ParseResult.FromSuccess(argList, paramList); break; } } if (!parseResult.IsSuccess) { if (commands.Count == 1) { return(parseResult); } else { continue; } } } return(await commands[i].ExecuteAsync(context, parseResult, services).ConfigureAwait(false)); } return(SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload.")); }
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) { 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())); }