/// <summary>Dispatch <paramref name="events"/></summary>
        /// <param name="events"></param>
        public void DispatchEvents(ref StructList12 <IEvent> events)
        {
            // Errors
            StructList4 <Exception> errors = new StructList4 <Exception>();

            for (int i = 0; i < events.Count; i++)
            {
                IEvent e = events[i];
                try
                {
                    e?.Observer?.Observer?.OnNext(e);
                }
                catch (Exception error)
                {
                    if (errorHandler != null)
                    {
                        errorHandler(this, e, error);
                    }
                    else
                    {
                        errors.Add(error);
                    }
                }
            }
            if (errors.Count > 0)
            {
                throw new AggregateException(errors.ToArray());
            }
        }
Esempio n. 2
0
        /// <summary>
        /// Get all non-canonical keys as parameterName,parameterValue in order of from root towards tail.
        /// </summary>
        /// <param name="line">line to read parameters of</param>
        /// <param name="parameterInfos">(optional) map of infos for determining if parameter is key</param>
        /// <returns>dictionary of keys</returns>
        public static KeyValuePair <string, string>[] GetNonCanonicalsArray(this ILine line, IParameterInfos parameterInfos = null)
        {
            StructList12 <KeyValuePair <string, string> > result = new StructList12 <KeyValuePair <string, string> >();

            line.GetNonCanonicalKeyPairs <StructList12 <KeyValuePair <string, string> > >(ref result, parameterInfos);
            return(result.ToReverseArray());
        }
Esempio n. 3
0
        /// <summary>
        /// Try parse <paramref name="str"/> into parameters.
        /// </summary>
        /// <param name="str"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public override bool TryParse(string str, ref StructList12 <KeyValuePair <string, string> > parameters)
        {
            if (str == null)
            {
                return(false);
            }
            MatchCollection matches = ParsePattern.Matches(str);

            foreach (Match m in matches)
            {
                if (!m.Success)
                {
                    return(false);
                }
                Group k_key = m.Groups["key"], k_value = m.Groups["value"];
                if (!k_key.Success || !k_value.Success)
                {
                    return(false);
                }
                string parameterName  = UnescapeLiteral(k_key.Value);
                string parameterValue = UnescapeLiteral(k_value.Value);
                parameters.Add(new KeyValuePair <string, string>(parameterName, parameterValue));
            }
            return(true);
        }
 /// <summary>Dispatch <paramref name="events"/></summary>
 /// <param name="events"></param>
 public void DispatchEvents(ref StructList12 <IEvent> events)
 {
     if (events.Count == 1)
     {
         taskFactory.StartNew(processEventsAction, events[0]);
     }
     else if (events.Count >= 2)
     {
         taskFactory.StartNew(processEventsAction, events.ToArray());
     }
 }
        /// <summary>
        /// Send <paramref name="events"/> to observers.
        /// </summary>
        /// <param name="events"></param>
        public void DispatchEvents(ref StructList12 <IEvent> events)
        {
            // Don't send events anymore
            if (IsDisposing)
            {
                return;
            }
            // Nothing to do
            if (events.Count == 0)
            {
                return;
            }
            // Get first dispatcher
            var _dispatcher = events[0].Observer.Dispatcher ?? EventDispatcher.Instance;

            // Dispatch one event
            if (events.Count == 1)
            {
                _dispatcher.DispatchEvent(events[0]); return;
            }
            // All same dispatcher?
            bool allSameDispatcher = true;

            for (int i = 1; i < events.Count; i++)
            {
                var __dispatcher = events[i].Observer.Dispatcher ?? EventDispatcher.Instance;
                if (__dispatcher != _dispatcher)
                {
                    allSameDispatcher = false; break;
                }
            }

            // All events use same dispatcher
            if (allSameDispatcher)
            {
                // Send with struct list
                if (_dispatcher is IEventDispatcherExtended ext)
                {
                    ext.DispatchEvents(ref events); return;
                }
                // Convert to array
                _dispatcher.DispatchEvents(events.ToArray());
            }
            else
            // Events use different dispatchers
            {
                // Dispatch each separately with different dispatchers
                for (int i = 0; i < events.Count; i++)
                {
                    (events[i].Observer.Dispatcher ?? EventDispatcher.Instance).DispatchEvent(events[i]);
                }
            }
        }
