/// <summary> /// Yields success when the specified <paramref name="parser"/> does not match. /// </summary> /// <typeparam name="TSource">The type of the source elements.</typeparam> /// <typeparam name="TResult">The type of the elements that are generated from parsing the source elements.</typeparam> /// <param name="parser">The parser for which any match results in failure.</param> /// <returns>A parser that yields failure when the specified <paramref name="parser"/> matches or /// an empty sequence to indicate success when it does not match.</returns> public static IObservableParser <TSource, IObservable <TResult> > None <TSource, TResult>( this IObservableParser <TSource, TResult> parser) { Contract.Requires(parser != null); Contract.Ensures(Contract.Result <IObservableParser <TSource, IObservable <TResult> > >() != null); if (parser is IObservableParserCursor <TSource> ) { return(parser.AtEndOfSequence()); } else { return(parser.Yield <TSource, TResult, IObservable <TResult> >( "None", (source, observer) => { return parser.Parse(source).Any().SubscribeSafe( any => { if (!any) { observer.OnNext(ObservableParseResult.SuccessMany <TResult>(length: 0)); } }, observer.OnError, observer.OnCompleted); })); } }
/// <summary> /// Matches the specified <paramref name="parser"/> or yields success without a value when it does not match. /// </summary> /// <typeparam name="TSource">The type of the source elements.</typeparam> /// <typeparam name="TResult">The type of the elements of the result sequences that are generated from parsing the source elements.</typeparam> /// <param name="parser">The parser that might produce matches.</param> /// <returns>A parser that yields matches from the specified <paramref name="parser"/> or /// an empty observable sequence to indicate success when it does not match.</returns> public static IObservableParser <TSource, IObservable <TResult> > Maybe <TSource, TResult>( this IObservableParser <TSource, IObservable <TResult> > parser) { Contract.Requires(parser != null); Contract.Ensures(Contract.Result <IObservableParser <TSource, IObservable <TResult> > >() != null); return(parser.Yield( "Maybe", (source, observer) => { bool hasResult = false; return parser.Parse(source).SubscribeSafe( result => { hasResult = true; observer.OnNext(result); }, observer.OnError, () => { if (!hasResult) { observer.OnNext(ObservableParseResult.SuccessMany <TResult>(length: 0)); } observer.OnCompleted(); }); })); }
/// <summary> /// Matches the specified <paramref name="parser"/> or yields the specified default result if there are /// no matches. /// </summary> /// <typeparam name="TSource">The type of the source elements.</typeparam> /// <typeparam name="TResult">The type of the elements that are generated from parsing the source elements.</typeparam> /// <param name="parser">The parser that might produce matches.</param> /// <param name="defaultResult">The value that is yielded if the specified <paramref name="parser"/> does not match.</param> /// <returns>A parser that yields matches from the specified <paramref name="parser"/> or the specified default result /// if the <paramref name="parser"/> does not match.</returns> public static IObservableParser <TSource, TResult> WithDefault <TSource, TResult>( this IObservableParser <TSource, TResult> parser, TResult defaultResult) { Contract.Requires(parser != null); Contract.Ensures(Contract.Result <IObservableParser <TSource, TResult> >() != null); return(parser.Yield( "WithDefault", (source, observer) => { bool hasResult = false; return parser.Parse(source).SubscribeSafe( result => { hasResult = true; observer.OnNext(result); }, observer.OnError, () => { if (!hasResult) { observer.OnNext(ParseResult.Create(defaultResult, length: 0)); } observer.OnCompleted(); }); })); }
/// <summary> /// Projects each match from the specified <paramref name="parser"/> into a singleton observable sequence /// that contains the match's value. /// </summary> /// <typeparam name="TSource">The type of the source elements.</typeparam> /// <typeparam name="TResult">The type of the elements that are generated from parsing the source elements.</typeparam> /// <param name="parser">The parser from which matches will be projected into singleton observable sequences.</param> /// <returns>A parser that yields matches from the specified <paramref name="parser"/> projected into singleton /// observable sequences.</returns> public static IObservableParser <TSource, IObservable <TResult> > Amplify <TSource, TResult>( this IObservableParser <TSource, TResult> parser) { Contract.Requires(parser != null); Contract.Ensures(Contract.Result <IObservableParser <TSource, IObservable <TResult> > >() != null); return(parser.Yield("Amplify", source => parser.Parse(source).Select(result => result.YieldMany()))); }
/// <summary> /// Converts greedy matches from the specified <paramref name="parser"/> into matches that /// have a length of zero. /// </summary> /// <typeparam name="TSource">The type of the source elements.</typeparam> /// <typeparam name="TResult">The type of the elements that are generated from parsing the source elements.</typeparam> /// <param name="parser">The greedy parser to be made into a non-greedy parser.</param> /// <returns>A parser that converts the greedy matches from the specified <paramref name="parser"/> into /// matches that have a length of zero.</returns> public static IObservableParser <TSource, TResult> NonGreedy <TSource, TResult>( this IObservableParser <TSource, TResult> parser) { Contract.Requires(parser != null); Contract.Ensures(Contract.Result <IObservableParser <TSource, TResult> >() != null); return(parser.Yield("NonGreedy", source => parser.Parse(source).Select(result => result.Yield(length: 0)))); }
private static IObservable <IParseResult <TResult> > SelectManyInternal <TSource, TFirstResult, TSecondResult, TResult>( IObservableCursor <TSource> source, IObservableParser <TSource, TFirstResult> firstParser, Func <TFirstResult, IObservableParser <TSource, TSecondResult> > secondSelector, Func <TFirstResult, TSecondResult, TResult> resultSelector, Func <IParseResult <TFirstResult>, IParseResult <TSecondResult>, int> lengthSelector = null) { Contract.Requires(source != null); Contract.Requires(source.IsForwardOnly); Contract.Requires(firstParser != null); Contract.Requires(secondSelector != null); Contract.Requires(resultSelector != null); Contract.Ensures(Contract.Result <IObservable <IParseResult <TResult> > >() != null); return(from first in firstParser.Parse(source) from second in Observable.Create <IParseResult <TSecondResult> >( observer => { var lookAhead = first as ILookAheadParseResult <TFirstResult>; bool hasResult = false; var remainder = source.Remainder(first.Length); return secondSelector(first.Value) .Parse(remainder) .Finally(remainder.Dispose) .Subscribe( second => { hasResult = true; if (lookAhead != null) { lookAhead.OnCompleted(success: true); observer.OnCompleted(); } else { observer.OnNext(second); } }, observer.OnError, () => { if (!hasResult && lookAhead != null) { lookAhead.OnCompleted(success: false); } observer.OnCompleted(); }); }) select lengthSelector == null ? first.Add(second, resultSelector) : first.Yield(second, resultSelector, lengthSelector)); }
/// <summary> /// Invokes the specified <paramref name="action"/> on each result for its side-effects. /// </summary> /// <typeparam name="TSource">The type of the source elements.</typeparam> /// <typeparam name="TResult">The type of the elements that are generated from parsing the source elements.</typeparam> /// <param name="parser">The parser from which results will be supplied to the specified <paramref name="action"/>.</param> /// <param name="action">The method that will be called for each parser result.</param> /// <returns>A new parser that is the same as the specified parser and also invokes the specified /// <paramref name="action"/> with each result for its side-effects.</returns> public static IObservableParser <TSource, TResult> OnSuccess <TSource, TResult>( this IObservableParser <TSource, TResult> parser, Action <IParseResult <TResult> > action) { Contract.Requires(parser != null); Contract.Requires(action != null); Contract.Ensures(Contract.Result <IObservableParser <TSource, TResult> >() != null); return(parser.Yield("OnSuccess", source => parser.Parse(source).Do(action))); }
/// <summary> /// Matches all results from the specified <paramref name="parser"/> for which the specified /// <paramref name="predicate"/> returns <see langword="true"/>. /// </summary> /// <typeparam name="TSource">The type of the source elements.</typeparam> /// <typeparam name="TResult">The type of the elements that are generated from parsing the source elements.</typeparam> /// <param name="parser">The parser from which matches will be filtered by the specified <paramref name="predicate"/>.</param> /// <param name="predicate">A function that returns <see langword="true"/> to indicate when a match should be yielded and /// <see langword="false"/> when a match should be ignored.</param> /// <returns>A parser that matches only those results from the specified <paramref name="parser"/> for which /// the specified <paramref name="predicate"/> returns <see langword="true"/>.</returns> public static IObservableParser <TSource, TResult> Where <TSource, TResult>( this IObservableParser <TSource, TResult> parser, Func <TResult, bool> predicate) { Contract.Requires(parser != null); Contract.Requires(predicate != null); Contract.Ensures(Contract.Result <IObservableParser <TSource, TResult> >() != null); return(parser.Yield("Where", source => parser.Parse(source).Where(result => predicate(result.Value)))); }
/// <summary> /// Projects matches from the specified <paramref name="parser"/> into a new form. /// </summary> /// <typeparam name="TSource">The type of the source elements.</typeparam> /// <typeparam name="TIntermediate">The type of the elements that are generated from parsing the source elements.</typeparam> /// <typeparam name="TResult">The type of the elements that are projected from the matches of the specified <paramref name="parser"/>.</typeparam> /// <param name="parser">The parser from which matches will be projected by the specified <paramref name="selector"/> function.</param> /// <param name="selector">A transform function to apply to each match.</param> /// <returns>A parser that projects matches from the specified <paramref name="parser"/> into a new form.</returns> public static IObservableParser <TSource, TResult> Select <TSource, TIntermediate, TResult>( this IObservableParser <TSource, TIntermediate> parser, Func <TIntermediate, TResult> selector) { Contract.Requires(parser != null); Contract.Requires(selector != null); Contract.Ensures(Contract.Result <IObservableParser <TSource, TResult> >() != null); return(parser.Yield("Select", source => parser.Parse(source).Select(result => result.Yield(selector)))); }
/// <summary> /// Applies the specified <paramref name="parser"/> to generate matches. /// </summary> /// <typeparam name="TSource">The type of the source elements.</typeparam> /// <typeparam name="TResult">The type of the elements that are generated from parsing the source elements.</typeparam> /// <param name="source">The observable sequence to parse.</param> /// <param name="parser">An object that defines a grammar to be applied to the observable sequence to generate matches.</param> /// <returns>An observable sequence of matches.</returns> public static IObservable <TResult> Parse <TSource, TResult>( this IObservable <TSource> source, IObservableParser <TSource, TResult> parser) { Contract.Requires(source != null); Contract.Requires(parser != null); Contract.Ensures(Contract.Result <IObservable <TResult> >() != null); // enableBranchOptimizations must be false: See the comments in the first interactive Rxx.Parsers.Linq.Parser.Parse method for details. return(parser.Parse(source.ToCursor(forwardOnly: true, enableBranchOptimizations: false)).Select(result => result.Value)); }
/// <summary> /// Throws a <see cref="ParseException"/> returned by the specified function if the specified /// <paramref name="parser"/> does not match. /// </summary> /// <typeparam name="TSource">The type of the source elements.</typeparam> /// <typeparam name="TResult">The type of the elements that are generated from parsing the source elements.</typeparam> /// <param name="parser">The parser that must succeed otherwise a <see cref="ParseException"/> is thrown.</param> /// <param name="exceptionFactory">A function that returns the <see cref="ParseException"/> to be thrown describing the /// failed expectation.</param> /// <returns>A parser that yields the matches from the specified <paramref name="parser"/> or throws /// a <see cref="ParseException"/> returned by the specified functions if there are no matches.</returns> public static IObservableParser <TSource, TResult> Required <TSource, TResult>( this IObservableParser <TSource, TResult> parser, Func <int, Exception> exceptionFactory) { Contract.Requires(parser != null); Contract.Requires(exceptionFactory != null); Contract.Ensures(Contract.Result <IObservableParser <TSource, TResult> >() != null); return(parser.Yield( "Required", (source, observer) => { int index = source.CurrentIndex; bool hasResult = false; return parser.Parse(source).SubscribeSafe( result => { hasResult = true; observer.OnNext(result); }, observer.OnError, () => { if (hasResult) { observer.OnCompleted(); } else { Exception exception = exceptionFactory(index); if (exception == null) { exception = new ParseException(index); } else if (!(exception is ParseException)) { exception = new ParseException(index, exception); } #if !SILVERLIGHT && !PORT_45 && !PORT_40 ParserTraceSources.Input.TraceEvent(System.Diagnostics.TraceEventType.Error, 0, "{0}", exception); #endif observer.OnError(exception); } }); })); }
/// <summary> /// Applies an <paramref name="accumulator"/> function over each result sequence from the /// specified <paramref name="parser"/> and yields a sequence of accumulated results. /// </summary> /// <typeparam name="TSource">The type of the source elements.</typeparam> /// <typeparam name="TIntermediate">The type of the elements that are generated from parsing the source elements.</typeparam> /// <typeparam name="TAccumulate">The type of the accumulation.</typeparam> /// <typeparam name="TResult">The type of the elements that are generated from projecting the accumulation.</typeparam> /// <param name="parser">The parser that produces a sequence of result sequences to be aggregated.</param> /// <param name="seed">A function that returns the initial value of the accumulation for each parse result.</param> /// <param name="accumulator">A function to be invoked on each element of each parse result.</param> /// <param name="selector">A function that projects the final aggregation of each parse result.</param> /// <returns>A parser that returns the aggregated results.</returns> public static IObservableParser <TSource, TResult> Aggregate <TSource, TIntermediate, TAccumulate, TResult>( this IObservableParser <TSource, IObservable <TIntermediate> > parser, Func <TAccumulate> seed, Func <TAccumulate, TIntermediate, TAccumulate> accumulator, Func <TAccumulate, TResult> selector) { Contract.Requires(parser != null); Contract.Requires(accumulator != null); Contract.Requires(selector != null); Contract.Ensures(Contract.Result <IObservableParser <TSource, TResult> >() != null); return(parser.Yield( "Aggregate", source => from result in parser.Parse(source) from acc in result.Value.Aggregate(seed(), accumulator) select result.Yield(selector(acc)))); }
public static IObservableParser <TSource, TResult> OnFailure <TSource, TResult>( this IObservableParser <TSource, TResult> parser, Action action) { Contract.Requires(parser != null); Contract.Requires(action != null); Contract.Ensures(Contract.Result <IObservableParser <TSource, TResult> >() != null); return(parser.Yield( "OnFailure", (source, observer) => { bool hasResult = false; return parser.Parse(source).Subscribe( result => { hasResult = true; observer.OnNext(result); }, observer.OnError, () => { if (!hasResult) { try { action(); } catch (Exception ex) { observer.OnError(ex); return; } } observer.OnCompleted(); }); })); }
/// <summary> /// Matches the single element from the ambiguous result sequence in each match that is yielded by the specified /// <paramref name="parser"/> and fails for any match in which there is zero or more than one element. /// </summary> /// <typeparam name="TSource">The type of the source elements.</typeparam> /// <typeparam name="TResult">The type of the elements of the result sequences that are generated from parsing the source elements.</typeparam> /// <param name="parser">The parser from which the single result element is yielded for each match.</param> /// <returns>A parser that matches the single element from the result sequence of each match that is /// yielded by the specified <paramref name="parser"/> and fails for any match in which there is zero /// or more than one element.</returns> public static IObservableParser <TSource, TResult> Single <TSource, TResult>( this IObservableParser <TSource, IObservable <TResult> > parser) { Contract.Requires(parser != null); Contract.Ensures(Contract.Result <IObservableParser <TSource, TResult> >() != null); return(parser.Yield <TSource, IObservable <TResult>, TResult>( "Single", source => parser.Parse(source).SelectMany( result => Observable.Create <IParseResult <TResult> >( observer => { bool hasResult = false; TResult firstResult = default(TResult); return result.Value.Take(2).Subscribe( innerResult => { if (!hasResult) { firstResult = innerResult; hasResult = true; } else { hasResult = false; } }, observer.OnError, () => { if (hasResult) { observer.OnNext(result.Yield(firstResult)); } observer.OnCompleted(); }); })))); }
private static IObservable <IParseResult <IObservable <TSource> > > AmbiguousGroupInternal <TSource>( IObservableCursor <TSource> source, IObservableParser <TSource, TSource> open, IObservableParser <TSource, TSource> close) { Contract.Requires(source != null); Contract.Requires(open != null); Contract.Requires(close != null); Contract.Ensures(Contract.Result <IObservable <IParseResult <IObservable <TSource> > > >() != null); return(Observable.Create <IParseResult <IObservable <TSource> > >( observer => { var branch = source.Branch(); var disposables = new CompositeDisposable(branch); bool hasOpenResult = false; disposables.Add(open.Parse(source).SubscribeSafe( openResult => { hasOpenResult = true; int openCount = 1; var openSinks = new List <Action <IParseResult <TSource> > >(); var closeSinks = new List <Action <IParseResult <TSource> > >(); var contentSinks = new List <Action <IParseResult <TSource> > >(); var bufferedResults = new List <IEnumerable <IParseResult <IObservable <TSource> > > >(); Func <List <IParseResult <IObservable <TSource> > > > addBuffer = () => { var buffer = new List <IParseResult <IObservable <TSource> > >(); bufferedResults.Add(buffer); return buffer; }; Action installSinks = () => { var buffer = addBuffer(); var content = new List <TSource>(); openSinks.Add(result => content.Add(result.Value)); contentSinks.Add(result => content.Add(result.Value)); closeSinks.Add(result => { // copy the content list to create a new branch var branchResult = result.Yield(new List <TSource>(content).ToObservable(Scheduler.Immediate)); buffer.Add(branchResult); if (openCount > 0) { content.Add(result.Value); } }); }; // base sinks must be installed first - openCount must be incremented before other sinks are executed openSinks.Add(_ => { openCount++; installSinks(); }); closeSinks.Add(_ => { openCount--; }); // now we can install the sinks for the first open (matched in the foreach above) installSinks(); var innerBranch = branch.Remainder(openResult.Length); bool hasInnerResult = false; IObservableParser <TSource, TSource> current = open; var recursion = new SingleAssignmentDisposable(); disposables.Add(recursion); recursion.Disposable = Scheduler.Immediate.Schedule( self => { var capturedBranch = innerBranch; var innerParser = open.OnSuccess(innerOpenResult => { hasInnerResult = true; var clone = openSinks.ToList(); // the sinks list is modified when open is matched, so we must run a clone clone.ForEach(sink => sink(innerOpenResult)); innerBranch = capturedBranch.Remainder(innerOpenResult.Length); current = open; }) .Or( close.OnSuccess(closeResult => { hasInnerResult = true; closeSinks.ForEach(sink => sink(closeResult)); innerBranch = capturedBranch.Remainder(closeResult.Length); current = close; })) .Or( current.Next.OnSuccess(content => { hasInnerResult = true; contentSinks.ForEach(sink => sink(content)); innerBranch = capturedBranch.Remainder(content.Length); })); var innerSubscription = new SingleAssignmentDisposable(); disposables.Add(innerSubscription); innerSubscription.Disposable = innerParser.Parse(capturedBranch).SubscribeSafe( _ => { }, observer.OnError, () => { if (openCount > 0 && hasInnerResult) { self(); } else { innerBranch.Dispose(); disposables.Remove(innerSubscription); disposables.Remove(recursion); if (hasInnerResult) { try { bufferedResults.Concat().ForEach(observer.OnNext); } catch (Exception ex) { observer.OnError(ex); } observer.OnCompleted(); } else { // completing without results is failure observer.OnCompleted(); } } }); }); }, observer.OnError, () => { if (!hasOpenResult) { observer.OnCompleted(); } })); return disposables; })); }
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); } })); }
public IObservable <IParseResult <IObservable <TResult> > > Parse(IObservableCursor <TSource> source) { return(Observable.Create <IParseResult <IObservable <TResult> > >( observer => { int matchCount = 0; int remainingLength = 0; Action <Action> iterate = moveNext => { bool hasResult = false; int length = 0; var branch = source.Branch(); var values = parser.Parse(branch) .Finally(branch.Dispose) .Select(result => { if (!hasResult) { matchCount++; hasResult = true; } length = Math.Max(length, result.Length); return result.Value; }) .Do( __ => { }, () => { /* We must respect the greediness of the results unless the length is zero since the * cursor would have already moved to the following element. It is acceptable to ignore * zero-length results because marking an entirely non-greedy parser as ambiguous would * otherwise cause the parser to continously parse the first element indefinitely. */ if (length > 0) { remainingLength = length - 1; } else if (remainingLength > 0) { remainingLength--; } moveNext(); }); observer.OnNext(ParseResult.Create(values, length: 1)); }; Action complete = () => { if (remainingLength > 0) { observer.OnNext(ObservableParseResult.SuccessMany <TResult>(remainingLength)); } observer.OnCompleted(); }; var untilSubscription = new SerialDisposable(); var schedule = Scheduler.Immediate.Schedule( self => { if (!source.AtEndOfSequence && (untilCount == unlimitedCount || matchCount < untilCount)) { if (untilParser == null) { iterate(self); } else { untilSubscription.SetDisposableIndirectly(() => untilParser.Parse(source).Any().Subscribe( any => { if (!any) { iterate(self); } else { complete(); } }, observer.OnError)); } } else { complete(); } }); return new CompositeDisposable(schedule, untilSubscription); })); }
private static IObservable <IParseResult <TResult> > SelectManyInternal <TSource, TFirstResult, TCollection, TResult>( IObservableCursor <TSource> source, IObservableParser <TSource, TFirstResult> parser, Func <TFirstResult, IObservable <TCollection> > collectionSelector, Func <TFirstResult, TCollection, TResult> resultSelector, Func <IParseResult <TFirstResult>, TCollection, int> lengthSelector = null) { Contract.Requires(source != null); Contract.Requires(source.IsForwardOnly); Contract.Requires(parser != null); Contract.Requires(collectionSelector != null); Contract.Requires(resultSelector != null); Contract.Ensures(Contract.Result <IObservable <IParseResult <TResult> > >() != null); return(from first in parser.Parse(source) from second in Observable.Create <Tuple <TCollection, bool> >( observer => { var lookAhead = first as ILookAheadParseResult <TFirstResult>; var hasResult = false; TCollection previous = default(TCollection); return collectionSelector(first.Value).Subscribe( second => { if (lookAhead != null) { hasResult = true; lookAhead.OnCompleted(success: true); observer.OnCompleted(); } else { if (hasResult) { observer.OnNext(Tuple.Create(previous, false)); } hasResult = true; previous = second; } }, observer.OnError, () => { if (hasResult && lookAhead == null) { observer.OnNext(Tuple.Create(previous, true)); } else if (!hasResult && lookAhead != null) { lookAhead.OnCompleted(success: false); } observer.OnCompleted(); }); }) select lengthSelector == null ? first.Yield(second.Item1, resultSelector, (f, s) => second.Item2?f.Length : 0) : first.Yield(second.Item1, resultSelector, lengthSelector)); }