Пример #1
0
        private static IEnumerable <IParseResult <IEnumerable <TResult> > > AtLeastIterator <TSource, TSeparator, TResult>(
            ICursor <TSource> source,
            IParser <TSource, TResult> parser,
            int count,
            int maximum,
            IParser <TSource, TSeparator> separator,
            bool nonGreedy)
        {
            Contract.Assume(source != null);
            Contract.Assume(source.IsForwardOnly);
            Contract.Assume(parser != null);
            Contract.Assume(count >= 0);
            Contract.Assume(maximum == -1 || maximum >= count);
            Contract.Assume(maximum != 0);

            // TODO: Update this method to properly support multi-result parsers.

            /* The current implementation just uses Math.Max for the lengths and aggregates all of the results into a single list.
             * The correct behavior is more like a SelectMany query, so consider using the new SelectMany overload that AllParser uses.
             */

            /* This method is optimized to prevent stack overflows due to two factors: recursion and using Skip to move the source cursor.
             *
             * The previous implementation used recursive calls to NoneOrMore, in which there was a linear relationship between the number
             * of stack frames and the number of elements in the input sequence.  As an input sequence grew and the parser continued matching
             * elements, the number of calls to the Skip operator (via the Remainder extension) grew linearly, and so did the number of branches
             * due to NoneOrMore using the Or operator, which not only added the Or operator to the stack but added all of the calls to the
             * quantified parser between the stack frames that Or added, for every subsequent element in the sequence that the parser matched.
             */
            using (var branch = source.Branch())
            {
                var list = new List <TResult>();

                int total       = 0;
                int totalLength = 0;

                bool iterate = true;

                if (nonGreedy && count == 0)
                {
                    using (var lookAhead = new LookAheadParseResult <IEnumerable <TResult> >(Enumerable.Empty <TResult>(), length: 0))
                    {
                        yield return(lookAhead);

                        Contract.Assume(lookAhead.Succeeded.HasValue);

                        if (lookAhead.Succeeded.Value)
                        {
                            iterate = false;
                        }
                    }
                }

                while (iterate)
                {
                    bool hasResult          = false;
                    bool hasSeparatorResult = false;

                    int length          = 0;
                    int separatorLength = 0;

                    foreach (var result in parser.Parse(branch))
                    {
                        hasResult = true;
                        length    = Math.Max(length, result.Length);

                        list.Add(result.Value);
                    }

                    branch.Move(length);

                    if (separator != null)
                    {
                        foreach (var separatorResult in separator.Parse(branch))
                        {
                            hasSeparatorResult = true;
                            separatorLength    = Math.Max(separatorLength, separatorResult.Length);
                        }

                        branch.Move(separatorLength);
                    }

                    if (hasResult)
                    {
                        totalLength += length + separatorLength;

                        if (total < (maximum == -1 ? count : maximum))
                        {
                            total++;

                            if (total == maximum)
                            {
                                break;
                            }
                        }

                        if (separator == null || hasSeparatorResult)
                        {
                            if (nonGreedy && total >= count)
                            {
                                using (var lookAhead = new LookAheadParseResult <IEnumerable <TResult> >(list.AsReadOnly(), totalLength))
                                {
                                    yield return(lookAhead);

                                    Contract.Assume(lookAhead.Succeeded.HasValue);

                                    if (lookAhead.Succeeded.Value)
                                    {
                                        break;
                                    }
                                }
                            }

                            continue;
                        }
                    }

                    break;
                }

                if (total >= count)
                {
                    yield return(new ParseResult <IEnumerable <TResult> >(list.AsReadOnly(), totalLength));
                }
            }
        }
