/// <summary> /// Creates an automaton for validating the correctness of argument placeholders for a given argument. /// </summary> /// <param name="argToValidateIndex">The index of the argument to validate.</param> /// <param name="argNames">The names of all arguments.</param> /// <returns>The created automaton.</returns> private static StringAutomaton GetArgumentValidatingAutomaton(int argToValidateIndex, IReadOnlyList <string> argNames) { Debug.Assert( argNames != null && argNames.All(name => !string.IsNullOrEmpty(name)), "A valid array of argument names must be provided."); Debug.Assert( argToValidateIndex >= 0 && argToValidateIndex < argNames.Count, "The provided argument index must be a valid index in the argument name array."); string argListKey = ArgumentListToDictionaryKey(argToValidateIndex, argNames); StringAutomaton result; if (ArgsToValidatingAutomaton.TryGetValue(argListKey, out result)) { return(result); } // Accepts placeholder for the current argument StringAutomaton checkBracesForCurrentArg = StringAutomaton.ConstantOn(1.0, "{" + argNames[argToValidateIndex] + "}"); StringAutomaton checkBracesForOtherArgs = DisallowBracesAutomaton.Clone(); if (argNames.Count > 1) { // Skips placeholders for every argument except the current one StringAutomaton skipOtherArgs = StringAutomaton.ConstantOnElement(1.0, '{') .Append(StringAutomaton.ConstantOn(1.0, argNames.Where((arg, index) => index != argToValidateIndex))) .Append(StringAutomaton.ConstantOnElement(1.0, '}')); // Accepts placeholders for arguments other than current, with arbitrary intermediate text checkBracesForOtherArgs = checkBracesForOtherArgs.Append(skipOtherArgs); checkBracesForOtherArgs = StringAutomaton.Repeat(checkBracesForOtherArgs, minTimes: 0) .Append(DisallowBracesAutomaton); } // Checks the placeholder for the current argument, then skips placeholders for other arguments StringAutomaton validateArgumentThenOtherArguments = checkBracesForCurrentArg.Append(checkBracesForOtherArgs); if (!RequirePlaceholderForEveryArgument) { // Make this block optional validateArgumentThenOtherArguments = StringAutomaton.Sum( validateArgumentThenOtherArguments, StringAutomaton.ConstantOn(1.0, string.Empty)); } // Accepts placeholders for arguments other then current, then for the current argument, then again other placeholders result = checkBracesForOtherArgs.Append(validateArgumentThenOtherArguments); result = result.TryDeterminize(); ArgsToValidatingAutomaton[argListKey] = result; return(result); }
/// <summary> /// An implementation of <see cref="FormatAverageConditional(StringDistribution, IReadOnlyList{StringDistribution}, IReadOnlyList{string})"/> /// specialized for some cases for performance reasons. /// </summary> /// <param name="str">The message from <c>str</c>.</param> /// <param name="allowedArgs">The message from <c>args</c>, truncated to allowed values and converted to automata.</param> /// <param name="argNames">The names of the arguments.</param> /// <param name="resultDist">The computed result.</param> /// <returns> /// <see langword="true"/> if there is an optimized implementation available for the provided parameters, /// and <paramref name="resultDist"/> has been computed using it. /// <see langword="false"/> otherwise. /// </returns> /// <remarks> /// Supports the case of point mass <paramref name="str"/> and <paramref name="allowedArgs"/>, /// where each of the arguments is present in <paramref name="str"/> at most once and the occurrences /// are non-overlapping. /// </remarks> private static bool TryOptimizedFormatAverageConditionalImpl( StringDistribution str, IReadOnlyList <StringAutomaton> allowedArgs, IReadOnlyList <string> argNames, out StringDistribution resultDist) { resultDist = null; string[] allowedArgPoints = Util.ArrayInit(allowedArgs.Count, i => allowedArgs[i].TryComputePoint()); if (!str.IsPointMass || !allowedArgPoints.All(argPoint => argPoint != null && SubstringOccurrencesCount(str.Point, argPoint) <= 1)) { // Fall back to the general case return(false); } // Obtain arguments present in 'str' (ordered by position) var argPositions = allowedArgPoints.Select((arg, argIndex) => Tuple.Create(argIndex, str.Point.IndexOf(arg, StringComparison.Ordinal))) .Where(t => t.Item2 != -1) .OrderBy(t => t.Item2) .ToList(); if (RequirePlaceholderForEveryArgument && argPositions.Count != allowedArgs.Count) { // Some argument is not in 'str' resultDist = StringDistribution.Zero(); return(true); } StringAutomaton result = StringAutomaton.ConstantOn(1.0, string.Empty); int curArgumentIndex = -1; int curArgumentPos = -1; int curArgumentLength = 1; for (int i = 0; i < argPositions.Count; ++i) { int prevArgumentIndex = curArgumentIndex; int prevArgumentPos = curArgumentPos; int prevArgumentLength = curArgumentLength; curArgumentIndex = argPositions[i].Item1; curArgumentPos = argPositions[i].Item2; curArgumentLength = allowedArgPoints[curArgumentIndex].Length; if (prevArgumentIndex != -1 && curArgumentPos < prevArgumentPos + prevArgumentLength) { // It's easier to fall back to the general case in case of overlapping arguments return(false); } // Append the contents of 'str' preceeding the current argument result = result.Append(str.Point.Substring(prevArgumentPos + prevArgumentLength, curArgumentPos - prevArgumentPos - prevArgumentLength)); // The format may have included either the text ot the placeholder string argName = "{" + argNames[curArgumentIndex] + "}"; if (RequirePlaceholderForEveryArgument) { result = result.Append(StringAutomaton.ConstantOn(1.0, argName)); } else { result = result.Append(StringAutomaton.ConstantOn(1.0, argName, allowedArgPoints[curArgumentIndex])); } } // Append the rest of 'str' result = result.Append(str.Point.Substring(curArgumentPos + curArgumentLength, str.Point.Length - curArgumentPos - curArgumentLength)); resultDist = StringDistribution.FromWeightFunction(result); return(true); }