Esempio n. 6
0
 /// <summary>
 /// Append a sequence of key,value pairs to a string.
 ///
 /// The format is as following:
 ///   parameterKey:parameterValue:parameterKey:parameterValue:...
 ///
 /// Escape character is backslash.
 ///  \unnnn
 ///  \xnnnn
 ///  \:
 ///  \\
 /// </summary>
 /// <param name="parameters"></param>
 /// <param name="sb"></param>
 /// <returns></returns>
 public override void Print(StructList12 <ILineParameter> parameters, StringBuilder sb)
 {
     for (int i = parameters.Count - 1; i >= 0; i--)
     {
         if (i < parameters.Count - 1)
         {
             sb.Append(':');
         }
         ILineParameter parameter = parameters[i];
         sb.Append(EscapeLiteral(parameter.ParameterName));
         sb.Append(':');
         sb.Append(EscapeLiteral(parameter.ParameterValue));
     }
 }
Esempio n. 7
0
        /// <summary>
        /// Calculate <paramref name="line"/>'s hashcode.
        /// </summary>
        /// <param name="line"></param>
        /// <returns>hashcode or 0 if <paramref name="line"/> was null</returns>
        public int CalculateHashCode(ILine line)
        {
            int result = FNVHashBasis;

            // Non-canonical hashing
            foreach (var comparer in comparers)
            {
                // hash in non-canonical comparer
                result ^= comparer.GetHashCode(line);
            }

            // Canonical hashing
            if (canonicalComparers.Count > 0)
            {
                StructList12 <ILineParameter> list = new StructList12 <ILineParameter>();
                line.GetCanonicalKeys <StructList12 <ILineParameter> >(ref list, parameterInfos);
                for (int i = 0; i < list.Count; i++)
                {
                    var key = list[i];
                    // hash in canonical comparer
                    foreach (var comparer in canonicalComparers)
                    {
                        // Use canonical comparer (the order of canonical comparisons should not be hashed in)
                        result ^= comparer.GetHashCode(key);
                    }
                }
            }

            // Parameter hashing
            if (parameterComparers.Count > 0)
            {
                StructList12 <ILineParameter> list = new StructList12 <ILineParameter>();
                line.GetParameterParts <StructList12 <ILineParameter> >(ref list);
                for (int i = 0; i < list.Count; i++)
                {
                    var key = list[i];
                    // hash in canonical comparer
                    foreach (var comparer in parameterComparers)
                    {
                        result ^= comparer.GetHashCode(key);
                        result *= FNVHashPrime;
                    }
                }
            }

            return(result);
        }
        /// <summary>
        /// Qualify <paramref name="line"/> against the qualifier rules.
        /// </summary>
        /// <param name="qualifier">(optional) qualifier</param>
        /// <param name="line"></param>
        /// <returns>true if line is qualified, false if disqualified</returns>
        public static bool Qualify(this ILineQualifier qualifier, ILine line)
        {
            // Evaluate whole line
            if (qualifier is ILineQualifierEvaluatable eval)
            {
                return(eval.Qualify(line));
            }

            // Evaluate argument
            if (qualifier is ILineArgumentQualifier argumentQualifier)
            {
                if (argumentQualifier.NeedsOccuranceIndex)
                {
                    // Break key into effective parameters with occurance index
                    StructList12 <(ILineArgument, int)> list1 = new StructList12 <(ILineArgument, int)>();
                    line.GetArgumentPartsWithOccurance(ref list1);
                    for (int i = 0; i < list1.Count; i++)
                    {
                        if (!argumentQualifier.QualifyArgument(list1[i].Item1, list1[i].Item2))
                        {
                            return(false);
                        }
                    }
                }
                else
                {
                    // Break key into parameters
                    StructList12 <ILineArgument> list2 = new StructList12 <ILineArgument>();
                    line.GetArgumentParts(ref list2);
                    for (int i = 0; i < list2.Count; i++)
                    {
                        if (!argumentQualifier.QualifyArgument(list2[i], -1))
                        {
                            return(false);
                        }
                    }
                }
            }

            // no criteria, accept all
            return(true);
        }