Пример #2
0
        private static IObservableParser <TSource, IObservable <TResult> > AtLeast <TSource, TSeparator, TResult>(
            this IObservableParser <TSource, TResult> parser,
            string name,
            int count,
            int maximum = -1,
            IObservableParser <TSource, TSeparator> separator = null,
            bool nonGreedy = false)
        {
            Contract.Requires(parser != null);
            Contract.Requires(!string.IsNullOrEmpty(name));
            Contract.Requires(count >= 0);
            Contract.Requires(maximum == -1 || maximum >= count);
            Contract.Requires(maximum != 0);
            Contract.Ensures(Contract.Result <IObservableParser <TSource, IObservable <TResult> > >() != null);

            // TODO: Update this method to properly support multi-result parsers.

            /* The current implementation just uses Math.Max for the lengths and aggregates all of the results into a single list.
             * The correct behavior is more like a SelectMany query, so consider using the new SelectMany overload that AllObservableParser uses.
             */

            /* This method is optimized to prevent stack overflows due to two factors: recursion and using Skip to move the source cursor.
             *
             * The previous implementation used recursive calls to NoneOrMore, in which there was a linear relationship between the number
             * of stack frames and the number of elements in the input sequence.  As an input sequence grew and the parser continued matching
             * elements, the number of calls to the Skip operator (via the Remainder extension) grew linearly, and so did the number of branches
             * due to NoneOrMore using the Or operator, which not only added the Or operator to the stack but added all of the calls to the
             * quantified parser between the stack frames that Or added, for every subsequent element in the sequence that the parser matched.
             */
            return(parser.Yield <TSource, TResult, IObservable <TResult> >(
                       name,
                       (source, observer) =>
            {
                Contract.Requires(source.IsForwardOnly);

                var branch = source.Branch();

                var list = new List <TResult>();

                int total = 0;
                int totalLength = 0;

                Action onCompleted = () =>
                {
                    if (total >= count)
                    {
                        observer.OnNext(new ParseResult <IObservable <TResult> >(list.ToObservable(Scheduler.Immediate), totalLength));
                    }

                    observer.OnCompleted();
                };

                var subscription = new SerialDisposable();

                Func <IDisposable> start = () =>
                                           Scheduler.Immediate.Schedule(self =>
                {
                    bool hasResult = false;
                    bool hasSeparatorResult = false;

                    int length = 0;
                    int separatorLength = 0;

                    Action currentCompleted = () =>
                    {
                        if (hasResult)
                        {
                            totalLength += length + separatorLength;

                            if (total < (maximum == -1 ? count : maximum))
                            {
                                total++;
                            }

                            if (total != maximum && (separator == null || hasSeparatorResult))
                            {
                                if (nonGreedy && total >= count)
                                {
                                    var lookAhead = new LookAheadParseResult <IObservable <TResult> >(list.ToObservable(Scheduler.Immediate), totalLength);

                                    subscription.SetDisposableIndirectly(() => new CompositeDisposable(
                                                                             lookAhead,
                                                                             lookAhead.Subscribe(success =>
                                    {
                                        if (success)
                                        {
                                            onCompleted();
                                        }
                                        else
                                        {
                                            self();
                                        }
                                    })));

                                    observer.OnNext(lookAhead);
                                    return;
                                }
                                else
                                {
                                    self();
                                    return;
                                }
                            }
                        }

                        onCompleted();
                    };

                    subscription.SetDisposableIndirectly(() =>
                                                         parser.Parse(branch).SubscribeSafe(
                                                             result =>
                    {
                        hasResult = true;
                        length = Math.Max(length, result.Length);

                        list.Add(result.Value);
                    },
                                                             observer.OnError,
                                                             () =>
                    {
                        branch.Move(length);

                        if (separator == null)
                        {
                            currentCompleted();
                        }
                        else
                        {
                            subscription.SetDisposableIndirectly(() =>
                                                                 separator.Parse(branch).SubscribeSafe(
                                                                     separatorResult =>
                            {
                                hasSeparatorResult = true;
                                separatorLength = Math.Max(separatorLength, separatorResult.Length);
                            },
                                                                     observer.OnError,
                                                                     () =>
                            {
                                branch.Move(separatorLength);

                                currentCompleted();
                            }));
                        }
                    }));
                });

                if (nonGreedy && count == 0)
                {
                    var startSubscription = new SingleAssignmentDisposable();

                    var lookAhead = new LookAheadParseResult <IObservable <TResult> >(Observable.Empty <TResult>(), length: 0);

                    var lookAheadSubscription = lookAhead.Subscribe(success =>
                    {
                        if (success)
                        {
                            onCompleted();
                        }
                        else
                        {
                            startSubscription.Disposable = start();
                        }
                    });

                    observer.OnNext(lookAhead);

                    return new CompositeDisposable(branch, subscription, lookAhead, lookAheadSubscription, startSubscription);
                }
                else
                {
                    var startSubscription = start();

                    return new CompositeDisposable(branch, subscription, startSubscription);
                }
            }));
        }