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)); } } }
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); } })); }