Esempio n. 9
0
        /// <summary>
        /// Prune out arguments that are disqualified by <paramref name="qualifier"/>.
        /// </summary>
        /// <param name="line"></param>
        /// <param name="qualifier">Argument qualifier that is used for determining which parts to keep in the line</param>
        /// <param name="lineFactory">(optional) extra line factory</param>
        /// <returns>a modified <paramref name="line"/></returns>
        /// <exception cref="LineException"></exception>
        public static ILine Prune(this ILine line, ILineQualifier qualifier, ILineFactory lineFactory = null)
        {
            // Qualified parts to append. Order: tail to root.
            StructList12 <ILineArgument> list = new StructList12 <ILineArgument>();

            ILineArgumentQualifier lineArgumentQualifier = qualifier as ILineArgumentQualifier;

            // Earliest qualified line part. The start tail, where to start appending qualified parts
            ILine startTail = line;

            // Line part's arguments. Order: root to tail
            StructList8 <ILineArgument> tmp = new StructList8 <ILineArgument>();
            // Start tail buffered args. Order: root to tail
            StructList8 <ILineArgument> startTailArgsBuffer = new StructList8 <ILineArgument>();

            // Add parts
            for (ILine l = line; l != null; l = l.GetPreviousPart())
            {
                tmp.Clear();
                if (l is ILineArgumentEnumerable lineArguments)
                {
                    foreach (ILineArgument lineArgument in lineArguments)
                    {
                        tmp.Add(lineArgument);
                    }
                }
                if (l is ILineArgument argument)
                {
                    tmp.Add(argument);
                }

                // Now qualify
                bool   linePartQualifies = true;
                string parameterName, parameterValue;
                for (int i = tmp.Count - 1; i >= 0; i--)
                {
                    ILineArgument a = tmp[i];

                    bool argumentQualifies = true;
                    if (lineArgumentQualifier != null)
                    {
                        // Qualify as an argument.
                        if (!a.IsNonCanonicalKey())
                        {
                            argumentQualifies = lineArgumentQualifier.QualifyArgument(a);
                        }
                        // Qualify as non-canonical parameter
                        else if (a.TryGetParameter(out parameterName, out parameterValue))
                        {
                            // Calculate occurance index
                            int occIx = -1;
                            if (lineArgumentQualifier.NeedsOccuranceIndex)
                            {
                                occIx = 0;
                                for (int j = i - 1; j >= 0; j--)
                                {
                                    ILineArgument b = list[j];
                                    string        parameterName2, parameterValue2;
                                    if (b.TryGetParameter(out parameterName2, out parameterValue2))
                                    {
                                        continue;
                                    }
                                    if (parameterValue2 != null && parameterName == parameterName2)
                                    {
                                        occIx++;
                                    }
                                }
                            }
                            argumentQualifies = lineArgumentQualifier.QualifyArgument(a, occIx);
                        }
                    }
                    if (!argumentQualifies)
                    {
                        tmp.RemoveAt(i);
                    }
                    linePartQualifies &= argumentQualifies;
                }

                // This part didn't qualify
                if (!linePartQualifies)
                {
                    // Append previous start tail to append args
                    if (startTailArgsBuffer.Count > 0)
                    {
                        for (int i = 0; i < startTailArgsBuffer.Count; i++)
                        {
                            list.Add(startTailArgsBuffer[i]);
                        }
                        startTailArgsBuffer.Clear();
                        startTail = null;
                    }
                    // Add parts that did qualify to append list
                    for (int i = 0; i < tmp.Count; i++)
                    {
                        list.Add(tmp[i]);
                    }
                    // preceding part might be better for start tail
                    startTail = l.GetPreviousPart();
                }
                else
                // This part qualified
                {
                    // Add to start tail buffer, in case preceding startTail fails qualifications
                    for (int i = 0; i < tmp.Count; i++)
                    {
                        startTailArgsBuffer.Add(tmp[i]);
                    }
                }
            }


            // Append qualified parts.
            ILineFactory appender1 = null;

            line.TryGetAppender(out appender1);

            // Nothing qualified, no start, create dummy
            if (startTail == null && list.Count == 0)
            {
                // Create dummy
                ILineFactory appender2 = null;
                line.TryGetAppender(out appender2);
                ILinePart dummy = null;
                if (lineFactory == null || !lineFactory.TryCreate(null, out dummy))
                {
                    if (appender2 == null || !appender2.TryCreate(null, out dummy))
                    {
                        throw new LineException(line, $"LineFactory doesn't have capability to create {nameof(ILinePart)}");
                    }
                }
                return(dummy);
            }

            // Append parts
            ILine result = startTail;

            for (int i = list.Count - 1; i >= 0; i--)
            {
                ILineArgument arg = list[i];
                if (lineFactory == null || !lineFactory.TryCreate(result, arg, out result))
                {
                    if (appender1 == null || !appender1.TryCreate(result, arg, out result))
                    {
                        throw new LineException(line, $"LineFactory doesn't have capability to concat {arg}");
                    }
                }
            }
            return(result);
        }
            /// <summary>
            /// Forward event
            /// </summary>
            /// <param name="sender"></param>
            void OnEvent(object sender)
            {
                var _observer = Observer;

                if (_observer == null)
                {
                    return;
                }

                // Create new token
                if (!IsDisposing)
                {
                    this.changeToken = FileProvider.Watch(Filter);
                }

                // Get new snapshot
                DateTimeOffset time = DateTimeOffset.UtcNow;
                Dictionary <string, IEntry> newSnapshot = ReadSnapshot();

                // List of events
                StructList12 <IEvent> events = new StructList12 <IEvent>();

                // Find adds
                foreach (KeyValuePair <string, IEntry> newEntry in newSnapshot)
                {
                    string path = newEntry.Key;
                    // Find matching previous entry
                    IEntry prevEntry;
                    if (previousSnapshot.TryGetValue(path, out prevEntry))
                    {
                        // Send change event
                        if (!EntryComparer.PathDateLengthTypeEqualityComparer.Equals(newEntry.Value, prevEntry))
                        {
                            events.Add(new ChangeEvent(this, time, path));
                        }
                    }
                    // Send create event
                    else
                    {
                        events.Add(new CreateEvent(this, time, path));
                    }
                }
                // Find Deletes
                foreach (KeyValuePair <string, IEntry> oldEntry in previousSnapshot)
                {
                    string path = oldEntry.Key;
                    if (!newSnapshot.ContainsKey(path))
                    {
                        events.Add(new DeleteEvent(this, time, path));
                    }
                }

                // Replace entires
                previousSnapshot = newSnapshot;

                // Dispatch events
                if (events.Count > 0)
                {
                    ((FileSystemBase)this.FileSystem).DispatchEvents(ref events);
                }

                // Start watching again
                if (!IsDisposing)
                {
                    this.watcher = changeToken.RegisterChangeCallback(OnEvent, this);
                }
            }
