/// <summary>Handles a wildcard node by taking words one by one until a template is found.</summary> private Template?WildcardSearch(RequestSentence subRequest, RequestProcess process, string[] inputPath, int inputPathIndex, bool traceSearch, MatchState matchState, int minimumWords) { int inputPathIndex2; var star = process.GetStar(matchState); int starIndex = star.Count; // Reserve a space in the star list. If a template is found, this slot will be filled with the matched phrase. // This function can call other wildcards recursively. The reservation ensures that the star list will be populated correctly. star.Add(""); for (inputPathIndex2 = inputPathIndex + minimumWords; inputPathIndex2 <= inputPath.Length; ++inputPathIndex2) { var result = this.Search(subRequest, process, inputPath, inputPathIndex2, traceSearch, matchState); if (result != null) { star[starIndex] = string.Join(" ", inputPath, inputPathIndex, inputPathIndex2 - inputPathIndex); return(result); } // Wildcards cannot match these tokens. if ((matchState == MatchState.Message && inputPath[inputPathIndex2] == "<that>") || (matchState == MatchState.That && inputPath[inputPathIndex2] == "<topic>")) { break; } } // No match; remove the reserved slot. star.RemoveAt(starIndex); Debug.Assert(star.Count == starIndex); return(null); }
public Template?Search(RequestSentence sentence, RequestProcess process, string that, bool traceSearch) { if (process.RecursionDepth > sentence.Bot.Config.RecursionLimit) { sentence.Bot.Log(LogLevel.Warning, "Recursion limit exceeded. User: "******"; raw input: \"" + sentence.Request.Text + "\""); throw new RecursionLimitException(); } // Generate the input path. var messageSplit = sentence.Text.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); var thatSplit = that.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); var topicSplit = sentence.Bot.Normalize(sentence.User.Topic).Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); var inputPath = new string[messageSplit.Length + thatSplit.Length + topicSplit.Length + 2]; int i = 0; messageSplit.CopyTo(inputPath, 0); i += messageSplit.Length; inputPath[i++] = "<that>"; thatSplit.CopyTo(inputPath, i); i += thatSplit.Length; inputPath[i++] = "<topic>"; topicSplit.CopyTo(inputPath, i); if (traceSearch) { process.Log(LogLevel.Diagnostic, "Normalized path: " + string.Join(" ", inputPath)); } var result = this.Search(sentence, process, inputPath, 0, traceSearch, MatchState.Message); return(result); }
public RequestProcess(RequestSentence sentence, int recursionDepth, bool useTests) { this.Sentence = sentence; this.RecursionDepth = recursionDepth; this.Variables = new Dictionary <string, string>(sentence.Bot.Config.StringComparer); if (useTests) { this.testResults = new Dictionary <string, TestResult>(sentence.Bot.Config.StringComparer); this.TestResults = new ReadOnlyDictionary <string, TestResult>(this.testResults); } }
private Template?Search(RequestSentence sentence, RequestProcess process, string[] inputPath, int inputPathIndex, bool traceSearch, MatchState matchState) { if (traceSearch) { sentence.Bot.Log(LogLevel.Diagnostic, "Search: " + process.Path); } int pathDepth = process.patternPathTokens.Count; if (process.CheckTimeout()) { sentence.Bot.Log(LogLevel.Warning, "Request timeout. User: "******"; raw input: \"" + sentence.Request.Text + "\""); throw new TimeoutException(); } bool tokensRemaining; if (inputPathIndex >= inputPath.Length) { // No tokens remaining in the input path. If this node has a template, return success. if (this.Template != null) { return(this.Template); } // Otherwise, look for zero+ wildcards. tokensRemaining = false; } else { tokensRemaining = true; switch (matchState) { case MatchState.Message: if (inputPath[inputPathIndex] == "<that>") { matchState = MatchState.That; } break; case MatchState.That: if (inputPath[inputPathIndex] == "<topic>") { matchState = MatchState.Topic; } break; } } // Reserve a space in the pattern path list here. This is so that further recursive calls will leave it alone. // If we find a template, we replace the empty string with the correct token. process.patternPathTokens.Add("?"); //var star = matchState == MatchState.That ? subRequest.thatstar : // matchState == MatchState.Topic ? subRequest.topicstar : // subRequest.star; // Search for child nodes that match the input in priority order. // Priority exact match. if (tokensRemaining && this.children.TryGetValue("$" + inputPath[inputPathIndex], out var node)) { process.patternPathTokens[pathDepth] = "$" + inputPath[inputPathIndex]; var result = node.Search(sentence, process, inputPath, inputPathIndex + 1, traceSearch, matchState); if (result != null) { return(result); } } // Priority zero+ wildcard. if (this.children.TryGetValue("#", out node)) { process.patternPathTokens[pathDepth] = "#"; var result = node.WildcardSearch(sentence, process, inputPath, inputPathIndex, traceSearch, matchState, 0); if (result != null) { return(result); } } // Priority one+ wildcard. if (this.children.TryGetValue("_", out node)) { process.patternPathTokens[pathDepth] = "_"; var result = node.WildcardSearch(sentence, process, inputPath, inputPathIndex, traceSearch, matchState, 1); if (result != null) { return(result); } } // Exact match. if (tokensRemaining && this.children.TryGetValue(inputPath[inputPathIndex], out node)) { process.patternPathTokens[pathDepth] = inputPath[inputPathIndex]; var result = node.Search(sentence, process, inputPath, inputPathIndex + 1, traceSearch, matchState); if (result != null) { return(result); } } // Sets. (The empty string cannot be matched by a set token.) if (tokensRemaining) { foreach (var child in this.setChildren) { process.patternPathTokens[pathDepth] = $"<set>{child.SetName}</set>"; if (sentence.Bot.Sets.TryGetValue(child.SetName, out var set)) { var star = process.GetStar(matchState); int starIndex = star.Count; star.Add(""); // Reserving a space; see above. // Similarly to a wildcard search, we take words one by one until either a template is found, or no words remain. // This time, each time we take a word, we must check that the phrase is in the set. var phrase = new StringBuilder(); int wordCount = 0; for (int inputPathIndex2 = inputPathIndex; inputPathIndex2 < inputPath.Length; ++inputPathIndex2) { if ((matchState == MatchState.Message && inputPath[inputPathIndex2] == "<that>") || (matchState == MatchState.That && inputPath[inputPathIndex2] == "<topic>")) { break; } if (phrase.Length > 0) { phrase.Append(' '); } phrase.Append(inputPath[inputPathIndex2]); ++wordCount; if (set.Contains(phrase.ToString())) { // Phrase found in the set. Now continue with the tree search. var result = child.Node.Search(sentence, process, inputPath, inputPathIndex + wordCount, traceSearch, matchState); if (result != null) { star[starIndex] = phrase.ToString(); return(result); } } // Each set keeps track of the greatest number of words any element in the set has. // After reaching that number, we can stop searching. if (wordCount >= set.MaxWords) { break; } } // No match; release the reserved space. star.RemoveAt(starIndex); Debug.Assert(star.Count == starIndex); } else { sentence.Request.Bot.Log(LogLevel.Warning, $"Reference to a missing set in pattern path '{string.Join(" ", process.patternPathTokens)}'."); } } } // Zero+ wildcard. if (this.children.TryGetValue("^", out node)) { process.patternPathTokens[pathDepth] = "^"; var result = node.WildcardSearch(sentence, process, inputPath, inputPathIndex, traceSearch, matchState, 0); if (result != null) { return(result); } } // One+ wildcard. if (this.children.TryGetValue("*", out node)) { process.patternPathTokens[pathDepth] = "*"; var result = node.WildcardSearch(sentence, process, inputPath, inputPathIndex, traceSearch, matchState, 1); if (result != null) { return(result); } } // No match. process.patternPathTokens.RemoveAt(pathDepth); Debug.Assert(process.patternPathTokens.Count == pathDepth); return(null); }