Commits

Timwi committed 04aa8ca

* Make Then, Process, the Repeat methods and Recursive generic
* Introduce Stringerex
* Introduce .Not()
* Rename GenerexMatchInfo to LengthAndResult

Comments (0)

Files changed (12)

         /// </summary>
         public static Generex<T, TResult> Ors<T, TResult>(params Generex<T, TResult>[] generexes) { return Generex<T, TResult>.Ors(generexes); }
 
+        /// <summary>Returns a regular expression that matches a single element which is none of the specified elements.</summary>
+        /// <param name="elements">List of elements excluded from matching.</param>
+        public static Generex<T> Not<T>(params T[] elements) { return Generex<T>.Not(EqualityComparer<T>.Default, elements); }
+        /// <summary>Returns a regular expression that matches a single element which is none of the specified elements.</summary>
+        /// <param name="elements">List of elements excluded from matching.</param>
+        /// <param name="comparer">Equality comparer to use.</param>
+        public static Generex<T> Not<T>(IEqualityComparer<T> comparer, params T[] elements) { return Generex<T>.Not(comparer, elements); }
+
         /// <summary>Generates a recursive regular expression, i.e. one that can contain itself, allowing the matching of arbitrarily nested expressions.</summary>
         /// <param name="generator">A function that generates the regular expression from an object that recursively represents the result.</param>
         public static Generex<T> Recursive<T>(Func<Generex<T>, Generex<T>> generator) { return Generex<T>.Recursive(generator); }
     <Compile Include="Generex2.cs" />
     <Compile Include="GenerexBase.cs" />
     <Compile Include="GenerexMatch.cs" />
-    <Compile Include="GenerexMatchInfo.cs" />
+    <Compile Include="LengthAndResult.cs" />
     <Compile Include="GenerexNoResultBase.cs" />
     <Compile Include="GenerexWithResultBase.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Stringerex.cs" />
+    <Compile Include="StringerexWithResult.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\main\common\Util\Util.csproj">
         /// <summary>
         /// Instantiates a regular expression that matches a sequence of consecutive regular expressions.
         /// </summary>
-        public Generex(params GenerexNoResultBase<T, Generex<T>, GenerexMatch<T>>[] generexSequence)
+        public Generex(params Generex<T>[] generexSequence)
             : base(
                 sequenceMatcher(generexSequence, backward: false),
                 sequenceMatcher(generexSequence, backward: true)) { }
         /// and retains the match object generated by each match of the other regular expression.
         /// </summary>
         public Generex<T, TResult> Then<TResult>(Generex<T, TResult> other) { return Then<Generex<T, TResult>, GenerexMatch<T, TResult>, TResult>(other); }
+
+        /// <summary>Processes each match of this regular expression by running it through a provided selector.</summary>
+        /// <typeparam name="TResult">Type of the object returned by <paramref name="selector"/>.</typeparam>
+        /// <param name="selector">Function to process a regular expression match.</param>
+        public Generex<T, TResult> Process<TResult>(Func<GenerexMatch<T>, TResult> selector)
+        {
+            return Process<Generex<T, TResult>, GenerexMatch<T, TResult>, TResult>(selector);
+        }
     }
 }