Esempio n. 11
0
        /// <summary>
        /// Compare <paramref name="x"/> and <paramref name="y"/> for equality.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns>true if equal</returns>
        public bool Equals(ILine x, ILine y)
        {
            bool xIsNull = x == null, yIsNull = y == null;

            if (xIsNull && yIsNull)
            {
                return(true);
            }
            if (xIsNull || yIsNull)
            {
                return(false);
            }
            if (Object.ReferenceEquals(x, y))
            {
                return(true);
            }

            // Test if cached hash-codes mismatch.
            if (this == LineComparer.key && x is ILineDefaultHashCode x_code && y is ILineDefaultHashCode y_code)
            {
                if (x_code.GetDefaultHashCode() != y_code.GetDefaultHashCode())
                {
                    return(false);
                }
            }

            // Regular comparers
            foreach (var comparer in comparers)
            {
                if (!comparer.Equals(x, y))
                {
                    return(false);
                }
            }

            // Canonical key part comparers
            if (canonicalComparers.Count > 0)
            {
                StructList12 <ILineParameter> x_canonicals = new StructList12 <ILineParameter>(), y_canonicals = new StructList12 <ILineParameter>();
                x.GetCanonicalKeys <StructList12 <ILineParameter> >(ref x_canonicals, parameterInfos);
                y.GetCanonicalKeys <StructList12 <ILineParameter> >(ref y_canonicals, parameterInfos);
                if (x_canonicals.Count != y_canonicals.Count)
                {
                    return(false);
                }
                for (int i = 0; i < x_canonicals.Count; i++)
                {
                    ILineParameter x_key = x_canonicals[i], y_key = y_canonicals[i];
                    // Run comparers
                    for (int j = 0; j < canonicalComparers.Count; j++)
                    {
                        if (!canonicalComparers[j].Equals(x_key, y_key))
                        {
                            return(false);
                        }
                    }
                }
            }

            // Parameter part comparers
            if (parameterComparers.Count > 0)
            {
                StructList12 <ILineParameter> x_parameters = new StructList12 <ILineParameter>(), y_parameters = new StructList12 <ILineParameter>();
                x.GetParameterParts <StructList12 <ILineParameter> >(ref x_parameters);
                y.GetParameterParts <StructList12 <ILineParameter> >(ref y_parameters);
                if (x_parameters.Count != y_parameters.Count)
                {
                    return(false);
                }
                for (int i = 0; i < x_parameters.Count; i++)
                {
                    ILineParameter x_key = x_parameters[i], y_key = y_parameters[i];
                    // Run comparers
                    for (int j = 0; j < parameterComparers.Count; j++)
                    {
                        if (!parameterComparers[j].Equals(x_key, y_key))
                        {
                            return(false);
                        }
                    }
                }
            }

            return(true);
        }
        /// <summary>
        /// Evaluate placeholders into string values.
        /// </summary>
        /// <param name="line">original requesting line</param>
        /// <param name="resolvedLine">(optional Line that was matched from IAsset or inlines</param>
        /// <param name="pluralLine">(optional) Line that was matched from IAsset or inlines for plural value</param>
        /// <param name="placeholders"></param>
        /// <param name="features">contextual data</param>
        /// <param name="placeholder_values">collection where strings are placed, one for each placeholder</param>
        /// <param name="culture">the culture in which to evaluate</param>
        void EvaluatePlaceholderValues(ILine line, ILine resolvedLine, ILine pluralLine, IPlaceholder[] placeholders, ref LineFeatures features, ref StructList12 <string> placeholder_values, CultureInfo culture)
        {
            PlaceholderExpressionEvaluator placeholder_evaluator = new PlaceholderExpressionEvaluator();

            placeholder_evaluator.Args = features.ValueArgs;
            placeholder_evaluator.FunctionEvaluationCtx.Culture        = culture;
            placeholder_evaluator.FunctionEvaluationCtx.Line           = line;
            placeholder_evaluator.FunctionEvaluationCtx.ResolvedLine   = resolvedLine;
            placeholder_evaluator.FunctionEvaluationCtx.PluralLine     = pluralLine;
            placeholder_evaluator.FunctionEvaluationCtx.StringResolver = this;
            placeholder_evaluator.FunctionEvaluationCtx.EnumResolver   = EnumResolver;
            if (features.FormatProviders.Count == 1)
            {
                placeholder_evaluator.FunctionEvaluationCtx.FormatProvider = features.FormatProviders[0];
            }
            else if (features.FormatProviders.Count > 1)
            {
                placeholder_evaluator.FunctionEvaluationCtx.FormatProvider = new FormatProviderComposition(features.FormatProviders.ToArray());
            }
            if (features.Functions.Count == 1)
            {
                placeholder_evaluator.FunctionEvaluationCtx.Functions = features.Functions[0];
            }
            else if (features.Functions.Count > 1)
            {
                placeholder_evaluator.FunctionEvaluationCtx.Functions = new FunctionsMap(features.Functions);
            }
            for (int i = 0; i < placeholders.Length; i++)
            {
                try
                {
                    // Get placeholder
                    IPlaceholder ph = placeholders[i];
                    // Evaluate value
                    string ph_value = placeholder_evaluator.toString(placeholder_evaluator.Evaluate(ph.Expression));
                    // Add to array
                    placeholder_values.Add(ph_value);
                    // Update code
                    features.Status.UpPlaceholder(placeholder_evaluator.Status);
                }
                catch (Exception e)
                {
                    // Log exceptions
                    features.Log(e);
                    // Mark error
                    features.Status.UpPlaceholder(LineStatus.PlaceholderErrorExpressionEvaluation);
                    // Put empty value
                    placeholder_values.Add(null);
                }
            }
        }
        /// <summary>
        /// Resolve <paramref name="key"/> into <see cref="IString"/>, but without applying format arguments.
        ///
        /// If the <see cref="IString"/> contains plural categories, then matches into the applicable plurality case.
        /// </summary>
        /// <param name="key"></param>
        /// <returns>format string</returns>
        public IString ResolveFormatString(ILine key)
        {
            // Extract parameters from line
            LineFeatures features = new LineFeatures {
                Resolvers = Resolvers
            };

            // Scan features
            try
            {
                features.ScanFeatures(key);
            }
            catch (Exception e)
            {
                features.Log(e);
                features.Status.UpResolve(LineStatus.ResolveFailedException);
                return(new StatusString(null, features.Status));
            }

            // Resolve key to line
            CultureInfo culture = features.Culture;
            ILine       line    = ResolveKeyToLine(key, ref features, ref culture);

            // No line or value
            if (line == null || !features.HasValue)
            {
                features.Status.UpResolve(LineStatus.ResolveFailedNoValue);
                LineString str = new LineString(key, (Exception)null, features.Status);
                features.Log(str);
                return(new StatusString(null, features.Status));
            }

            // Parse value
            IString value = features.EffectiveString;

            features.Status.Up(value.Status);

            // Value has error
            if (value.Parts == null || value.Status.Failed())
            {
                LineString str = new LineString(key, (Exception)null, features.Status);
                features.Log(str);
                return(new StatusString(null, features.Status));
            }

            // Plural Rules
            if (value.HasPluralRules())
            {
                if (features.PluralRules != null)
                {
                    // Evaluate expressions in placeholders into strings
                    StructList12 <string> placeholder_values = new StructList12 <string>();
                    CultureInfo           culture_for_format = features.Culture;
                    if (culture_for_format == null && features.CulturePolicy != null)
                    {
                        CultureInfo[] cultures = features.CulturePolicy.Cultures; if (cultures != null && cultures.Length > 0)
                        {
                            culture_for_format = cultures[0];
                        }
                    }
                    if (culture_for_format == null)
                    {
                        culture_for_format = CultureInfo.InvariantCulture;
                    }
                    EvaluatePlaceholderValues(key, line, null, value.Placeholders, ref features, ref placeholder_values, culture_for_format);

                    // Create permutation configuration
                    PluralCasePermutations permutations = new PluralCasePermutations(line);
                    for (int i = 0; i < value.Placeholders.Length; i++)
                    {
                        // Get placeholder
                        IPlaceholder placeholder = value.Placeholders[i];
                        // No plural category in this placeholder
                        if (placeholder.PluralCategory == null)
                        {
                            continue;
                        }
                        // Placeholder value after evaluation
                        string ph_value = placeholder_values[i];
                        // Placeholder evaluated value
                        IPluralNumber placeholderValue = ph_value == null ? DecimalNumber.Empty : new DecimalNumber.Text(ph_value?.ToString(), culture);
                        // Add placeholder to permutation configuration
                        permutations.AddPlaceholder(placeholder, placeholderValue, features.PluralRules, culture?.Name ?? "");
                    }

                    // Find first value that matches permutations
                    features.CulturePolicy = null;
                    features.String        = null;
                    features.StringText    = null;
                    for (int i = 0; i < permutations.Count - 1; i++)
                    {
                        // Create key with plurality cases
                        ILine key_with_plurality = permutations[i];
                        // Search line with the key
                        ILine line_for_plurality_arguments = ResolveKeyToLine(key_with_plurality, ref features, ref culture);
                        // Got no match
                        if (line_for_plurality_arguments == null)
                        {
                            continue;
                        }
                        // Parse value
                        IString value_for_plurality = line_for_plurality_arguments.GetString(Resolvers);
                        // Add status from parsing the value
                        features.Status.Up(value_for_plurality.Status);
                        // Value has error
                        if (value_for_plurality.Parts == null || value_for_plurality.Status.Failed())
                        {
                            LineString str = new LineString(key, (Exception)null, features.Status);
                            features.Log(str);
                            return(new StatusString(null, features.Status));
                        }
                        // Return with match
                        features.Status.UpPlurality(LineStatus.PluralityOkMatched);
                        // Update status codes
                        features.Status.Up(value_for_plurality.Status);
                        // Return values
                        value = value_for_plurality;
                        line  = line_for_plurality_arguments;
                        break;
                    }
                }
                else
                {
                    // Plural rules were not found
                    features.Status.Up(LineStatus.PluralityErrorRulesNotFound);
                }
            }
            else
            {
                // Plurality feature was not used.
                features.Status.UpPlurality(LineStatus.PluralityOkNotUsed);
            }

            return(value);
        }
        /// <summary>
        /// Resolve <paramref name="key"/> into <see cref="LineString"/> with format arguments applied.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public LineString ResolveString(ILine key)
        {
            // Extract parameters from line
            LineFeatures features = new LineFeatures {
                Resolvers = Resolvers
            };

            // Scan features
            try
            {
                features.ScanFeatures(key);
            }
            catch (Exception e)
            {
                features.Log(e);
                features.Status.UpResolve(LineStatus.ResolveFailedException);
                return(new LineString(key, e, features.Status));
            }

            try
            {
                // Resolve key to line
                CultureInfo culture = features.Culture;
                ILine       line    = ResolveKeyToLine(key, ref features, ref culture);

                // No line or value
                if (line == null || !features.HasValue)
                {
                    features.Status.UpResolve(LineStatus.ResolveFailedNoValue);
                    LineString str = new LineString(key, (Exception)null, features.Status);
                    features.Log(str);
                    return(str);
                }

                // Parse value
                IString value = features.EffectiveString;
                features.Status.Up(value.Status);

                // Value has error
                if (value.Parts == null || value.Status.Failed())
                {
                    LineString str = new LineString(key, (Exception)null, features.Status);
                    features.Log(str);
                    return(str);
                }

                // Evaluate expressions in placeholders into strings
                StructList12 <string> placeholder_values = new StructList12 <string>();
                CultureInfo           culture_for_format = features.Culture;
                if (culture_for_format == null && features.CulturePolicy != null)
                {
                    CultureInfo[] cultures = features.CulturePolicy.Cultures; if (cultures != null && cultures.Length > 0)
                    {
                        culture_for_format = cultures[0];
                    }
                }
                if (culture_for_format == null)
                {
                    culture_for_format = CultureInfo.InvariantCulture;
                }
                EvaluatePlaceholderValues(key, line, null, value.Placeholders, ref features, ref placeholder_values, culture_for_format);

                // Plural Rules
                if (value.HasPluralRules())
                {
                    if (features.PluralRules != null)
                    {
                        // Create permutation configuration
                        PluralCasePermutations permutations = new PluralCasePermutations(line);
                        for (int i = 0; i < value.Placeholders.Length; i++)
                        {
                            // Get placeholder
                            IPlaceholder placeholder = value.Placeholders[i];
                            // No plural category in this placeholder
                            if (placeholder.PluralCategory == null)
                            {
                                continue;
                            }
                            // Placeholder value after evaluation
                            string ph_value = placeholder_values[i];
                            // Placeholder evaluated value
                            IPluralNumber placeholderValue = ph_value == null ? DecimalNumber.Empty : new DecimalNumber.Text(ph_value?.ToString(), culture);
                            // Add placeholder to permutation configuration
                            permutations.AddPlaceholder(placeholder, placeholderValue, features.PluralRules, culture?.Name ?? "");
                        }

                        if (permutations.ArgumentCount <= MaxPluralArguments)
                        {
                            // Find first value that matches permutations
                            features.CulturePolicy = null;
                            features.String        = null;
                            features.StringText    = null;
                            for (int i = 0; i < permutations.Count - 1; i++)
                            {
                                // Create key with plurality cases
                                ILine key_with_plurality = permutations[i];
                                // Search line with the key
                                ILine line_for_plurality_arguments = ResolveKeyToLine(key_with_plurality, ref features, ref culture);
                                // Got no match
                                if (line_for_plurality_arguments == null)
                                {
                                    continue;
                                }
                                // Scan value
                                try
                                {
                                    features.ScanValueFeature(line_for_plurality_arguments);
                                }
                                catch (Exception e)
                                {
                                    features.Log(e);
                                    features.Status.Up(LineStatus.FailedUnknownReason);
                                    return(new LineString(key, e, features.Status));
                                }
                                // Parse value
                                IString value_for_plurality = features.EffectiveString;
                                // Add status from parsing the value
                                features.Status.Up(value_for_plurality.Status);
                                // Value has error
                                if (value_for_plurality.Parts == null || value_for_plurality.Status.Failed())
                                {
                                    LineString str = new LineString(key, (Exception)null, features.Status);
                                    features.Log(str);
                                    return(str);
                                }
                                // Return with match
                                features.Status.UpPlurality(LineStatus.PluralityOkMatched);
                                // Evaluate placeholders again
                                if (!EqualPlaceholders(value, value_for_plurality))
                                {
                                    placeholder_values.Clear(); EvaluatePlaceholderValues(key, line, line_for_plurality_arguments, value_for_plurality.Placeholders, ref features, ref placeholder_values, culture);
                                }
                                // Update status codes
                                features.Status.Up(value_for_plurality.Status);
                                // Return values
                                value = value_for_plurality;
                                line  = line_for_plurality_arguments;
                                break;
                            }
                        }
                        else
                        {
                            features.Status.UpPlaceholder(LineStatus.PluralityErrorMaxPluralArgumentsExceeded);
                        }
                    }
                    else
                    {
                        // Plural rules were not found
                        features.Status.Up(LineStatus.PluralityErrorRulesNotFound);
                    }
                }
                else
                {
                    // Plurality feature was not used.
                    features.Status.UpPlurality(LineStatus.PluralityOkNotUsed);
                }

                // Put string together
                string text = null;

                if (value == null || value.Parts == null)
                {
                    text = null;
                }

                // Only one part
                else if (value.Parts.Length == 1)
                {
                    if (value.Parts[0].Kind == StringPartKind.Text)
                    {
                        text = value.Parts[0].Text;
                        features.Status.UpStringFormat(LineStatus.StringFormatOkString);
                    }
                    else if (value.Parts[0].Kind == StringPartKind.Placeholder)
                    {
                        text = placeholder_values[0];
                        features.Status.UpStringFormat(LineStatus.StringFormatOkString);
                    }
                }
                // Compile multiple parts
                else
                {
                    // Calculate length
                    int length = 0;
                    for (int i = 0; i < value.Parts.Length; i++)
                    {
                        IStringPart part     = value.Parts[i];
                        string      partText = part.Kind switch { StringPartKind.Text => part.Text, StringPartKind.Placeholder => placeholder_values[((IPlaceholder)part).PlaceholderIndex], _ => null };
                        if (partText != null)
                        {
                            length += partText.Length;
                        }
                    }

                    // Copy characters
                    char[] arr = new char[length];
                    int    ix  = 0;
                    for (int i = 0; i < value.Parts.Length; i++)
                    {
                        IStringPart part = value.Parts[i];
                        string      str  = part.Kind switch { StringPartKind.Text => part.Text, StringPartKind.Placeholder => placeholder_values[((IPlaceholder)part).PlaceholderIndex], _ => null };
                        if (str != null)
                        {
                            str.CopyTo(0, arr, ix, str.Length); ix += str.Length;
                        }
                    }

                    // String
                    text = new string(arr);
                    features.Status.UpStringFormat(LineStatus.StringFormatOkString);
                }

                // Create result
                LineString result = new LineString(key, text, features.Status);

                // Log
                features.Log(result);

                // Return
                return(result);
            } catch (Exception e)
            {
                // Capture unexpected error
                features.Log(e);
                features.Status.UpResolve(LineStatus.ResolveFailedException);
                LineString lineString = new LineString(key, e, features.Status);
                features.Log(lineString);
                return(lineString);
            }
        }