-
+using System;
+using System.Collections.Generic;
+using RT.Util.ExtensionMethods;
+
 namespace RT.Generexes
 {
     /// <summary>
     /// </summary>
     /// <typeparam name="T">Type of the objects in the collection.</typeparam>
     /// <typeparam name="TResult">Type of objects generated from each match of the regular expression.</typeparam>
+    /// <remarks>This type is not directly instantiated; use <see cref="Generex{T}.Process"/>.</remarks>
     public sealed class Generex<T, TResult> : GenerexWithResultBase<T, TResult, Generex<T, TResult>, GenerexMatch<T, TResult>>
     {
         internal sealed override GenerexMatch<T, TResult> createMatchWithResult(TResult result, T[] input, int index, int length)
             return new GenerexMatch<T, TResult>(result, input, index, length);
         }
 
-        /// <summary>
-        /// Instantiates an empty regular expression which always matches and returns the default result object (null or zero).
-        /// </summary>
-        public Generex() : base(default(TResult)) { }
+        internal Generex(matcher forward, matcher backward) : base(forward, backward) { }
+        static Generex() { Constructor = (forward, backward) => new Generex<T, TResult>(forward, backward); }
+
+        /// <summary>Processes each match of this regular expression by running it through a provided selector.</summary>
+        /// <typeparam name="TOtherResult">Type of the object returned by <paramref name="selector"/>.</typeparam>
+        /// <param name="selector">Function to process a regular expression match.</param>
+        public Generex<T, TOtherResult> Process<TOtherResult>(Func<GenerexMatch<T, TResult>, TOtherResult> selector)
+        {
+            return base.Process<Generex<T, TOtherResult>, GenerexMatch<T, TOtherResult>, TOtherResult>(selector);
+        }
+
+        /// <summary>Processes each match of this regular expression by running each result through a provided selector.</summary>
+        /// <typeparam name="TOtherResult">Type of the object returned by <paramref name="selector"/>.</typeparam>
+        /// <param name="selector">Function to process the result of a regular expression match.</param>
+        public Generex<T, TOtherResult> ProcessRaw<TOtherResult>(Func<TResult, TOtherResult> selector)
+        {
+            return base.ProcessRaw<Generex<T, TOtherResult>, GenerexMatch<T, TOtherResult>, TOtherResult>(selector);
+        }
 
         /// <summary>
-        /// Instantiates an empty regular expression which always matches and returns the specified result object.
+        /// Returns a regular expression that matches this regular expression, followed by the specified ones,
+        /// and generates a match object that combines the result of this regular expression with the match of the other.
         /// </summary>
-        public Generex(TResult result) : base(result) { }
+        public Generex<T, TCombined> Then<TCombined>(Generex<T> other, Func<TResult, GenerexMatch<T>, TCombined> selector)
+        {
+            return base.Then<Generex<T>, GenerexMatch<T>, Generex<T, TCombined>, GenerexMatch<T, TCombined>, TCombined>(other, selector);
+        }
 
-        internal Generex(matcher forward, matcher backward) : base(forward, backward) { }
-        static Generex() { Constructor = (forward, backward) => new Generex<T, TResult>(forward, backward); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression, followed by the specified one,
+        /// and generates a match object that combines the result of this regular expression with the match of the other.
+        /// </summary>
+        public Generex<T, TCombined> Then<TOther, TCombined>(Generex<T, TOther> other, Func<TResult, GenerexMatch<T, TOther>, TCombined> selector)
+        {
+            return base.Then<Generex<T, TOther>, GenerexMatch<T, TOther>, TOther, Generex<T, TCombined>, GenerexMatch<T, TCombined>, TCombined>(other, selector);
+        }
+
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression, followed by the specified ones,
+        /// and generates a match object that combines the original two matches.
+        /// </summary>
+        public Generex<T, TCombined> ThenRaw<TOther, TCombined>(Generex<T, TOther> other, Func<TResult, TOther, TCombined> selector)
+        {
+            return base.ThenRaw<Generex<T, TOther>, GenerexMatch<T, TOther>, TOther, Generex<T, TCombined>, GenerexMatch<T, TCombined>, TCombined>(other, selector);
+        }
+
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression zero times or once. Once is prioritised (cf. "?" in traditional regular expression syntax).
+        /// </summary>
+        public Generex<T, IEnumerable<TResult>> OptionalGreedy() { return repeatBetween<Generex<T, IEnumerable<TResult>>, GenerexMatch<T, IEnumerable<TResult>>>(0, 1, greedy: true); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression zero times or once. Zero times is prioritised (cf. "??" in traditional regular expression syntax).
+        /// </summary>
+        public Generex<T, IEnumerable<TResult>> Optional() { return repeatBetween<Generex<T, IEnumerable<TResult>>, GenerexMatch<T, IEnumerable<TResult>>>(0, 1, greedy: false); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression zero or more times. More times are prioritised (cf. "*" in traditional regular expression syntax).
+        /// </summary>
+        public Generex<T, IEnumerable<TResult>> RepeatGreedy() { return repeatInfinite<Generex<T, IEnumerable<TResult>>, GenerexMatch<T, IEnumerable<TResult>>>(greedy: true); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression zero or more times. Fewer times are prioritised (cf. "*?" in traditional regular expression syntax).
+        /// </summary>
+        public Generex<T, IEnumerable<TResult>> Repeat() { return repeatInfinite<Generex<T, IEnumerable<TResult>>, GenerexMatch<T, IEnumerable<TResult>>>(greedy: false); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression the specified number of times or more. More times are prioritised (cf. "{min,}" in traditional regular expression syntax).
+        /// </summary>
+        public Generex<T, IEnumerable<TResult>> RepeatGreedy(int min) { return repeatMin<Generex<T, IEnumerable<TResult>>, GenerexMatch<T, IEnumerable<TResult>>>(min, greedy: true); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression the specified number of times or more. Fewer times are prioritised (cf. "{min,}?" in traditional regular expression syntax).
+        /// </summary>
+        public Generex<T, IEnumerable<TResult>> Repeat(int min) { return repeatMin<Generex<T, IEnumerable<TResult>>, GenerexMatch<T, IEnumerable<TResult>>>(min, greedy: false); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression any number of times within specified boundaries. More times are prioritised (cf. "{min,max}" in traditional regular expression syntax).
+        /// </summary>
+        /// <param name="min">Minimum number of times to match.</param>
+        /// <param name="max">Maximum number of times to match.</param>
+        public Generex<T, IEnumerable<TResult>> RepeatGreedy(int min, int max) { return repeatBetween<Generex<T, IEnumerable<TResult>>, GenerexMatch<T, IEnumerable<TResult>>>(min, max, greedy: true); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression any number of times within specified boundaries. Fewer times are prioritised (cf. "{min,max}?" in traditional regular expression syntax).
+        /// </summary>
+        /// <param name="min">Minimum number of times to match.</param>
+        /// <param name="max">Maximum number of times to match.</param>
+        public Generex<T, IEnumerable<TResult>> Repeat(int min, int max) { return repeatBetween<Generex<T, IEnumerable<TResult>>, GenerexMatch<T, IEnumerable<TResult>>>(min, max, greedy: false); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression the specified number of times (cf. "{times}" in traditional regular expression syntax).
+        /// </summary>
+        public Generex<T, IEnumerable<TResult>> Times(int times)
+        {
+            if (times < 0) throw new ArgumentException("'times' cannot be negative.", "times");
+            return repeatBetween<Generex<T, IEnumerable<TResult>>, GenerexMatch<T, IEnumerable<TResult>>>(times, times, true);
+        }
+
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression one or more times, interspersed with a separator. Fewer times are prioritised.
+        /// </summary>
+        public Generex<T, IEnumerable<TResult>> RepeatWithSeparator(Generex<T> separator)
+        {
+            return ThenRaw(separator.Then(this).Repeat(), IEnumerableExtensions.Concat);
+        }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression one or more times, interspersed with a separator. More times are prioritised.
+        /// </summary>
+        public Generex<T, IEnumerable<TResult>> RepeatWithSeparatorGreedy(Generex<T> separator)
+        {
+            return ThenRaw(separator.Then(this).RepeatGreedy(), IEnumerableExtensions.Concat);
+        }
     }
 }
 {
     /// <summary>Abstract base class for all Generex regular expressions.</summary>
     /// <typeparam name="T">Type of the objects in the collection.</typeparam>
-    /// <typeparam name="TMatch">Either int or <see cref="GenerexMatchInfo{TResult}"/>.</typeparam>
+    /// <typeparam name="TMatch">Either int or <see cref="LengthAndResult{TResult}"/>.</typeparam>
     /// <typeparam name="TGenerex">The derived type. (Pass the type itself recursively.)</typeparam>
     /// <typeparam name="TGenerexMatch">Type describing a match of a regular expression.</typeparam>
     public abstract class GenerexBase<T, TMatch, TGenerex, TGenerexMatch>
-        where TGenerex : GenerexBase<T, TMatch, TGenerex, TGenerexMatch>, new()
+        where TGenerex : GenerexBase<T, TMatch, TGenerex, TGenerexMatch>
         where TGenerexMatch : GenerexMatch<T>
     {
         internal delegate IEnumerable<TMatch> matcher(T[] input, int startIndex);
             {
                 if (_constructor == null)
                 {
-                    new TGenerex();
+                    if (typeof(TGenerex).TypeInitializer != null)
+                        typeof(TGenerex).TypeInitializer.Invoke(null, null);
                     if (_constructor == null)
-                        throw new InvalidOperationException("The static constructor of {0} didn’t initialize the Constructor field.");
+                        throw new InvalidOperationException("The static constructor of {0} didn’t initialize the Constructor field.".Fmt(typeof(TGenerex)));
                 }
                 return _constructor;
             }
         /// </summary>
         /// <param name="input">Input sequence to match the regular expression against.</param>
         /// <param name="startAt">Optional index at which to start the search. Matches that start before this index are not included.</param>
-        /// <returns>A <see cref="GenerexMatch{T}"/> object describing a regular expression match in case of success; null if no match.</returns>
+        /// <returns>An object describing a regular expression match in case of success; null if no match.</returns>
         public TGenerexMatch Match(T[] input, int startAt = 0)
         {
             return Matches(input, startAt).FirstOrDefault();
         /// <param name="input">Input sequence to match the regular expression against.</param>
         /// <param name="mustStartAt">Index at which the match must start (default is 0).</param>
         /// <param name="mustEndAt">Index at which the match must end (default is the end of the input sequence).</param>
-        /// <returns>A <see cref="GenerexMatch{T}"/> object describing the regular expression match in case of success; null if no match.</returns>
+        /// <returns>An object describing the regular expression match in case of success; null if no match.</returns>
         public TGenerexMatch MatchExact(T[] input, int mustStartAt = 0, int? mustEndAt = null)
         {
             return matchExact(input, mustStartAt, mustEndAt ?? input.Length, match => createMatch(input, mustStartAt, match));
         /// </summary>
         /// <param name="input">Input sequence to match the regular expression against.</param>
         /// <param name="endAt">Optional index at which to end the search. Matches that end at or after this index are not included.</param>
-        /// <returns>A <see cref="GenerexMatch{T}"/> object describing a regular expression match in case of success; null if no match.</returns>
+        /// <returns>An object describing a regular expression match in case of success; null if no match.</returns>
         public TGenerexMatch MatchReverse(T[] input, int? endAt = null)
         {
             return MatchesReverse(input, endAt).FirstOrDefault();
         /// Returns a regular expression that matches a consecutive sequence of regular expressions, beginning with this one, followed by the specified ones.
         /// </summary>
         public TGenerex Then<TGenerex2, TGenerexMatch2>(params GenerexNoResultBase<T, TGenerex2, TGenerexMatch2>[] other)
-            where TGenerex2 : GenerexNoResultBase<T, TGenerex2, TGenerexMatch2>, new()
+            where TGenerex2 : GenerexNoResultBase<T, TGenerex2, TGenerexMatch2>
             where TGenerexMatch2 : GenerexMatch<T>
         {
             var backwardFirst = other.Reverse().Select(o => o._backwardMatcher).Aggregate(GenerexNoResultBase<T, TGenerex2, TGenerexMatch2>.then);
             matcher newMatcher = (input, startIndex) => innerMatcher(input, startIndex).Any() ? Enumerable.Empty<TMatch>() : defaultMatch;
             return Constructor(newMatcher, newMatcher);
         }
+
+        /// <summary>Generates a recursive regular expression, i.e. one that can contain itself, allowing the matching of arbitrarily nested expressions.</summary>
+        /// <param name="generator">A function that generates the regular expression from an object that recursively represents the result.</param>
+        public static TGenerex Recursive(Func<TGenerex, TGenerex> generator)
+        {
+            if (generator == null)
+                throw new ArgumentNullException("generator");
+
+            matcher recursiveForward = null, recursiveBackward = null;
+
+            // Note the following *must* be lambdas so that they capture the above *variables* (which are modified afterwards), not their current value (which would be null)
+            var carrier = Constructor(
+                (input, startIndex) => recursiveForward(input, startIndex),
+                (input, startIndex) => recursiveBackward(input, startIndex)
+            );
+
+            var generated = generator(carrier);
+            if (generated == null)
+                throw new InvalidOperationException("Generator function returned null.");
+            recursiveForward = generated._forwardMatcher;
+            recursiveBackward = generated._backwardMatcher;
+            return generated;
+        }
     }
 }
     }
 
     /// <summary>
+    /// Represents the result of a regular expression match using <see cref="Stringerex"/>.
+    /// </summary>
+    public class StringerexMatch : GenerexMatch<char>
+    {
+        /// <summary>
+        /// Gets the entire original string against which the regular expression was matched.
+        /// </summary>
+        public new string OriginalSource { get { return _originalSource ?? (_originalSource = new string(base.OriginalSource)); } }
+        private string _originalSource;
+
+        /// <summary>
+        /// Returns the part of the original string which the regular expression matched.
+        /// </summary>
+        public new string Match { get { return _match ?? (_match = new string(base.Match)); } }
+        private string _match;
+
+        internal StringerexMatch(char[] original, int index, int length) : base(original, index, length) { }
+    }
+
+    /// <summary>
     /// Represents the result of a regular expression match using <see cref="Generex{T,TResult}"/>.
     /// </summary>
     /// <typeparam name="T">Type of the objects in the collection.</typeparam>
             Result = result;
         }
     }
+
+    /// <summary>
+    /// Represents the result of a regular expression match using <see cref="Stringerex{TResult}"/>.
+    /// </summary>
+    /// <typeparam name="TResult">Type of objects generated from each match of the regular expression.</typeparam>
+    public class StringerexMatch<TResult> : GenerexMatch<char, TResult>
+    {
+        /// <summary>
+        /// Gets the entire original string against which the regular expression was matched.
+        /// </summary>
+        public new string OriginalSource { get { return _originalSource ?? (_originalSource = new string(base.OriginalSource)); } }
+        private string _originalSource;
+
+        /// <summary>
+        /// Returns the part of the original string which the regular expression matched.
+        /// </summary>
+        public new string Match { get { return _match ?? (_match = new string(base.Match)); } }
+        private string _match;
+
+        internal StringerexMatch(TResult result, char[] original, int index, int length) : base(result, original, index, length) { }
+    }
 }

GenerexMatchInfo.cs

-
-namespace RT.Generexes
-{
-    /// <summary>Encapsulates preliminary information about matches generated by <see cref="Generex{T,TResult}"/>.</summary>
-    /// <typeparam name="TResult">Type of objects generated from each match of the regular expression.</typeparam>
-    /// <remarks>This type is an implementation detail. It is not intended to be used outside this library.
-    /// However, it cannot be made internal because it is used as a generic type parameter in a base-type declaration.</remarks>
-    public struct GenerexMatchInfo<TResult>
-    {
-        internal TResult Result { get; private set; }
-        internal int Length { get; private set; }
-        internal GenerexMatchInfo(TResult result, int length) : this() { Result = result; Length = length; }
-        internal GenerexMatchInfo<TResult> Add(int extraLength)
-        {
-            return new GenerexMatchInfo<TResult>(Result, Length + extraLength);
-        }
-    }
-}

GenerexNoResultBase.cs

     /// <typeparam name="TGenerex">The derived type. (Pass the type itself recursively.)</typeparam>
     /// <typeparam name="TGenerexMatch">Type describing a match of a regular expression.</typeparam>
     public abstract class GenerexNoResultBase<T, TGenerex, TGenerexMatch> : GenerexBase<T, int, TGenerex, TGenerexMatch>
-        where TGenerex : GenerexNoResultBase<T, TGenerex, TGenerexMatch>, new()
+        where TGenerex : GenerexNoResultBase<T, TGenerex, TGenerexMatch>
         where TGenerexMatch : GenerexMatch<T>
     {
         internal sealed override int getLength(int match) { return match; }
         /// and retains the match object generated by each match of the other regular expression.
         /// </summary>
         public TGenerexWithResult Then<TGenerexWithResult, TGenerexWithResultMatch, TResult>(TGenerexWithResult other)
-            where TGenerexWithResult : GenerexWithResultBase<T, TResult, TGenerexWithResult, TGenerexWithResultMatch>, new()
+            where TGenerexWithResult : GenerexWithResultBase<T, TResult, TGenerexWithResult, TGenerexWithResultMatch>
             where TGenerexWithResultMatch : GenerexMatch<T, TResult>
         {
             return GenerexWithResultBase<T, TResult, TGenerexWithResult, TGenerexWithResultMatch>.Constructor(
         public TGenerex Or(params T[] elements) { return Or(EqualityComparer<T>.Default, elements); }
 
         /// <summary>
-        /// Returns a regular expression that matches either this regular expression or any of the specified elements using the specified equality comparer (cf. "|" or "[...]" in traditional regular expression syntax).
+        /// Returns a regular expression that matches either this regular expression or the specified sequence of elements using the specified equality comparer (cf. "|" or "[...]" in traditional regular expression syntax).
         /// </summary>
         /// <seealso cref="Or(T[])"/>
         public TGenerex Or(IEqualityComparer<T> comparer, params T[] elements)
         /// </summary>
         public TGenerex Or(IEnumerable<T> elements) { return Or(EqualityComparer<T>.Default, elements.ToArray()); }
         /// <summary>
-        /// Returns a regular expression that matches either this regular expression or any of the specified elements using the specified equality comparer (cf. "|" or "[...]" in traditional regular expression syntax).
+        /// Returns a regular expression that matches either this regular expression or the specified sequence of elements using the specified equality comparer (cf. "|" or "[...]" in traditional regular expression syntax).
         /// </summary>
         public TGenerex Or(IEqualityComparer<T> comparer, IEnumerable<T> elements) { return Or(comparer, elements.ToArray()); }
 
         /// <summary>Processes each match of this regular expression by running it through a provided selector.</summary>
         /// <typeparam name="TResult">Type of the object returned by <paramref name="selector"/>.</typeparam>
         /// <param name="selector">Function to process a regular expression match.</param>
-        public Generex<T, TResult> Process<TResult>(Func<TGenerexMatch, TResult> selector)
+        public TGenerexWithResult Process<TGenerexWithResult, TGenerexWithResultMatch, TResult>(Func<TGenerexMatch, TResult> selector)
+            where TGenerexWithResult : GenerexWithResultBase<T, TResult, TGenerexWithResult, TGenerexWithResultMatch>
+            where TGenerexWithResultMatch : GenerexMatch<T, TResult>
         {
-            return new Generex<T, TResult>(
-                (input, startIndex) => _forwardMatcher(input, startIndex).Select(m => new GenerexMatchInfo<TResult>(selector(createMatch(input, startIndex, m)), m)),
-                (input, startIndex) => _backwardMatcher(input, startIndex).Select(m => new GenerexMatchInfo<TResult>(selector(createBackwardsMatch(input, startIndex, m)), m))
+            return GenerexWithResultBase<T, TResult, TGenerexWithResult, TGenerexWithResultMatch>.Constructor(
+                (input, startIndex) => _forwardMatcher(input, startIndex).Select(m => new LengthAndResult<TResult>(selector(createMatch(input, startIndex, m)), m)),
+                (input, startIndex) => _backwardMatcher(input, startIndex).Select(m => new LengthAndResult<TResult>(selector(createBackwardsMatch(input, startIndex, m)), m))
             );
         }
 
-        /// <summary>Generates a recursive regular expression, i.e. one that can contain itself, allowing the matching of arbitrarily nested expressions.</summary>
-        /// <param name="generator">A function that generates the regular expression from an object that recursively represents the result.</param>
-        public static TGenerex Recursive(Func<TGenerex, TGenerex> generator)
+        /// <summary>Returns a regular expression that matches a single element which is not equal to the specified element.</summary>
+        /// <param name="element">List of elements excluded from matching.</param>
+        public static TGenerex Not(T element) { return Not(EqualityComparer<T>.Default, element); }
+
+        /// <summary>Returns a regular expression that matches a single element which is not equal to the specified element.</summary>
+        /// <param name="element">List of elements excluded from matching.</param>
+        /// <param name="comparer">Equality comparer to use.</param>
+        public static TGenerex Not(IEqualityComparer<T> comparer, T element)
         {
-            if (generator == null)
-                throw new ArgumentNullException("generator");
+            if (comparer == null)
+                throw new ArgumentNullException("comparer");
 
-            matcher recursiveForward = null, recursiveBackward = null;
+            return Constructor(
+                (input, startIndex) => startIndex >= input.Length || comparer.Equals(input[startIndex], element) ? Generex.NoMatch : Generex.OneElementMatch,
+                (input, startIndex) => startIndex <= 0 || comparer.Equals(input[startIndex - 1], element) ? Generex.NoMatch : Generex.NegativeOneElementMatch
+            );
+        }
 
-            // Note the following *must* be lambdas so that they capture the above *variables* (which are modified afterwards), not their current value (which would be null)
-            var carrier = Constructor(
-                (input, startIndex) => recursiveForward(input, startIndex),
-                (input, startIndex) => recursiveBackward(input, startIndex)
+        /// <summary>Returns a regular expression that matches a single element which is none of the specified elements.</summary>
+        /// <param name="elements">List of elements excluded from matching.</param>
+        public static TGenerex Not(params T[] elements) { return Not(EqualityComparer<T>.Default, elements); }
+
+        /// <summary>Returns a regular expression that matches a single element which is none of the specified elements.</summary>
+        /// <param name="elements">List of elements excluded from matching.</param>
+        /// <param name="comparer">Equality comparer to use.</param>
+        public static TGenerex Not(IEqualityComparer<T> comparer, params T[] elements)
+        {
+            if (comparer == null)
+                throw new ArgumentNullException("comparer");
+            if (elements == null)
+                throw new ArgumentNullException("elements");
+
+            if (elements.Length == 0)
+                return Any;
+
+            if (elements.Length == 1)
+                return Not(comparer, elements[0]);
+
+            return Constructor(
+                (input, startIndex) => startIndex >= input.Length || elements.Any(e => comparer.Equals(input[startIndex], e)) ? Generex.NoMatch : Generex.OneElementMatch,
+                (input, startIndex) => startIndex <= 0 || elements.Any(e => comparer.Equals(input[startIndex - 1], e)) ? Generex.NoMatch : Generex.NegativeOneElementMatch
             );
-
-            var generated = generator(carrier);
-            if (generated == null)
-                throw new InvalidOperationException("Generator function returned null.");
-            recursiveForward = generated._forwardMatcher;
-            recursiveBackward = generated._backwardMatcher;
-            return generated;
         }
     }
 }

GenerexWithResultBase.cs

 using System.Collections.Generic;
 using System.Linq;
 using System.Text.RegularExpressions;
+using RT.Util;
 using RT.Util.ExtensionMethods;
 
 namespace RT.Generexes
     /// <typeparam name="T">Type of the objects in the collection.</typeparam>
     /// <typeparam name="TGenerex">The derived type. (Pass the type itself recursively.)</typeparam>
     /// <typeparam name="TGenerexMatch">Type describing a match of a regular expression.</typeparam>
-    public abstract class GenerexWithResultBase<T, TResult, TGenerex, TGenerexMatch> : GenerexBase<T, GenerexMatchInfo<TResult>, TGenerex, TGenerexMatch>
-        where TGenerex : GenerexWithResultBase<T, TResult, TGenerex, TGenerexMatch>, new()
+    public abstract class GenerexWithResultBase<T, TResult, TGenerex, TGenerexMatch> : GenerexBase<T, LengthAndResult<TResult>, TGenerex, TGenerexMatch>
+        where TGenerex : GenerexWithResultBase<T, TResult, TGenerex, TGenerexMatch>
         where TGenerexMatch : GenerexMatch<T, TResult>
     {
-        internal sealed override int getLength(GenerexMatchInfo<TResult> match) { return match.Length; }
-        internal sealed override GenerexMatchInfo<TResult> add(GenerexMatchInfo<TResult> match, int extra) { return match.Add(extra); }
-        internal sealed override GenerexMatchInfo<TResult> setZero(GenerexMatchInfo<TResult> match) { return new GenerexMatchInfo<TResult>(match.Result, 0); }
-        internal sealed override TGenerexMatch createMatch(T[] input, int index, GenerexMatchInfo<TResult> match) { return createMatchWithResult(match.Result, input, index, match.Length); }
-        internal sealed override TGenerexMatch createBackwardsMatch(T[] input, int index, GenerexMatchInfo<TResult> match) { return createMatchWithResult(match.Result, input, index + match.Length, -match.Length); }
+        internal sealed override int getLength(LengthAndResult<TResult> match) { return match.Length; }
+        internal sealed override LengthAndResult<TResult> add(LengthAndResult<TResult> match, int extra) { return match.Add(extra); }
+        internal sealed override LengthAndResult<TResult> setZero(LengthAndResult<TResult> match) { return new LengthAndResult<TResult>(match.Result, 0); }
+        internal sealed override TGenerexMatch createMatch(T[] input, int index, LengthAndResult<TResult> match) { return createMatchWithResult(match.Result, input, index, match.Length); }
+        internal sealed override TGenerexMatch createBackwardsMatch(T[] input, int index, LengthAndResult<TResult> match) { return createMatchWithResult(match.Result, input, index + match.Length, -match.Length); }
         internal abstract TGenerexMatch createMatchWithResult(TResult result, T[] input, int index, int length);
 
         /// <summary>
         /// Instantiates an empty regular expression which always matches and returns the specified result object.
         /// </summary>
-        public GenerexWithResultBase(TResult result) : this(new[] { new GenerexMatchInfo<TResult>(result, 0) }) { }
+        public GenerexWithResultBase(TResult result) : this(new[] { new LengthAndResult<TResult>(result, 0) }) { }
 
-        private GenerexWithResultBase(GenerexMatchInfo<TResult>[] emptyMatch) : this((input, startIndex) => emptyMatch) { }
+        private GenerexWithResultBase(LengthAndResult<TResult>[] emptyMatch) : this((input, startIndex) => emptyMatch) { }
         private GenerexWithResultBase(matcher bothMatcher) : base(bothMatcher, bothMatcher) { }
 
         internal GenerexWithResultBase(matcher forward, matcher backward) : base(forward, backward) { }
             return matches(input, endAt ?? input.Length, (index, resultInfo) => resultInfo.Result, backward: true);
         }
 
-        //internal static matcher forwardPredicateMatcher(Predicate<T> predicate)
-        //{
-        //    return (input, startIndex) => startIndex >= input.Length || !predicate(input[startIndex]) ? Generex.NoMatch : Generex.OneElementMatch;
-        //}
-
-        //internal static matcher backwardPredicateMatcher(Predicate<T> predicate)
-        //{
-        //    return (input, startIndex) => startIndex <= 0 || !predicate(input[startIndex - 1]) ? Generex.NoMatch : Generex.NegativeOneElementMatch;
-        //}
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression, followed by the specified ones,
+        /// and generates a match object that combines the result of this regular expression with the match of the other.
+        /// </summary>
+        public TCombinedGenerex Then<TGenerexNoResult, TGenerexNoResultMatch, TCombinedGenerex, TCombinedGenerexMatch, TCombinedResult>(TGenerexNoResult other, Func<TResult, TGenerexNoResultMatch, TCombinedResult> selector)
+            where TGenerexNoResult : GenerexNoResultBase<T, TGenerexNoResult, TGenerexNoResultMatch>
+            where TGenerexNoResultMatch : GenerexMatch<T>
+            where TCombinedGenerex : GenerexWithResultBase<T, TCombinedResult, TCombinedGenerex, TCombinedGenerexMatch>
+            where TCombinedGenerexMatch : GenerexMatch<T, TCombinedResult>
+        {
+            return GenerexWithResultBase<T, TCombinedResult, TCombinedGenerex, TCombinedGenerexMatch>.Constructor(
+                (input, startIndex) => _forwardMatcher(input, startIndex).SelectMany(m => other._forwardMatcher(input, startIndex + m.Length)
+                    .Select(m2 => new LengthAndResult<TCombinedResult>(selector(m.Result, other.createMatch(input, startIndex + m.Length, m2)), m.Length + m2))),
+                (input, startIndex) => other._backwardMatcher(input, startIndex).Select(m2 => new { Match = other.createBackwardsMatch(input, startIndex, m2), Length = m2 })
+                    .SelectMany(inf => _backwardMatcher(input, startIndex + inf.Length).Select(m => new LengthAndResult<TCombinedResult>(selector(m.Result, inf.Match), m.Length + inf.Length)))
+            );
+        }
 
         /// <summary>
         /// Returns a regular expression that matches this regular expression, followed by the specified one,
         /// and generates a match object that combines the result of this regular expression with the match of the other.
         /// </summary>
-        public Generex<T, TCombined> Then<TOtherResult, TCombined>(Generex<T, TOtherResult> other, Func<TResult, GenerexMatch<T, TOtherResult>, TCombined> selector)
+        public TCombinedGenerex Then<TOtherGenerex, TOtherGenerexMatch, TOtherResult, TCombinedGenerex, TCombinedGenerexMatch, TCombinedResult>(TOtherGenerex other, Func<TResult, TOtherGenerexMatch, TCombinedResult> selector)
+            where TOtherGenerex : GenerexWithResultBase<T, TOtherResult, TOtherGenerex, TOtherGenerexMatch>
+            where TOtherGenerexMatch : GenerexMatch<T, TOtherResult>
+            where TCombinedGenerex : GenerexWithResultBase<T, TCombinedResult, TCombinedGenerex, TCombinedGenerexMatch>
+            where TCombinedGenerexMatch : GenerexMatch<T, TCombinedResult>
         {
-            return new Generex<T, TCombined>(
+            return GenerexWithResultBase<T, TCombinedResult, TCombinedGenerex, TCombinedGenerexMatch>.Constructor(
                 (input, startIndex) => _forwardMatcher(input, startIndex).SelectMany(m => other._forwardMatcher(input, startIndex + m.Length)
-                    .Select(m2 => new GenerexMatchInfo<TCombined>(selector(m.Result, new GenerexMatch<T, TOtherResult>(m2.Result, input, startIndex + m.Length, m2.Length)), m.Length + m2.Length))),
-                (input, startIndex) => other._backwardMatcher(input, startIndex).Select(m2 => new { Match = new GenerexMatch<T, TOtherResult>(m2.Result, input, startIndex + m2.Length, -m2.Length), Length = m2.Length })
-                    .SelectMany(inf => _backwardMatcher(input, startIndex + inf.Length).Select(m => new GenerexMatchInfo<TCombined>(selector(m.Result, inf.Match), m.Length + inf.Length)))
-            );
-        }
-
-        /// <summary>
-        /// Returns a regular expression that matches this regular expression, followed by the specified ones,
-        /// and generates a match object that combines the result of this regular expression with the match of the other.
-        /// </summary>
-        public Generex<T, TCombined> Then<TCombined>(Generex<T> other, Func<TResult, GenerexMatch<T>, TCombined> selector)
-        {
-            return new Generex<T, TCombined>(
-                (input, startIndex) => _forwardMatcher(input, startIndex).SelectMany(m => other._forwardMatcher(input, startIndex + m.Length)
-                    .Select(m2 => new GenerexMatchInfo<TCombined>(selector(m.Result, new GenerexMatch<T>(input, startIndex + m.Length, m2)), m.Length + m2))),
-                (input, startIndex) => other._backwardMatcher(input, startIndex).Select(m2 => new { Match = new GenerexMatch<T>(input, startIndex + m2, -m2), Length = m2 })
-                    .SelectMany(inf => _backwardMatcher(input, startIndex + inf.Length).Select(m => new GenerexMatchInfo<TCombined>(selector(m.Result, inf.Match), m.Length + inf.Length)))
+                    .Select(m2 => new LengthAndResult<TCombinedResult>(selector(m.Result, other.createMatch(input, startIndex + m.Length, m2)), m.Length + m2.Length))),
+                (input, startIndex) => other._backwardMatcher(input, startIndex).Select(m2 => new { Match = other.createBackwardsMatch(input, startIndex, m2), Length = m2.Length })
+                    .SelectMany(inf => _backwardMatcher(input, startIndex + inf.Length).Select(m => new LengthAndResult<TCombinedResult>(selector(m.Result, inf.Match), m.Length + inf.Length)))
             );
         }
 
         /// Returns a regular expression that matches this regular expression, followed by the specified ones,
         /// and generates a match object that combines the original two matches.
         /// </summary>
-        public Generex<T, TCombined> ThenRaw<TOtherResult, TCombined>(Generex<T, TOtherResult> other, Func<TResult, TOtherResult, TCombined> selector)
+        public TCombinedGenerex ThenRaw<TOtherGenerex, TOtherGenerexMatch, TOtherResult, TCombinedGenerex, TCombinedGenerexMatch, TCombinedResult>(TOtherGenerex other, Func<TResult, TOtherResult, TCombinedResult> selector)
+            where TOtherGenerex : GenerexWithResultBase<T, TOtherResult, TOtherGenerex, TOtherGenerexMatch>
+            where TOtherGenerexMatch : GenerexMatch<T, TOtherResult>
+            where TCombinedGenerex : GenerexWithResultBase<T, TCombinedResult, TCombinedGenerex, TCombinedGenerexMatch>
+            where TCombinedGenerexMatch : GenerexMatch<T, TCombinedResult>
         {
-            return new Generex<T, TCombined>(
+            return GenerexWithResultBase<T, TCombinedResult, TCombinedGenerex, TCombinedGenerexMatch>.Constructor(
                 (input, startIndex) => _forwardMatcher(input, startIndex).SelectMany(m => other._forwardMatcher(input, startIndex + m.Length)
-                    .Select(m2 => new GenerexMatchInfo<TCombined>(selector(m.Result, m2.Result), m.Length + m2.Length))),
+                    .Select(m2 => new LengthAndResult<TCombinedResult>(selector(m.Result, m2.Result), m.Length + m2.Length))),
                 (input, startIndex) => other._backwardMatcher(input, startIndex).SelectMany(m2 => _backwardMatcher(input, startIndex + m2.Length)
-                    .Select(m => new GenerexMatchInfo<TCombined>(selector(m.Result, m2.Result), m.Length + m2.Length)))
+                    .Select(m => new LengthAndResult<TCombinedResult>(selector(m.Result, m2.Result), m.Length + m2.Length)))
             );
         }
 
             return Or(Constructor(new matcher(orred._forwardMatcher), new matcher(orred._backwardMatcher)));
         }
 
-        /// <summary>
-        /// Returns a regular expression that matches this regular expression zero times or once. Once is prioritised (cf. "?" in traditional regular expression syntax).
-        /// </summary>
-        public Generex<T, IEnumerable<TResult>> OptionalGreedy() { return repeatBetween(0, 1, true); }
-        /// <summary>
-        /// Returns a regular expression that matches this regular expression zero times or once. Zero times is prioritised (cf. "??" in traditional regular expression syntax).
-        /// </summary>
-        public Generex<T, IEnumerable<TResult>> Optional() { return repeatBetween(0, 1, false); }
-        /// <summary>
-        /// Returns a regular expression that matches this regular expression zero or more times. More times are prioritised (cf. "*" in traditional regular expression syntax).
-        /// </summary>
-        public Generex<T, IEnumerable<TResult>> RepeatGreedy() { return new Generex<T, IEnumerable<TResult>>(repeatInfinite(_forwardMatcher, true), repeatInfinite(_backwardMatcher, true)); }
-        /// <summary>
-        /// Returns a regular expression that matches this regular expression zero or more times. Fewer times are prioritised (cf. "*?" in traditional regular expression syntax).
-        /// </summary>
-        public Generex<T, IEnumerable<TResult>> Repeat() { return new Generex<T, IEnumerable<TResult>>(repeatInfinite(_forwardMatcher, false), repeatInfinite(_backwardMatcher, false)); }
-        /// <summary>
-        /// Returns a regular expression that matches this regular expression the specified number of times or more. More times are prioritised (cf. "{min,}" in traditional regular expression syntax).
-        /// </summary>
-        public Generex<T, IEnumerable<TResult>> RepeatGreedy(int min) { return repeatMin(min, true); }
-        /// <summary>
-        /// Returns a regular expression that matches this regular expression the specified number of times or more. Fewer times are prioritised (cf. "{min,}?" in traditional regular expression syntax).
-        /// </summary>
-        public Generex<T, IEnumerable<TResult>> Repeat(int min) { return repeatMin(min, false); }
-        /// <summary>
-        /// Returns a regular expression that matches this regular expression any number of times within specified boundaries. More times are prioritised (cf. "{min,max}" in traditional regular expression syntax).
-        /// </summary>
-        /// <param name="min">Minimum number of times to match.</param>
-        /// <param name="max">Maximum number of times to match.</param>
-        public Generex<T, IEnumerable<TResult>> RepeatGreedy(int min, int max) { return repeatBetween(min, max, true); }
-        /// <summary>
-        /// Returns a regular expression that matches this regular expression any number of times within specified boundaries. Fewer times are prioritised (cf. "{min,max}?" in traditional regular expression syntax).
-        /// </summary>
-        /// <param name="min">Minimum number of times to match.</param>
-        /// <param name="max">Maximum number of times to match.</param>
-        public Generex<T, IEnumerable<TResult>> Repeat(int min, int max) { return repeatBetween(min, max, false); }
-        /// <summary>
-        /// Returns a regular expression that matches this regular expression the specified number of times (cf. "{times}" in traditional regular expression syntax).
-        /// </summary>
-        public Generex<T, IEnumerable<TResult>> Times(int times)
+        internal TManyGenerex repeatInfinite<TManyGenerex, TManyGenerexMatch>(bool greedy)
+            where TManyGenerex : GenerexWithResultBase<T, IEnumerable<TResult>, TManyGenerex, TManyGenerexMatch>
+            where TManyGenerexMatch : GenerexMatch<T, IEnumerable<TResult>>
         {
-            if (times < 0) throw new ArgumentException("'times' cannot be negative.", "times");
-            return repeatBetween(times, times, true);
-        }
-        /// <summary>
-        /// Returns a regular expression that matches this regular expression one or more times, interspersed with a separator. Fewer times are prioritised.
-        /// </summary>
-        public Generex<T, IEnumerable<TResult>> RepeatWithSeparator(Generex<T> separator) { return ThenRaw(separator.Then<TGenerex, TGenerexMatch, TResult>((TGenerex) this).Repeat(), (first, rest) => first.Concat(rest)); }
-        /// <summary>
-        /// Returns a regular expression that matches this regular expression one or more times, interspersed with a separator. More times are prioritised.
-        /// </summary>
-        public Generex<T, IEnumerable<TResult>> RepeatWithSeparatorGreedy(Generex<T> separator) { return ThenRaw(separator.Then<TGenerex, TGenerexMatch, TResult>((TGenerex) this).RepeatGreedy(), (first, rest) => first.Concat(rest)); }
-
-        /// <summary>
-        /// Generates a matcher that matches this regular expression zero or more times.
-        /// </summary>
-        /// <param name="inner">Inner matcher.</param>
-        /// <param name="greedy">If true, more matches are prioritised; otherwise, fewer matches are prioritised.</param>
-        private static Generex<T, IEnumerable<TResult>>.matcher repeatInfinite(matcher inner, bool greedy)
-        {
-            Generex<T, IEnumerable<TResult>>.matcher newMatcher = null;
-            if (greedy)
-                newMatcher = (input, startIndex) => inner(input, startIndex).SelectMany(m => newMatcher(input, startIndex + m.Length)
-                    .Select(m2 => new GenerexMatchInfo<IEnumerable<TResult>>(m.Result.Concat(m2.Result), m.Length + m2.Length)))
-                    .Concat(new GenerexMatchInfo<IEnumerable<TResult>>(Enumerable.Empty<TResult>(), 0));
-            else
-                newMatcher = (input, startIndex) => new GenerexMatchInfo<IEnumerable<TResult>>(Enumerable.Empty<TResult>(), 0)
-                    .Concat(inner(input, startIndex).SelectMany(m => newMatcher(input, startIndex + m.Length)
-                    .Select(m2 => new GenerexMatchInfo<IEnumerable<TResult>>(m.Result.Concat(m2.Result), m.Length + m2.Length))));
-            return newMatcher;
+            var createRepeatInfiniteMatcher = Ut.Lambda((matcher inner) =>
+            {
+                GenerexWithResultBase<T, IEnumerable<TResult>, TManyGenerex, TManyGenerexMatch>.matcher newMatcher = null;
+                if (greedy)
+                    newMatcher = (input, startIndex) => inner(input, startIndex).SelectMany(m => newMatcher(input, startIndex + m.Length)
+                        .Select(m2 => new LengthAndResult<IEnumerable<TResult>>(m.Result.Concat(m2.Result), m.Length + m2.Length)))
+                        .Concat(new LengthAndResult<IEnumerable<TResult>>(Enumerable.Empty<TResult>(), 0));
+                else
+                    newMatcher = (input, startIndex) => new LengthAndResult<IEnumerable<TResult>>(Enumerable.Empty<TResult>(), 0)
+                        .Concat(inner(input, startIndex).SelectMany(m => newMatcher(input, startIndex + m.Length)
+                        .Select(m2 => new LengthAndResult<IEnumerable<TResult>>(m.Result.Concat(m2.Result), m.Length + m2.Length))));
+                return newMatcher;
+            });
+            return GenerexWithResultBase<T, IEnumerable<TResult>, TManyGenerex, TManyGenerexMatch>.Constructor(
+                createRepeatInfiniteMatcher(_forwardMatcher),
+                createRepeatInfiniteMatcher(_backwardMatcher));
         }
 
-        /// <summary>
-        /// Generates a regular expression that matches this regular expression zero or more times.
-        /// </summary>
-        /// <param name="greedy">If true, more matches are prioritised; otherwise, fewer matches are prioritised.</param>
-        private Generex<T, IEnumerable<TResult>> repeatInfinite(bool greedy)
+        internal TManyGenerex repeatMin<TManyGenerex, TManyGenerexMatch>(int min, bool greedy)
+            where TManyGenerex : GenerexWithResultBase<T, IEnumerable<TResult>, TManyGenerex, TManyGenerexMatch>
+            where TManyGenerexMatch : GenerexMatch<T, IEnumerable<TResult>>
         {
-            return new Generex<T, IEnumerable<TResult>>(repeatInfinite(_forwardMatcher, greedy), repeatInfinite(_backwardMatcher, greedy));
+            if (min < 0) throw new ArgumentException("'min' cannot be negative.", "min");
+            return repeatBetween<TManyGenerex, TManyGenerexMatch>(min, min, true)
+                .ThenRaw<TManyGenerex, TManyGenerexMatch, IEnumerable<TResult>, TManyGenerex, TManyGenerexMatch, IEnumerable<TResult>>(repeatInfinite<TManyGenerex, TManyGenerexMatch>(greedy), Enumerable.Concat);
         }
 
-        /// <summary>
-        /// Generates a matcher that matches this regular expression at least a minimum number of times.
-        /// </summary>
-        /// <param name="min">Minimum number of times to match.</param>
-        /// <param name="greedy">If true, more matches are prioritised; otherwise, fewer matches are prioritised.</param>
-        private Generex<T, IEnumerable<TResult>> repeatMin(int min, bool greedy)
-        {
-            if (min < 0) throw new ArgumentException("'min' cannot be negative.", "min");
-            return repeatBetween(min, min, true).ThenRaw(repeatInfinite(greedy), Enumerable.Concat);
-        }
-
-        /// <summary>
-        /// Generates a matcher that matches this regular expression at least a minimum number of times and at most a maximum number of times.
-        /// </summary>
-        /// <param name="min">Minimum number of times to match.</param>
-        /// <param name="max">Maximum number of times to match.</param>
-        /// <param name="greedy">If true, more matches are prioritised; otherwise, fewer matches are prioritised.</param>
-        private Generex<T, IEnumerable<TResult>> repeatBetween(int min, int max, bool greedy)
+        internal TManyGenerex repeatBetween<TManyGenerex, TManyGenerexMatch>(int min, int max, bool greedy)
+            where TManyGenerex : GenerexWithResultBase<T, IEnumerable<TResult>, TManyGenerex, TManyGenerexMatch>
+            where TManyGenerexMatch : GenerexMatch<T, IEnumerable<TResult>>
         {
             if (min < 0) throw new ArgumentException("'min' cannot be negative.", "min");
             if (max < min) throw new ArgumentException("'max' cannot be smaller than 'min'.", "max");
                 InnerForwardMatcher = _forwardMatcher,
                 InnerBackwardMatcher = _backwardMatcher
             };
-            return new Generex<T, IEnumerable<TResult>>(rm.ForwardMatcher, rm.BackwardMatcher);
+            return GenerexWithResultBase<T, IEnumerable<TResult>, TManyGenerex, TManyGenerexMatch>.Constructor(rm.ForwardMatcher, rm.BackwardMatcher);
         }
 
         private sealed class repeatMatcher
             public bool Greedy;
             public matcher InnerForwardMatcher;
             public matcher InnerBackwardMatcher;
-            public IEnumerable<GenerexMatchInfo<IEnumerable<TResult>>> ForwardMatcher(T[] input, int startIndex) { return matcher(input, startIndex, 0, backward: false); }
-            public IEnumerable<GenerexMatchInfo<IEnumerable<TResult>>> BackwardMatcher(T[] input, int startIndex) { return matcher(input, startIndex, 0, backward: true); }
-            private IEnumerable<GenerexMatchInfo<IEnumerable<TResult>>> matcher(T[] input, int startIndex, int iteration, bool backward)
+            public IEnumerable<LengthAndResult<IEnumerable<TResult>>> ForwardMatcher(T[] input, int startIndex) { return matcher(input, startIndex, 0, backward: false); }
+            public IEnumerable<LengthAndResult<IEnumerable<TResult>>> BackwardMatcher(T[] input, int startIndex) { return matcher(input, startIndex, 0, backward: true); }
+            private IEnumerable<LengthAndResult<IEnumerable<TResult>>> matcher(T[] input, int startIndex, int iteration, bool backward)
             {
                 if (!Greedy && iteration >= MinTimes)
-                    yield return new GenerexMatchInfo<IEnumerable<TResult>>(Enumerable.Empty<TResult>(), 0);
+                    yield return new LengthAndResult<IEnumerable<TResult>>(Enumerable.Empty<TResult>(), 0);
                 if (iteration < MaxTimes)
                 {
                     foreach (var m in (backward ? InnerBackwardMatcher : InnerForwardMatcher)(input, startIndex))
                         foreach (var m2 in matcher(input, startIndex + m.Length, iteration + 1, backward))
-                            yield return new GenerexMatchInfo<IEnumerable<TResult>>(
+                            yield return new LengthAndResult<IEnumerable<TResult>>(
                                 backward ? m2.Result.Concat(m.Result) : m.Result.Concat(m2.Result),
                                 m.Length + m2.Length
                             );
                 }
                 if (Greedy && iteration >= MinTimes)
-                    yield return new GenerexMatchInfo<IEnumerable<TResult>>(Enumerable.Empty<TResult>(), 0);
+                    yield return new LengthAndResult<IEnumerable<TResult>>(Enumerable.Empty<TResult>(), 0);
             }
         }
 
         }
 
         /// <summary>Turns the current regular expression into a zero-width negative look-ahead assertion, which returns the specified default result in case of a match.</summary>
-        public TGenerex LookAheadNegative(TResult defaultMatch) { return lookNegative(behind: false, defaultMatch: new[] { new GenerexMatchInfo<TResult>(defaultMatch, 0) }); }
+        public TGenerex LookAheadNegative(TResult defaultMatch) { return lookNegative(behind: false, defaultMatch: new[] { new LengthAndResult<TResult>(defaultMatch, 0) }); }
         /// <summary>Turns the current regular expression into a zero-width negative look-behind assertion, which returns the specified default result in case of a match.</summary>
-        public TGenerex LookBehindNegative(TResult defaultMatch) { return lookNegative(behind: true, defaultMatch: new[] { new GenerexMatchInfo<TResult>(defaultMatch, 0) }); }
+        public TGenerex LookBehindNegative(TResult defaultMatch) { return lookNegative(behind: true, defaultMatch: new[] { new LengthAndResult<TResult>(defaultMatch, 0) }); }
 
         /// <summary>Processes each match of this regular expression by running it through a provided selector.</summary>
         /// <typeparam name="TOtherResult">Type of the object returned by <paramref name="selector"/>.</typeparam>
         /// <param name="selector">Function to process a regular expression match.</param>
-        public Generex<T, TOtherResult> Process<TOtherResult>(Func<TGenerexMatch, TOtherResult> selector)
+        public TOtherGenerex Process<TOtherGenerex, TOtherGenerexMatch, TOtherResult>(Func<TGenerexMatch, TOtherResult> selector)
+            where TOtherGenerex : GenerexWithResultBase<T, TOtherResult, TOtherGenerex, TOtherGenerexMatch>
+            where TOtherGenerexMatch : GenerexMatch<T, TOtherResult>
         {
-            return new Generex<T, TOtherResult>(
-                (input, startIndex) => _forwardMatcher(input, startIndex).Select(m => new GenerexMatchInfo<TOtherResult>(selector(createMatch(input, startIndex, m)), m.Length)),
-                (input, startIndex) => _backwardMatcher(input, startIndex).Select(m => new GenerexMatchInfo<TOtherResult>(selector(createBackwardsMatch(input, startIndex, m)), m.Length))
+            return GenerexWithResultBase<T, TOtherResult, TOtherGenerex, TOtherGenerexMatch>.Constructor(
+                (input, startIndex) => _forwardMatcher(input, startIndex).Select(m => new LengthAndResult<TOtherResult>(selector(createMatch(input, startIndex, m)), m.Length)),
+                (input, startIndex) => _backwardMatcher(input, startIndex).Select(m => new LengthAndResult<TOtherResult>(selector(createBackwardsMatch(input, startIndex, m)), m.Length))
             );
         }
 
         /// <summary>Processes each match of this regular expression by running it through a provided selector.</summary>
         /// <typeparam name="TOtherResult">Type of the object returned by <paramref name="selector"/>.</typeparam>
         /// <param name="selector">Function to process a regular expression match.</param>
-        public Generex<T, TOtherResult> ProcessRaw<TOtherResult>(Func<TResult, TOtherResult> selector)
+        public TOtherGenerex ProcessRaw<TOtherGenerex, TOtherGenerexMatch, TOtherResult>(Func<TResult, TOtherResult> selector)
+            where TOtherGenerex : GenerexWithResultBase<T, TOtherResult, TOtherGenerex, TOtherGenerexMatch>
+            where TOtherGenerexMatch : GenerexMatch<T, TOtherResult>
         {
-            return new Generex<T, TOtherResult>(
-                (input, startIndex) => _forwardMatcher(input, startIndex).Select(m => new GenerexMatchInfo<TOtherResult>(selector(m.Result), m.Length)),
-                (input, startIndex) => _backwardMatcher(input, startIndex).Select(m => new GenerexMatchInfo<TOtherResult>(selector(m.Result), m.Length))
+            return GenerexWithResultBase<T, TOtherResult, TOtherGenerex, TOtherGenerexMatch>.Constructor(
+                (input, startIndex) => _forwardMatcher(input, startIndex).Select(m => new LengthAndResult<TOtherResult>(selector(m.Result), m.Length)),
+                (input, startIndex) => _backwardMatcher(input, startIndex).Select(m => new LengthAndResult<TOtherResult>(selector(m.Result), m.Length))
             );
         }
-
-        /// <summary>Generates a recursive regular expression, i.e. one that can contain itself, allowing the matching of arbitrarily nested expressions.</summary>
-        /// <param name="generator">A function that generates the regular expression from an object that recursively represents the result.</param>
-        public static TGenerex Recursive(Func<TGenerex, TGenerex> generator)
-        {
-            if (generator == null)
-                throw new ArgumentNullException("generator");
-
-            matcher recursiveForward = null, recursiveBackward = null;
-
-            // Note the following *must* be lambdas so that they capture the above *variables* (which are modified afterwards), not their current value (which would be null)
-            var carrier = Constructor(
-                (input, startIndex) => recursiveForward(input, startIndex),
-                (input, startIndex) => recursiveBackward(input, startIndex)
-            );
-
-            var generated = generator(carrier);
-            if (generated == null)
-                throw new InvalidOperationException("Generator function returned null.");
-            recursiveForward = generated._forwardMatcher;
-            recursiveBackward = generated._backwardMatcher;
-            return generated;
-        }
     }
 }

LengthAndResult.cs

+
+namespace RT.Generexes
+{
+    /// <summary>Encapsulates preliminary information about matches generated by descendants of <see cref="GenerexWithResultBase{T,TResult,TGenerex,TGenerexMatch}"/>.</summary>
+    /// <typeparam name="TResult">Type of objects generated from each match of the regular expression.</typeparam>
+    /// <remarks>This type is an implementation detail. It is not intended to be used outside this library.
+    /// However, it cannot be made internal because it is used as a generic type parameter in a base-type declaration.</remarks>
+    public struct LengthAndResult<TResult>
+    {
+        internal TResult Result { get; private set; }
+        internal int Length { get; private set; }
+        internal LengthAndResult(TResult result, int length) : this() { Result = result; Length = length; }
+        internal LengthAndResult<TResult> Add(int extraLength)
+        {
+            return new LengthAndResult<TResult>(Result, Length + extraLength);
+        }
+    }
+}
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace RT.Generexes
+{
+    /// <summary>
+    /// Provides regular-expression functionality for strings.
+    /// </summary>
+    public sealed class Stringerex : GenerexNoResultBase<char, Stringerex, StringerexMatch>
+    {
+        internal sealed override StringerexMatch createNoResultMatch(char[] input, int index, int matchLength)
+        {
+            return new StringerexMatch(input, index, matchLength);
+        }
+
+        /// <summary>
+        /// Instantiates an empty regular expression (always matches).
+        /// </summary>
+        public Stringerex() : base(emptyMatch, emptyMatch) { }
+
+        /// <summary>
+        /// Instantiates a regular expression that matches a specified string.
+        /// </summary>
+        public Stringerex(string elements) : base(EqualityComparer<char>.Default, elements.ToCharArray()) { }
+
+        /// <summary>
+        /// Instantiates a regular expression that matches a sequence of consecutive elements.
+        /// </summary>
+        public Stringerex(IEnumerable<char> elements) : base(EqualityComparer<char>.Default, elements.ToArray()) { }
+
+        /// <summary>
+        /// Instantiates a regular expression that matches a sequence of consecutive elements using the specified equality comparer.
+        /// </summary>
+        public Stringerex(IEqualityComparer<char> comparer, string elements) : base(comparer, elements.ToCharArray()) { }
+
+        /// <summary>
+        /// Instantiates a regular expression that matches a sequence of consecutive elements.
+        /// </summary>
+        public Stringerex(IEqualityComparer<char> comparer, IEnumerable<char> elements) : base(comparer, elements.ToArray()) { }
+
+        /// <summary>
+        /// Instantiates a regular expression that matches a single character that satisfies the given predicate (cf. "[...]" in traditional regular expression syntax).
+        /// </summary>
+        public Stringerex(Predicate<char> predicate) : base(predicate) { }
+
+        /// <summary>
+        /// Instantiates a regular expression that matches a sequence of consecutive regular expressions.
+        /// </summary>
+        public Stringerex(params Stringerex[] generexSequence)
+            : base(
+                sequenceMatcher(generexSequence, backward: false),
+                sequenceMatcher(generexSequence, backward: true)) { }
+
+        private Stringerex(matcher forward, matcher backward) : base(forward, backward) { }
+        static Stringerex() { Constructor = (forward, backward) => new Stringerex(forward, backward); }
+
+        /// <summary>Implicitly converts a character into a regular expression that matches just that character.</summary>
+        public static implicit operator Stringerex(char ch) { return new Stringerex(new[] { ch }); }
+        /// <summary>Implicitly converts a string into a regular expression that matches that string.</summary>
+        public static implicit operator Stringerex(string str) { return new Stringerex(str); }
+        /// <summary>Implicitly converts a predicate into a regular expression that matches a single character satisfying the predicate.</summary>
+        public static implicit operator Stringerex(Predicate<char> predicate) { return new Stringerex(predicate); }
+
+        /// <summary>
+        /// Determines whether the given string contains a match for this regular expression, optionally starting the search at a specified character index.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="startAt">Optional character index at which to start the search. Matches that start before this index are not included.</param>
+        public bool IsMatch(string input, int startAt = 0) { return base.IsMatch(input.ToCharArray(), startAt); }
+
+        /// <summary>
+        /// Determines whether the given string matches this regular expression at a specific character index.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="mustStartAt">Index at which the match must start (default is 0).</param>
+        /// <returns>True if a match starting at the specified index exists (which need not run all the way to the end of the string); otherwise, false.</returns>
+        public bool IsMatchAt(string input, int mustStartAt = 0) { return base.IsMatchAt(input.ToCharArray(), mustStartAt); }
+
+        /// <summary>
+        /// Determines whether the given string matches this regular expression up to a specific character index.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="mustEndAt">Index at which the match must end (default is the end of the string).</param>
+        /// <returns>True if a match ending at the specified index exists (which need not begin at the start of the string); otherwise, false.</returns>
+        public bool IsMatchUpTo(string input, int? mustEndAt = null) { return base.IsMatchUpTo(input.ToCharArray(), mustEndAt); }
+
+        /// <summary>
+        /// Determines whether the given string matches this regular expression exactly.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="mustStartAt">Index at which the match must start (default is 0).</param>
+        /// <param name="mustEndAt">Index at which the match must end (default is the end of the string).</param>
+        /// <returns>True if a match starting and ending at the specified indexes exists; otherwise, false.</returns>
+        public bool IsMatchExact(string input, int mustStartAt = 0, int? mustEndAt = null) { return base.IsMatchExact(input.ToCharArray(), mustStartAt, mustEndAt); }
+
+        /// <summary>
+        /// Determines whether the given string contains a match for this regular expression that ends before the specified maximum character index.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="endAt">Optional index before which a match must end. The search begins by matching from this index backwards, and then proceeds towards the start of the string.</param>
+        public bool IsMatchReverse(string input, int? endAt = null) { return base.IsMatchReverse(input.ToCharArray(), endAt); }
+
+        /// <summary>
+        /// Determines whether the given string matches this regular expression, and if so, returns information about the first match.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="startAt">Optional index at which to start the search. Matches that start before this index are not included.</param>
+        /// <returns>An object describing a regular expression match in case of success; null if no match.</returns>
+        public StringerexMatch Match(string input, int startAt = 0) { return base.Match(input.ToCharArray(), startAt); }
+
+        /// <summary>
+        /// Determines whether the given string matches this regular expression exactly, and if so, returns information about the match.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="mustStartAt">Index at which the match must start (default is 0).</param>
+        /// <param name="mustEndAt">Index at which the match must end (default is the end of the string).</param>
+        /// <returns>An object describing the regular expression match in case of success; null if no match.</returns>
+        public StringerexMatch MatchExact(string input, int mustStartAt = 0, int? mustEndAt = null) { return base.MatchExact(input.ToCharArray(), mustStartAt, mustEndAt); }
+
+        /// <summary>
+        /// Determines whether the given string matches this regular expression, and if so, returns information about the first match
+        /// found by matching the regular expression backwards (starting from the end of the string).
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="endAt">Optional index at which to end the search. Matches that end at or after this index are not included.</param>
+        /// <returns>An object describing a regular expression match in case of success; null if no match.</returns>
+        public StringerexMatch MatchReverse(string input, int? endAt = null) { return base.MatchReverse(input.ToCharArray(), endAt); }
+
+        /// <summary>
+        /// Returns a sequence of non-overlapping regular expression matches going backwards (starting at the end of the specified
+        /// string), optionally starting the search at the specified index.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="endAt">Optional index at which to begin the reverse search. Matches that end at or after this index are not included.</param>
+        public IEnumerable<StringerexMatch> MatchesReverse(string input, int? endAt = null) { return base.MatchesReverse(input.ToCharArray(), endAt); }
+
+        /// <summary>
+        /// Returns a sequence of non-overlapping regular expression matches, optionally starting the search at the specified character index.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="startAt">Optional index at which to start the search. Matches that start before this index are not included.</param>
+        /// <remarks>The behaviour is analogous to <see cref="System.Text.RegularExpressions.Regex.Matches(string,string)"/>.
+        /// The documentation for that method claims that it returns “all occurrences of the regular expression”, but this is false.</remarks>
+        public IEnumerable<StringerexMatch> Matches(string input, int startAt = 0) { return base.Matches(input.ToCharArray(), startAt); }
+
+        /// <summary>
+        /// Returns a regular expression that matches either this regular expression or the specified string (cf. "|" or "[...]" in traditional regular expression syntax).
+        /// </summary>
+        /// <seealso cref="Or(IEqualityComparer{char},string)"/>
+        public Stringerex Or(string str) { return base.Or(EqualityComparer<char>.Default, str.ToCharArray()); }
+
+        /// <summary>
+        /// Returns a regular expression that matches either this regular expression or the specified string using the specified equality comparer (cf. "|" or "[...]" in traditional regular expression syntax).
+        /// </summary>
+        /// <seealso cref="Or(string)"/>
+        public Stringerex Or(IEqualityComparer<char> comparer, string str) { return base.Or(comparer, str.ToCharArray()); }
+
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression followed by the specified string.
+        /// </summary>
+        public Stringerex Then(string str) { return base.Then(EqualityComparer<char>.Default, str.ToCharArray()); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression followed by the specified string, using the specified equality comparer.
+        /// </summary>
+        public Stringerex Then(IEqualityComparer<char> comparer, string str) { return base.Then(comparer, str.ToCharArray()); }
+
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression, followed by the specified other,
+        /// and retains the match object generated by each match of the other regular expression.
+        /// </summary>
+        public Stringerex<TResult> Then<TResult>(Stringerex<TResult> other) { return Then<Stringerex<TResult>, StringerexMatch<TResult>, TResult>(other); }
+
+        /// <summary>Processes each match of this regular expression by running it through a provided selector.</summary>
+        /// <typeparam name="TResult">Type of the object returned by <paramref name="selector"/>.</typeparam>
+        /// <param name="selector">Function to process a regular expression match.</param>
+        public Stringerex<TResult> Process<TResult>(Func<StringerexMatch, TResult> selector) { return Process<Stringerex<TResult>, StringerexMatch<TResult>, TResult>(selector); }
+
+        /// <summary>
+        /// Instantiates a regular expression that matches the specified string.
+        /// </summary>
+        public static Stringerex New(string str) { return new Stringerex(str); }
+        /// <summary>
+        /// Instantiates a regular expression that matches the specified string using the specified equality comparer.
+        /// </summary>
+        public static Stringerex New(IEqualityComparer<char> comparer, string str) { return new Stringerex(comparer, str); }
+        /// <summary>
+        /// Instantiates a regular expression that matches a single character that satisfies the given predicate (cf. "[...]" in traditional regular expression syntax).
+        /// </summary>
+        public static Stringerex New(Predicate<char> predicate) { return new Stringerex(predicate); }
+        /// <summary>
+        /// Instantiates a regular expression that matches a sequence of consecutive regular expressions.
+        /// </summary>
+        public static Stringerex New(params Stringerex[] stringerexes) { return new Stringerex(stringerexes); }
+
+        /// <summary>
+        /// Returns a regular expression that matches any of the specified regular expressions (cf. "|" in traditional regular expression syntax).
+        /// </summary>
+        public static Stringerex Ors(IEnumerable<Stringerex> generexes) { return Stringerex.Ors(generexes); }
+        /// <summary>
+        /// Returns a regular expression that matches any of the specified regular expressions (cf. "|" in traditional regular expression syntax).
+        /// </summary>
+        public static Stringerex Ors(params Stringerex[] generexes) { return Stringerex.Ors(generexes); }
+        /// <summary>
+        /// Returns a regular expression that matches any of the specified regular expressions (cf. "|" in traditional regular expression syntax).
+        /// </summary>
+        public static Stringerex<TResult> Ors<TResult>(IEnumerable<Stringerex<TResult>> generexes) { return Stringerex<TResult>.Ors(generexes); }
+        /// <summary>
+        /// Returns a regular expression that matches any of the specified regular expressions (cf. "|" in traditional regular expression syntax).
+        /// </summary>
+        public static Stringerex<TResult> Ors<TResult>(params Stringerex<TResult>[] generexes) { return Stringerex<TResult>.Ors(generexes); }
+
+        /// <summary>Generates a recursive regular expression, i.e. one that can contain itself, allowing the matching of arbitrarily nested expressions.</summary>
+        /// <param name="generator">A function that generates the regular expression from an object that recursively represents the result.</param>
+        public static Stringerex<TResult> Recursive<TResult>(Func<Stringerex<TResult>, Stringerex<TResult>> generator) { return Stringerex<TResult>.Recursive(generator); }
+    }
+}

StringerexWithResult.cs

+using System;
+using System.Collections.Generic;
+using RT.Util.ExtensionMethods;
+
+namespace RT.Generexes
+{
+    /// <summary>
+    /// Provides regular-expression functionality for strings.
+    /// </summary>
+    /// <typeparam name="TResult">Type of objects generated from each match of the regular expression.</typeparam>
+    /// <remarks>This type is not directly instantiated; use <see cref="Stringerex.Process"/>.</remarks>
+    public sealed class Stringerex<TResult> : GenerexWithResultBase<char, TResult, Stringerex<TResult>, StringerexMatch<TResult>>
+    {
+        internal sealed override StringerexMatch<TResult> createMatchWithResult(TResult result, char[] input, int index, int length)
+        {
+            return new StringerexMatch<TResult>(result, input, index, length);
+        }
+
+        internal Stringerex(matcher forward, matcher backward) : base(forward, backward) { }
+        static Stringerex() { Constructor = (forward, backward) => new Stringerex<TResult>(forward, backward); }
+
+        /// <summary>
+        /// Determines whether the given string contains a match for this regular expression, optionally starting the search at a specified character index.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="startAt">Optional character index at which to start the search. Matches that start before this index are not included.</param>
+        public bool IsMatch(string input, int startAt = 0) { return base.IsMatch(input.ToCharArray(), startAt); }
+
+        /// <summary>
+        /// Determines whether the given string matches this regular expression at a specific character index.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="mustStartAt">Index at which the match must start (default is 0).</param>
+        /// <returns>True if a match starting at the specified index exists (which need not run all the way to the end of the string); otherwise, false.</returns>
+        public bool IsMatchAt(string input, int mustStartAt = 0) { return base.IsMatchAt(input.ToCharArray(), mustStartAt); }
+
+        /// <summary>
+        /// Determines whether the given string matches this regular expression up to a specific character index.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="mustEndAt">Index at which the match must end (default is the end of the string).</param>
+        /// <returns>True if a match ending at the specified index exists (which need not begin at the start of the string); otherwise, false.</returns>
+        public bool IsMatchUpTo(string input, int? mustEndAt = null) { return base.IsMatchUpTo(input.ToCharArray(), mustEndAt); }
+
+        /// <summary>
+        /// Determines whether the given string matches this regular expression exactly.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="mustStartAt">Index at which the match must start (default is 0).</param>
+        /// <param name="mustEndAt">Index at which the match must end (default is the end of the string).</param>
+        /// <returns>True if a match starting and ending at the specified indexes exists; otherwise, false.</returns>
+        public bool IsMatchExact(string input, int mustStartAt = 0, int? mustEndAt = null) { return base.IsMatchExact(input.ToCharArray(), mustStartAt, mustEndAt); }
+
+        /// <summary>
+        /// Determines whether the given string contains a match for this regular expression that ends before the specified maximum character index.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="endAt">Optional index before which a match must end. The search begins by matching from this index backwards, and then proceeds towards the start of the string.</param>
+        public bool IsMatchReverse(string input, int? endAt = null) { return base.IsMatchReverse(input.ToCharArray(), endAt); }
+
+        /// <summary>
+        /// Determines whether the given string matches this regular expression, and if so, returns information about the first match.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="startAt">Optional index at which to start the search. Matches that start before this index are not included.</param>
+        /// <returns>An object describing a regular expression match in case of success; null if no match.</returns>
+        public StringerexMatch<TResult> Match(string input, int startAt = 0) { return base.Match(input.ToCharArray(), startAt); }
+
+        /// <summary>
+        /// Determines whether the given string matches this regular expression exactly, and if so, returns information about the match.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="mustStartAt">Index at which the match must start (default is 0).</param>
+        /// <param name="mustEndAt">Index at which the match must end (default is the end of the string).</param>
+        /// <returns>An object describing the regular expression match in case of success; null if no match.</returns>
+        public StringerexMatch<TResult> MatchExact(string input, int mustStartAt = 0, int? mustEndAt = null) { return base.MatchExact(input.ToCharArray(), mustStartAt, mustEndAt); }
+
+        /// <summary>
+        /// Determines whether the given string matches this regular expression, and if so, returns information about the first match
+        /// found by matching the regular expression backwards (starting from the end of the string).
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="endAt">Optional index at which to end the search. Matches that end at or after this index are not included.</param>
+        /// <returns>An object describing a regular expression match in case of success; null if no match.</returns>
+        public StringerexMatch<TResult> MatchReverse(string input, int? endAt = null) { return base.MatchReverse(input.ToCharArray(), endAt); }
+
+        /// <summary>
+        /// Returns a sequence of non-overlapping regular expression matches going backwards (starting at the end of the specified
+        /// string), optionally starting the search at the specified index.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="endAt">Optional index at which to begin the reverse search. Matches that end at or after this index are not included.</param>
+        public IEnumerable<StringerexMatch<TResult>> MatchesReverse(string input, int? endAt = null) { return base.MatchesReverse(input.ToCharArray(), endAt); }
+
+        /// <summary>
+        /// Returns a sequence of non-overlapping regular expression matches, optionally starting the search at the specified character index.
+        /// </summary>
+        /// <param name="input">String to match the regular expression against.</param>
+        /// <param name="startAt">Optional index at which to start the search. Matches that start before this index are not included.</param>
+        /// <remarks>The behaviour is analogous to <see cref="System.Text.RegularExpressions.Regex.Matches(string,string)"/>.
+        /// The documentation for that method claims that it returns “all occurrences of the regular expression”, but this is false.</remarks>
+        public IEnumerable<StringerexMatch<TResult>> Matches(string input, int startAt = 0) { return base.Matches(input.ToCharArray(), startAt); }
+
+        /// <summary>Processes each match of this regular expression by running it through a provided selector.</summary>
+        /// <typeparam name="TOtherResult">Type of the object returned by <paramref name="selector"/>.</typeparam>
+        /// <param name="selector">Function to process a regular expression match.</param>
+        public Stringerex<TOtherResult> Process<TOtherResult>(Func<StringerexMatch<TResult>, TOtherResult> selector)
+        {
+            return base.Process<Stringerex<TOtherResult>, StringerexMatch<TOtherResult>, TOtherResult>(selector);
+        }
+
+        /// <summary>Processes each match of this regular expression by running each result through a provided selector.</summary>
+        /// <typeparam name="TOtherResult">Type of the object returned by <paramref name="selector"/>.</typeparam>
+        /// <param name="selector">Function to process the result of a regular expression match.</param>
+        public Stringerex<TOtherResult> ProcessRaw<TOtherResult>(Func<TResult, TOtherResult> selector)
+        {
+            return base.ProcessRaw<Stringerex<TOtherResult>, StringerexMatch<TOtherResult>, TOtherResult>(selector);
+        }
+
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression, followed by the specified ones,
+        /// and generates a match object that combines the result of this regular expression with the match of the other.
+        /// </summary>
+        public Stringerex<TCombined> Then<TCombined>(Stringerex other, Func<TResult, StringerexMatch, TCombined> selector)
+        {
+            return base.Then<Stringerex, StringerexMatch, Stringerex<TCombined>, StringerexMatch<TCombined>, TCombined>(other, selector);
+        }
+
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression, followed by the specified one,
+        /// and generates a match object that combines the result of this regular expression with the match of the other.
+        /// </summary>
+        public Stringerex<TCombined> Then<TOther, TCombined>(Stringerex<TOther> other, Func<TResult, StringerexMatch<TOther>, TCombined> selector)
+        {
+            return base.Then<Stringerex<TOther>, StringerexMatch<TOther>, TOther, Stringerex<TCombined>, StringerexMatch<TCombined>, TCombined>(other, selector);
+        }
+
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression, followed by the specified ones,
+        /// and generates a match object that combines the original two matches.
+        /// </summary>
+        public Stringerex<TCombined> ThenRaw<TOther, TCombined>(Stringerex<TOther> other, Func<TResult, TOther, TCombined> selector)
+        {
+            return base.ThenRaw<Stringerex<TOther>, StringerexMatch<TOther>, TOther, Stringerex<TCombined>, StringerexMatch<TCombined>, TCombined>(other, selector);
+        }
+
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression zero times or once. Once is prioritised (cf. "?" in traditional regular expression syntax).
+        /// </summary>
+        public Stringerex<IEnumerable<TResult>> OptionalGreedy() { return repeatBetween<Stringerex<IEnumerable<TResult>>, StringerexMatch<IEnumerable<TResult>>>(0, 1, greedy: true); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression zero times or once. Zero times is prioritised (cf. "??" in traditional regular expression syntax).
+        /// </summary>
+        public Stringerex<IEnumerable<TResult>> Optional() { return repeatBetween<Stringerex<IEnumerable<TResult>>, StringerexMatch<IEnumerable<TResult>>>(0, 1, greedy: false); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression zero or more times. More times are prioritised (cf. "*" in traditional regular expression syntax).
+        /// </summary>
+        public Stringerex<IEnumerable<TResult>> RepeatGreedy() { return repeatInfinite<Stringerex<IEnumerable<TResult>>, StringerexMatch<IEnumerable<TResult>>>(greedy: true); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression zero or more times. Fewer times are prioritised (cf. "*?" in traditional regular expression syntax).
+        /// </summary>
+        public Stringerex<IEnumerable<TResult>> Repeat() { return repeatInfinite<Stringerex<IEnumerable<TResult>>, StringerexMatch<IEnumerable<TResult>>>(greedy: false); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression the specified number of times or more. More times are prioritised (cf. "{min,}" in traditional regular expression syntax).
+        /// </summary>
+        public Stringerex<IEnumerable<TResult>> RepeatGreedy(int min) { return repeatMin<Stringerex<IEnumerable<TResult>>, StringerexMatch<IEnumerable<TResult>>>(min, greedy: true); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression the specified number of times or more. Fewer times are prioritised (cf. "{min,}?" in traditional regular expression syntax).
+        /// </summary>
+        public Stringerex<IEnumerable<TResult>> Repeat(int min) { return repeatMin<Stringerex<IEnumerable<TResult>>, StringerexMatch<IEnumerable<TResult>>>(min, greedy: false); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression any number of times within specified boundaries. More times are prioritised (cf. "{min,max}" in traditional regular expression syntax).
+        /// </summary>
+        /// <param name="min">Minimum number of times to match.</param>
+        /// <param name="max">Maximum number of times to match.</param>
+        public Stringerex<IEnumerable<TResult>> RepeatGreedy(int min, int max) { return repeatBetween<Stringerex<IEnumerable<TResult>>, StringerexMatch<IEnumerable<TResult>>>(min, max, greedy: true); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression any number of times within specified boundaries. Fewer times are prioritised (cf. "{min,max}?" in traditional regular expression syntax).
+        /// </summary>
+        /// <param name="min">Minimum number of times to match.</param>
+        /// <param name="max">Maximum number of times to match.</param>
+        public Stringerex<IEnumerable<TResult>> Repeat(int min, int max) { return repeatBetween<Stringerex<IEnumerable<TResult>>, StringerexMatch<IEnumerable<TResult>>>(min, max, greedy: false); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression the specified number of times (cf. "{times}" in traditional regular expression syntax).
+        /// </summary>
+        public Stringerex<IEnumerable<TResult>> Times(int times)
+        {
+            if (times < 0) throw new ArgumentException("'times' cannot be negative.", "times");
+            return repeatBetween<Stringerex<IEnumerable<TResult>>, StringerexMatch<IEnumerable<TResult>>>(times, times, true);
+        }
+
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression one or more times, interspersed with a separator. Fewer times are prioritised.
+        /// </summary>
+        public Stringerex<IEnumerable<TResult>> RepeatWithSeparator(Stringerex separator)
+        {
+            return ThenRaw(separator.Then(this).Repeat(), IEnumerableExtensions.Concat);
+        }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression one or more times, interspersed with a separator. More times are prioritised.
+        /// </summary>
+        public Stringerex<IEnumerable<TResult>> RepeatWithSeparatorGreedy(Stringerex separator)
+        {
+            return ThenRaw(separator.Then(this).RepeatGreedy(), IEnumerableExtensions.Concat);
+        }
+    }
+}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.