Commits

Timwi committed e5480d4

* Abstract a lot of Generex<T> into a GenerexNoResultBase<T, TGenerex, TGenerexMatch> which will later allow us to write a Stringerex
* Change build path
* Comment fixes

Comments (0)

Files changed (5)

     <AssemblyName>RT.Generex</AssemblyName>
     <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
+    <OutputPath>..\builds\$(Configuration)\</OutputPath>
+    <IntermediateOutputPath>..\builds\$(Configuration)\obj-$(AssemblyName)\</IntermediateOutputPath>
+    <BaseIntermediateOutputPath>..\builds\$(Configuration)\obj-$(AssemblyName)\</BaseIntermediateOutputPath>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
     <DebugType>full</DebugType>
     <Optimize>false</Optimize>
-    <OutputPath>bin\Debug\</OutputPath>
     <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
+    <DocumentationFile>..\builds\Debug\RT.Generex.XML</DocumentationFile>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>pdbonly</DebugType>
     <Optimize>true</Optimize>
-    <OutputPath>bin\Release\</OutputPath>
     <DefineConstants>TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
   </PropertyGroup>
   <ItemGroup>
     <Reference Include="System" />
     <Compile Include="GenerexBase.cs" />
     <Compile Include="GenerexMatch.cs" />
     <Compile Include="GenerexMatchInfo.cs" />
+    <Compile Include="GenerexNoResultBase.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
   <ItemGroup>
     /// Provides regular-expression functionality for collections of arbitrary objects.
     /// </summary>
     /// <typeparam name="T">Type of the objects in the collection.</typeparam>
-    public sealed class Generex<T> : GenerexBase<T, int, Generex<T>, GenerexMatch<T>>
+    public sealed class Generex<T> : GenerexNoResultBase<T, Generex<T>, GenerexMatch<T>>
     {
-        internal override Generex<T> create(matcher forward, matcher backward) { return new Generex<T>(forward, backward); }
-        internal override int getLength(int match) { return match; }
-        internal override int add(int match, int extra) { return match + extra; }
-        internal override int setZero(int match) { return 0; }
-        internal override GenerexMatch<T> createMatch(T[] input, int index, int match) { return new GenerexMatch<T>(input, index, match); }
-        internal override GenerexMatch<T> createBackwardsMatch(T[] input, int index, int match) { return new GenerexMatch<T>(input, index + match, -match); }
+        internal sealed override Generex<T> create(matcher forward, matcher backward) { return new Generex<T>(forward, backward); }
+        internal sealed override GenerexMatch<T> createNoResultMatch(T[] input, int index, int matchLength) { return new GenerexMatch<T>(input, index, matchLength); }
 
         /// <summary>
         /// Instantiates an empty regular expression (always matches).
         /// <summary>
         /// Instantiates a regular expression that matches a sequence of consecutive elements.
         /// </summary>
-        public Generex(params T[] elements)
-            : this(EqualityComparer<T>.Default, elements) { }
+        public Generex(params T[] elements) : base(EqualityComparer<T>.Default, elements) { }
 
         /// <summary>
         /// Instantiates a regular expression that matches a sequence of consecutive elements.
         /// </summary>
-        public Generex(IEnumerable<T> elements)
-            : this(EqualityComparer<T>.Default, elements.ToArray()) { }
+        public Generex(IEnumerable<T> elements) : base(EqualityComparer<T>.Default, elements.ToArray()) { }
 
         /// <summary>
         /// Instantiates a regular expression that matches a sequence of consecutive elements using the specified equality comparer.
         /// </summary>
-        public Generex(IEqualityComparer<T> comparer, params T[] elements)
-            : base(
-                elementsMatcher(elements, comparer, backward: false),
-                elementsMatcher(elements, comparer, backward: true)) { }
+        public Generex(IEqualityComparer<T> comparer, params T[] elements) : base(comparer, elements) { }
 
         /// <summary>
         /// Instantiates a regular expression that matches a sequence of consecutive elements.
         /// </summary>
-        public Generex(IEqualityComparer<T> comparer, IEnumerable<T> elements)
-            : this(comparer, elements.ToArray()) { }
+        public Generex(IEqualityComparer<T> comparer, IEnumerable<T> elements) : base(comparer, elements.ToArray()) { }
 
         /// <summary>
         /// Instantiates a regular expression that matches a single element that satisfies the given predicate (cf. "[...]" in traditional regular expression syntax).
         /// </summary>
-        public Generex(Predicate<T> predicate)
-            : base(
-                (input, startIndex) => startIndex >= input.Length || !predicate(input[startIndex]) ? Generex.NoMatch : Generex.OneElementMatch,
-                (input, startIndex) => startIndex <= 0 || !predicate(input[startIndex - 1]) ? Generex.NoMatch : Generex.NegativeOneElementMatch) { }
+        public Generex(Predicate<T> predicate) : base(predicate) { }
 
         /// <summary>
         /// Instantiates a regular expression that matches a sequence of consecutive regular expressions.
         /// </summary>
         public Generex(params Generex<T>[] generexes)
             : base(
-                generexes.Length == 0 ? emptyMatch : generexes.Select(p => p._forwardMatcher).Aggregate(thenSimple),
-                generexes.Length == 0 ? emptyMatch : generexes.Reverse().Select(p => p._backwardMatcher).Aggregate(thenSimple)) { }
+                generexes.Length == 0 ? emptyMatch : generexes.Select(p => p._forwardMatcher).Aggregate(thenNoResult),
+                generexes.Length == 0 ? emptyMatch : generexes.Reverse().Select(p => p._backwardMatcher).Aggregate(thenNoResult)) { }
 
         private Generex(matcher forward, matcher backward) : base(forward, backward) { }
 
-        /// <summary>
-        /// Generates a matcher that matches a sequence of specific elements either fully or not at all.
-        /// </summary>
-        private static matcher elementsMatcher(T[] elements, IEqualityComparer<T> comparer, bool backward)
-        {
-            if (elements.Length == 0)
-                return emptyMatch;
-
-            if (elements.Length == 1)
-            {
-                var element = elements[0];
-                if (backward)
-                    return (input, startIndex) => startIndex > 0 && comparer.Equals(input[startIndex - 1], element) ? Generex.NegativeOneElementMatch : Generex.NoMatch;
-                else
-                    return (input, startIndex) => startIndex < input.Length && comparer.Equals(input[startIndex], element) ? Generex.OneElementMatch : Generex.NoMatch;
-            }
-
-            if (backward)
-                return (input, startIndex) => startIndex >= elements.Length && input.SubarrayEquals(startIndex - elements.Length, elements, comparer) ? new int[] { -elements.Length } : Generex.NoMatch;
-            else
-                return (input, startIndex) => startIndex <= input.Length - elements.Length && input.SubarrayEquals(startIndex, elements, comparer) ? new int[] { elements.Length } : Generex.NoMatch;
-        }
-
-        /// <summary>
-        /// Returns a regular expression that matches a single element, no matter what it is (cf. "." in traditional regular expression syntax).
-        /// </summary>
-        public static Generex<T> Any
-        {
-            get
-            {
-                if (_anyCache == null)
-                    _anyCache = new Generex<T>(
-                        (input, startIndex) => startIndex >= input.Length ? Generex.NoMatch : Generex.OneElementMatch,
-                        (input, startIndex) => startIndex <= 0 ? Generex.NoMatch : Generex.NegativeOneElementMatch
-                    );
-                return _anyCache;
-            }
-        }
-        private static Generex<T> _anyCache;
-
-        /// <summary>
-        /// Returns a regular expression that always matches and returns a zero-width match.
-        /// </summary>
-        public static Generex<T> Empty
-        {
-            get
-            {
-                if (_emptyCache == null)
-                {
-                    matcher zeroWidthMatch = (input, startIndex) => Generex.ZeroWidthMatch;
-                    _emptyCache = new Generex<T>(zeroWidthMatch, zeroWidthMatch);
-                }
-                return _emptyCache;
-            }
-        }
-        private static Generex<T> _emptyCache;
-
-        /// <summary>
-        /// Returns a regular expression that matches the beginning of the input collection (cf. "^" in traditional regular expression syntax). Successful matches are always zero length.
-        /// </summary>
-        public static Generex<T> Start
-        {
-            get
-            {
-                if (_startCache == null)
-                {
-                    matcher matcher = (input, startIndex) => startIndex != 0 ? Generex.NoMatch : Generex.ZeroWidthMatch;
-                    _startCache = new Generex<T>(matcher, matcher);
-                }
-                return _startCache;
-            }
-        }
-        private static Generex<T> _startCache;
-
-        /// <summary>
-        /// Returns a regular expression that matches the end of the input collection (cf. "$" in traditional regular expression syntax). Successful matches are always zero length.
-        /// </summary>
-        public static Generex<T> End
-        {
-            get
-            {
-                if (_endCache == null)
-                {
-                    matcher matcher = (input, startIndex) => startIndex != input.Length ? Generex.NoMatch : Generex.ZeroWidthMatch;
-                    _endCache = new Generex<T>(matcher, matcher);
-                }
-                return _endCache;
-            }
-        }
-        private static Generex<T> _endCache;
-
-        /// <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 Generex<T, TResult> Then<TResult>(Generex<T, TResult> other)
-        {
-            return new Generex<T, TResult>(
-                (input, startIndex) => _forwardMatcher(input, startIndex).SelectMany(m => other._forwardMatcher(input, startIndex + m).Select(m2 => m2.Add(m))),
-                (input, startIndex) => other._backwardMatcher(input, startIndex).SelectMany(m2 => _backwardMatcher(input, startIndex + m2.Length).Select(m => m2.Add(m)))
-            );
-        }
-
-        /// <summary>
-        /// Returns a regular expression that matches either this regular expression or the specified sequence of elements (cf. "|" or "[...]" in traditional regular expression syntax).
-        /// </summary>
-        /// <example>
-        /// <para>The following code:</para>
-        /// <code>var regex = new Generex&lt;char&gt;('a', 'b').Or('c', 'd');</code>
-        /// <para>is equivalent to the regular expression <c>ab|cd</c>, NOT <c>ab|c|d</c>.</para>
-        /// </example>
-        /// <seealso cref="Or(IEqualityComparer{Generex},Generex[])"/>
-        public Generex<T> Or(params T[] elements)
-        {
-            return Or(new Generex<T>(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).
-        /// </summary>
-        /// <seealso cref="Or(T[])"/>
-        public Generex<T> Or(IEqualityComparer<T> comparer, params T[] elements)
-        {
-            return Or(new Generex<T>(comparer, elements));
-        }
-
-        /// <summary>
-        /// Returns a regular expression that matches either this regular expression or the specified sequence of elements (cf. "|" or "[...]" in traditional regular expression syntax).
-        /// </summary>
-        public Generex<T> Or(IEnumerable<T> elements) { return Or(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).
-        /// </summary>
-        public Generex<T> Or(IEqualityComparer<T> comparer, IEnumerable<T> elements) { return Or(comparer, elements.ToArray()); }
-
-        /// <summary>
-        /// Returns a regular expression that matches either this regular expression or a single element that satisfies the specified predicate (cf. "|" in traditional regular expression syntax).
-        /// </summary>
-        public Generex<T> Or(Predicate<T> predicate)
-        {
-            return Or(new Generex<T>(predicate));
-        }
-
-        /// <summary>
-        /// Returns a regular expression that matches either this regular expression or the specified sequence of regular expressions (cf. "|" in traditional regular expression syntax).
-        /// </summary>
-        /// <example>
-        /// <para>The following code:</para>
-        /// <code>var regex = regex1.Or(regex2, regex3);</code>
-        /// <para>generates a regular expression equivalent to <c>1|23</c>, NOT <c>1|2|3</c>.</para>
-        /// </example>
-        public Generex<T> Or(params Generex<T>[] other)
-        {
-            if (other.Length == 0)
-                return this;
-            if (other.Length == 1)
-                return Or(other[0]);
-            return Or(new Generex<T>(other));
-        }
-
-        /// <summary>
-        /// Returns a regular expression that matches either this regular expression or the specified other regular expression (cf. "|" in traditional regular expression syntax).
-        /// </summary>
-        public new Generex<T> Or(Generex<T> other)
-        {
-            return base.Or(other);
-        }
-
-        /// <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> 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> 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> RepeatGreedy() { return new Generex<T>(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> Repeat() { return new Generex<T>(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> 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> 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> 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> 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> Times(int times)
-        {
-            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> RepeatWithSeparator(Generex<T> separator) { return Then(separator.Then(this).Repeat()); }
-        /// <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> RepeatWithSeparatorGreedy(Generex<T> separator) { return Then(separator.Then(this).RepeatGreedy()); }
-
-
-        /// <summary>
-        /// Generates a matcher that matches the specified matcher 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 matcher repeatInfinite(matcher inner, bool greedy)
-        {
-            matcher newMatcher = null;
-            if (greedy)
-                newMatcher = (input, startIndex) => inner(input, startIndex).SelectMany(m => newMatcher(input, startIndex + m)
-                    .Select(m2 => m + m2))
-                    .Concat(0);
-            else
-                newMatcher = (input, startIndex) => 0
-                    .Concat(inner(input, startIndex).SelectMany(m => newMatcher(input, startIndex + m)
-                    .Select(m2 => m + m2)));
-            return newMatcher;
-        }
-
-        /// <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> repeatInfinite(bool greedy)
-        {
-            return new Generex<T>(repeatInfinite(_forwardMatcher, greedy), repeatInfinite(_backwardMatcher, greedy));
-        }
-
-        /// <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> repeatMin(int min, bool greedy)
-        {
-            if (min < 0) throw new ArgumentException("'min' cannot be negative.", "min");
-            return repeatBetween(min, min, true).Then(repeatInfinite(greedy));
-        }
-
-        /// <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> repeatBetween(int min, int max, bool greedy)
-        {
-            if (min < 0) throw new ArgumentException("'min' cannot be negative.", "min");
-            if (max < min) throw new ArgumentException("'max' cannot be smaller than 'min'.", "max");
-            var rm = new repeatMatcher
-            {
-                Greedy = greedy,
-                MinTimes = min,
-                MaxTimes = max,
-                InnerForwardMatcher = _forwardMatcher,
-                InnerBackwardMatcher = _backwardMatcher
-            };
-            return new Generex<T>(rm.ForwardMatcher, rm.BackwardMatcher);
-        }
-
-        private sealed class repeatMatcher
-        {
-            public int MinTimes;
-            public int MaxTimes;
-            public bool Greedy;
-            public matcher InnerForwardMatcher;
-            public matcher InnerBackwardMatcher;
-            public IEnumerable<int> ForwardMatcher(T[] input, int startIndex) { return matcher(input, startIndex, 0, InnerForwardMatcher); }
-            public IEnumerable<int> BackwardMatcher(T[] input, int startIndex) { return matcher(input, startIndex, 0, InnerBackwardMatcher); }
-            private IEnumerable<int> matcher(T[] input, int startIndex, int iteration, matcher inner)
-            {
-                if (!Greedy && iteration >= MinTimes)
-                    yield return 0;
-                if (iteration < MaxTimes)
-                {
-                    foreach (var m in inner(input, startIndex))
-                        foreach (var m2 in matcher(input, startIndex + m, iteration + 1, inner))
-                            yield return m + m2;
-                }
-                if (Greedy && iteration >= MinTimes)
-                    yield return 0;
-            }
-        }
-
-        /// <summary>Turns the current regular expression into a zero-width negative look-ahead assertion.</summary>
-        public Generex<T> LookAheadNegative() { return lookNegative(behind: false, defaultMatch: Generex.ZeroWidthMatch); }
-        /// <summary>Turns the current regular expression into a zero-width negative look-ahead assertion.</summary>
-        public Generex<T> LookBehindNegative() { return lookNegative(behind: true, defaultMatch: Generex.ZeroWidthMatch); }
-
-        /// <summary>Returns a successful zero-width match.</summary>
-        private static IEnumerable<int> emptyMatch(T[] input, int startIndex) { return Generex.ZeroWidthMatch; }
-
         /// <summary>Implicitly converts an element into a regular expression that matches just that element.</summary>
         public static implicit operator Generex<T>(T element) { return new Generex<T>(element); }
         /// <summary>Implicitly converts a predicate into a regular expression that matches a single element satisfying the predicate.</summary>
         public static implicit operator Generex<T>(Predicate<T> predicate) { return new Generex<T>(predicate); }
-
-        /// <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 new Generex<T, TResult>(
-                (input, startIndex) => _forwardMatcher(input, startIndex).Select(m => new GenerexMatchInfo<TResult>(selector(new GenerexMatch<T>(input, startIndex, m)), m)),
-                (input, startIndex) => _backwardMatcher(input, startIndex).Select(m => new GenerexMatchInfo<TResult>(selector(new GenerexMatch<T>(input, startIndex + m, -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 Generex<T> Recursive(Func<Generex<T>, Generex<T>> 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 = new Generex<T>(
-                (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;
-        }
     }
 }
         internal override GenerexMatch<T, TResult> createMatch(T[] input, int index, GenerexMatchInfo<TResult> match) { return new GenerexMatch<T, TResult>(match.Result, input, index, match.Length); }
         internal override GenerexMatch<T, TResult> createBackwardsMatch(T[] input, int index, GenerexMatchInfo<TResult> match) { return new GenerexMatch<T, TResult>(match.Result, input, index + match.Length, -match.Length); }
 
+        /// <summary>
+        /// Instantiates an empty regular expression which always matches and returns the specified result object.
+        /// </summary>
         public Generex(TResult result) : this(new[] { new GenerexMatchInfo<TResult>(result, 0) }) { }
+
         private Generex(GenerexMatchInfo<TResult>[] emptyMatch) : this((input, startIndex) => emptyMatch) { }
         private Generex(matcher bothMatcher) : base(bothMatcher, bothMatcher) { }
 
 
         /// <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.
+        /// 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)
         {
     /// <summary>Abstract base class for <see cref="Generex{T}"/> and <see cref="Generex{T,TResult}"/>.</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="TGenerex">The derived type. (Pass the type itself recursive.)</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>
         /// </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>
-        /// <remarks>The behaviour is analogous to <see cref="Regex.Matches(string,string)"/>.
+        /// <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<TGenerexMatch> Matches(T[] input, int startAt = 0)
         {
         /// <summary>
         /// 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(params Generex<T>[] other)
+        public TGenerex Then<TGenerex2, TGenerexMatch2>(params GenerexNoResultBase<T, TGenerex2, TGenerexMatch2>[] other)
+            where TGenerex2 : GenerexNoResultBase<T, TGenerex2, TGenerexMatch2>
+            where TGenerexMatch2 : GenerexMatch<T>
         {
             return create(
                 other.Select(o => o._forwardMatcher).Aggregate(_forwardMatcher, then),
-                then(other.Reverse().Select(o => o._backwardMatcher).Aggregate(thenSimple), _backwardMatcher)
+                then(other.Reverse().Select(o => o._backwardMatcher).Aggregate(GenerexNoResultBase<T, TGenerex2, TGenerexMatch2>.thenNoResult), _backwardMatcher)
             );
         }
 
 
         /// <summary>
         /// This class implements the “or” (or alternation) operation without invoking both matchers at the start.
-        /// (This is important in cases involving recursive regular expressions, see <see cref="Recursive"/>.)
+        /// (This is important in cases involving recursive regular expressions, see <see cref="Generex{T}.Recursive"/> and <see cref="Generex{T,TResult}.Recursive"/>.)
         /// </summary>
         private class safeOrMatcher
         {
         /// Generates a matcher that matches the <paramref name="first"/> regular expression followed by the <paramref name="second"/> regular expression
         /// while retaining the result object of the first one.
         /// </summary>
-        internal matcher then(matcher first, Generex<T>.matcher second)
+        internal matcher then<TGenerex2, TGenerexMatch2>(matcher first, GenerexNoResultBase<T, TGenerex2, TGenerexMatch2>.matcher second)
+            where TGenerex2 : GenerexNoResultBase<T, TGenerex2, TGenerexMatch2>
+            where TGenerexMatch2 : GenerexMatch<T>
         {
             return (input, startIndex) => first(input, startIndex).SelectMany(m => second(input, startIndex + getLength(m)).Select(m2 => add(m, m2)));
         }
         /// Generates a matcher that matches the <paramref name="first"/> regular expression followed by the <paramref name="second"/> regular expression
         /// while retaining the result object of the second one.
         /// </summary>
-        internal matcher then(Generex<T>.matcher first, matcher second)
+        internal matcher then<TGenerex2, TGenerexMatch2>(GenerexNoResultBase<T, TGenerex2, TGenerexMatch2>.matcher first, matcher second)
+            where TGenerex2 : GenerexNoResultBase<T, TGenerex2, TGenerexMatch2>
+            where TGenerexMatch2 : GenerexMatch<T>
         {
             return (input, startIndex) => first(input, startIndex).SelectMany(m => second(input, startIndex + m).Select(m2 => add(m2, m)));
         }
 
         /// <summary>
-        /// Generates a matcher that matches the <paramref name="first"/> regular expression followed by the <paramref name="second"/> regular expression.
-        /// </summary>
-        internal static Generex<T>.matcher thenSimple(Generex<T>.matcher first, Generex<T>.matcher second)
-        {
-            return (input, startIndex) => first(input, startIndex).SelectMany(m => second(input, startIndex + m).Select(m2 => m + m2));
-        }
-
-        /// <summary>
         /// Executes the specified code every time the regular expression engine encounters this expression. (This always matches successfully and all matches are zero-length.)
         /// </summary>
         public TGenerex Do(Action code)
             return create(newMatcher, newMatcher);
         }
 
-        protected TGenerex lookNegative(bool behind, IEnumerable<TMatch> defaultMatch)
+        internal TGenerex lookNegative(bool behind, IEnumerable<TMatch> defaultMatch)
         {
             // In a look-*behind* assertion, both matchers use the _backwardMatcher. Similarly, look-*ahead* assertions always use _forwardMatcher.
             matcher innerMatcher = behind ? _backwardMatcher : _forwardMatcher;

GenerexNoResultBase.cs

+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+using RT.Util;
+using RT.Util.ExtensionMethods;
+using System.Reflection;
+
+namespace RT.Generexes
+{
+    public abstract class GenerexNoResultBase<T, TGenerex, TGenerexMatch> : GenerexBase<T, int, TGenerex, TGenerexMatch>
+        where TGenerex : GenerexNoResultBase<T, TGenerex, TGenerexMatch>
+        where TGenerexMatch : GenerexMatch<T>
+    {
+        internal sealed override int getLength(int match) { return match; }
+        internal sealed override int add(int match, int extra) { return match + extra; }
+        internal sealed override int setZero(int match) { return 0; }
+        internal sealed override TGenerexMatch createMatch(T[] input, int index, int match) { return createNoResultMatch(input, index, match); }
+        internal sealed override TGenerexMatch createBackwardsMatch(T[] input, int index, int match) { return createNoResultMatch(input, index + match, -match); }
+        internal abstract TGenerexMatch createNoResultMatch(T[] input, int index, int matchLength);
+
+        /// <summary>
+        /// Instantiates an empty regular expression (always matches).
+        /// </summary>
+        internal GenerexNoResultBase() : base(emptyMatch, emptyMatch) { }
+
+        internal GenerexNoResultBase(IEqualityComparer<T> comparer, T[] elements)
+            : base(
+                elementsMatcher(elements, comparer, backward: false),
+                elementsMatcher(elements, comparer, backward: true)) { }
+
+        internal GenerexNoResultBase(Predicate<T> predicate)
+            : base(
+                predicateMatcher(predicate, backward: false),
+                predicateMatcher(predicate, backward: true)) { }
+
+        internal GenerexNoResultBase(GenerexNoResultBase<T, TGenerex, TGenerexMatch>[] generexSequence)
+            : base(
+                sequenceMatcher(generexSequence, backward: false),
+                sequenceMatcher(generexSequence, backward: true)) { }
+
+        internal GenerexNoResultBase(matcher forward, matcher backward) : base(forward, backward) { }
+
+        /// <summary>
+        /// Generates a matcher that matches a sequence of specific elements either fully or not at all.
+        /// </summary>
+        private static matcher elementsMatcher(T[] elements, IEqualityComparer<T> comparer, bool backward)
+        {
+            if (comparer == null)
+                throw new ArgumentNullException("comparer");
+
+            if (elements.Length == 0)
+                return emptyMatch;
+
+            if (elements.Length == 1)
+            {
+                var element = elements[0];
+                if (backward)
+                    return (input, startIndex) => startIndex > 0 && comparer.Equals(input[startIndex - 1], element) ? Generex.NegativeOneElementMatch : Generex.NoMatch;
+                else
+                    return (input, startIndex) => startIndex < input.Length && comparer.Equals(input[startIndex], element) ? Generex.OneElementMatch : Generex.NoMatch;
+            }
+
+            if (backward)
+                return (input, startIndex) => startIndex >= elements.Length && input.SubarrayEquals(startIndex - elements.Length, elements, comparer) ? new int[] { -elements.Length } : Generex.NoMatch;
+            else
+                return (input, startIndex) => startIndex <= input.Length - elements.Length && input.SubarrayEquals(startIndex, elements, comparer) ? new int[] { elements.Length } : Generex.NoMatch;
+        }
+
+        internal static matcher predicateMatcher(Predicate<T> predicate, bool backward)
+        {
+            if (backward)
+                return (input, startIndex) => startIndex <= 0 || !predicate(input[startIndex - 1]) ? Generex.NoMatch : Generex.NegativeOneElementMatch;
+            else
+                return (input, startIndex) => startIndex >= input.Length || !predicate(input[startIndex]) ? Generex.NoMatch : Generex.OneElementMatch;
+        }
+
+        /// <summary>
+        /// Generates a matcher that matches the <paramref name="first"/> regular expression followed by the <paramref name="second"/> regular expression.
+        /// </summary>
+        internal static matcher thenNoResult(matcher first, matcher second)
+        {
+            return (input, startIndex) => first(input, startIndex).SelectMany(m => second(input, startIndex + m).Select(m2 => m + m2));
+        }
+
+        internal static matcher sequenceMatcher(GenerexNoResultBase<T, TGenerex, TGenerexMatch>[] generexSequence, bool backward)
+        {
+            return generexSequence.Length == 0 ? emptyMatch : backward
+                ? generexSequence.Reverse().Select(p => p._backwardMatcher).Aggregate(thenNoResult)
+                : generexSequence.Select(p => p._forwardMatcher).Aggregate(thenNoResult);
+        }
+
+        private static ConstructorInfo _constructor;
+        internal static TGenerex newGenerex(matcher forward, matcher backward)
+        {
+            if (_constructor == null)
+            {
+                var matcherType = typeof(matcher);
+                _constructor = typeof(TGenerex).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { matcherType, matcherType }, null);
+                if (_constructor == null)
+                    throw new InvalidOperationException("The derived type does not declare a constructor with two parameters, each of type matcher.");
+            }
+            return (TGenerex) _constructor.Invoke(new object[] { forward, backward });
+        }
+
+        /// <summary>
+        /// Returns a regular expression that matches a single element, no matter what it is (cf. "." in traditional regular expression syntax).
+        /// </summary>
+        public static TGenerex Any
+        {
+            get
+            {
+                if (_anyCache == null)
+                    _anyCache = newGenerex(
+                        (input, startIndex) => startIndex >= input.Length ? Generex.NoMatch : Generex.OneElementMatch,
+                        (input, startIndex) => startIndex <= 0 ? Generex.NoMatch : Generex.NegativeOneElementMatch
+                    );
+                return _anyCache;
+            }
+        }
+        private static TGenerex _anyCache;
+
+        /// <summary>
+        /// Returns a regular expression that always matches and returns a zero-width match.
+        /// </summary>
+        public static TGenerex Empty
+        {
+            get
+            {
+                if (_emptyCache == null)
+                {
+                    matcher zeroWidthMatch = (input, startIndex) => Generex.ZeroWidthMatch;
+                    _emptyCache = newGenerex(zeroWidthMatch, zeroWidthMatch);
+                }
+                return _emptyCache;
+            }
+        }
+        private static TGenerex _emptyCache;
+
+        /// <summary>
+        /// Returns a regular expression that matches the beginning of the input collection (cf. "^" in traditional regular expression syntax). Successful matches are always zero length.
+        /// </summary>
+        public static TGenerex Start
+        {
+            get
+            {
+                if (_startCache == null)
+                {
+                    matcher matcher = (input, startIndex) => startIndex != 0 ? Generex.NoMatch : Generex.ZeroWidthMatch;
+                    _startCache = newGenerex(matcher, matcher);
+                }
+                return _startCache;
+            }
+        }
+        private static TGenerex _startCache;
+
+        /// <summary>
+        /// Returns a regular expression that matches the end of the input collection (cf. "$" in traditional regular expression syntax). Successful matches are always zero length.
+        /// </summary>
+        public static TGenerex End
+        {
+            get
+            {
+                if (_endCache == null)
+                {
+                    matcher matcher = (input, startIndex) => startIndex != input.Length ? Generex.NoMatch : Generex.ZeroWidthMatch;
+                    _endCache = newGenerex(matcher, matcher);
+                }
+                return _endCache;
+            }
+        }
+        private static TGenerex _endCache;
+
+        /// <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 Generex<T, TResult> Then<TResult>(Generex<T, TResult> other)
+        {
+#warning TODO: This method should return a TGenerexWithResult, not a Generex<T, TResult>
+            return new Generex<T, TResult>(
+                (input, startIndex) => _forwardMatcher(input, startIndex).SelectMany(m => other._forwardMatcher(input, startIndex + m).Select(m2 => m2.Add(m))),
+                (input, startIndex) => other._backwardMatcher(input, startIndex).SelectMany(m2 => _backwardMatcher(input, startIndex + m2.Length).Select(m => m2.Add(m)))
+            );
+        }
+
+        /// <summary>
+        /// Returns a regular expression that matches either this regular expression or the specified sequence of elements (cf. "|" or "[...]" in traditional regular expression syntax).
+        /// </summary>
+        /// <example>
+        /// <para>The following code:</para>
+        /// <code>var regex = new Generex&lt;char&gt;('a', 'b').Or('c', 'd');</code>
+        /// <para>is equivalent to the regular expression <c>ab|cd</c>, NOT <c>ab|c|d</c>.</para>
+        /// </example>
+        /// <seealso cref="Or(IEqualityComparer{T},T[])"/>
+        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).
+        /// </summary>
+        /// <seealso cref="Or(T[])"/>
+        public TGenerex Or(IEqualityComparer<T> comparer, params T[] elements)
+        {
+            return Or(newGenerex(
+                elementsMatcher(elements, comparer, backward: false),
+                elementsMatcher(elements, comparer, backward: true)
+            ));
+        }
+
+        /// <summary>
+        /// Returns a regular expression that matches either this regular expression or the specified sequence of elements (cf. "|" or "[...]" in traditional regular expression syntax).
+        /// </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).
+        /// </summary>
+        public TGenerex Or(IEqualityComparer<T> comparer, IEnumerable<T> elements) { return Or(comparer, elements.ToArray()); }
+
+        /// <summary>
+        /// Returns a regular expression that matches either this regular expression or a single element that satisfies the specified predicate (cf. "|" in traditional regular expression syntax).
+        /// </summary>
+        public TGenerex Or(Predicate<T> predicate)
+        {
+            return Or(newGenerex(predicateMatcher(predicate, backward: false), predicateMatcher(predicate, backward: true)));
+        }
+
+        /// <summary>
+        /// Returns a regular expression that matches either this regular expression or the specified sequence of regular expressions (cf. "|" in traditional regular expression syntax).
+        /// </summary>
+        /// <example>
+        /// <para>The following code:</para>
+        /// <code>var regex = regex1.Or(regex2, regex3);</code>
+        /// <para>generates a regular expression equivalent to <c>1|23</c>, NOT <c>1|2|3</c>.</para>
+        /// </example>
+        public TGenerex Or(params TGenerex[] other)
+        {
+            if (other.Length == 0)
+                return (TGenerex) this;
+            if (other.Length == 1)
+                return Or(other[0]);
+            return Or(newGenerex(sequenceMatcher(other, backward: false), sequenceMatcher(other, backward: true)));
+        }
+
+        /// <summary>
+        /// Returns a regular expression that matches either this regular expression or the specified other regular expression (cf. "|" in traditional regular expression syntax).
+        /// </summary>
+        /// <remarks>
+        /// <para>This overload is here even though an equivalent method is inherited from <see cref="GenerexBase{T,TMatch,TGenerex,TGenerexMatch}"/> because without it, the following code:</para>
+        /// <code>myGenerex.Or(myOtherGenerex);</code>
+        /// <para>would call the <see cref="Or(Generex{T}[])"/> overload instead because method overload resolution prefers direct members over inherited ones.</para>
+        /// </remarks>
+        public new TGenerex Or(TGenerex other) { return base.Or(other); }
+
+        /// <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 TGenerex 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 TGenerex 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 TGenerex RepeatGreedy() { return newGenerex(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 TGenerex Repeat() { return newGenerex(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 TGenerex 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 TGenerex 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 TGenerex 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 TGenerex 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 TGenerex Times(int times)
+        {
+            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 TGenerex RepeatWithSeparator(TGenerex separator) { return Then(separator.Then(this).Repeat()); }
+        /// <summary>
+        /// Returns a regular expression that matches this regular expression one or more times, interspersed with a separator. More times are prioritised.
+        /// </summary>
+        public TGenerex RepeatWithSeparatorGreedy(TGenerex separator) { return Then(separator.Then(this).RepeatGreedy()); }
+
+
+        /// <summary>
+        /// Generates a matcher that matches the specified matcher 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 matcher repeatInfinite(matcher inner, bool greedy)
+        {
+            matcher newMatcher = null;
+            if (greedy)
+                newMatcher = (input, startIndex) => inner(input, startIndex).SelectMany(m => newMatcher(input, startIndex + m)
+                    .Select(m2 => m + m2))
+                    .Concat(0);
+            else
+                newMatcher = (input, startIndex) => 0
+                    .Concat(inner(input, startIndex).SelectMany(m => newMatcher(input, startIndex + m)
+                    .Select(m2 => m + m2)));
+            return newMatcher;
+        }
+
+        /// <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 TGenerex repeatInfinite(bool greedy)
+        {
+            return newGenerex(repeatInfinite(_forwardMatcher, greedy), repeatInfinite(_backwardMatcher, greedy));
+        }
+
+        /// <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 TGenerex repeatMin(int min, bool greedy)
+        {
+            if (min < 0) throw new ArgumentException("'min' cannot be negative.", "min");
+            return repeatBetween(min, min, true).Then(repeatInfinite(greedy));
+        }
+
+        /// <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 TGenerex repeatBetween(int min, int max, bool greedy)
+        {
+            if (min < 0) throw new ArgumentException("'min' cannot be negative.", "min");
+            if (max < min) throw new ArgumentException("'max' cannot be smaller than 'min'.", "max");
+            var rm = new repeatMatcher
+            {
+                Greedy = greedy,
+                MinTimes = min,
+                MaxTimes = max,
+                InnerForwardMatcher = _forwardMatcher,
+                InnerBackwardMatcher = _backwardMatcher
+            };
+            return newGenerex(rm.ForwardMatcher, rm.BackwardMatcher);
+        }
+
+        private sealed class repeatMatcher
+        {
+            public int MinTimes;
+            public int MaxTimes;
+            public bool Greedy;
+            public matcher InnerForwardMatcher;
+            public matcher InnerBackwardMatcher;
+            public IEnumerable<int> ForwardMatcher(T[] input, int startIndex) { return matcher(input, startIndex, 0, InnerForwardMatcher); }
+            public IEnumerable<int> BackwardMatcher(T[] input, int startIndex) { return matcher(input, startIndex, 0, InnerBackwardMatcher); }
+            private IEnumerable<int> matcher(T[] input, int startIndex, int iteration, matcher inner)
+            {
+                if (!Greedy && iteration >= MinTimes)
+                    yield return 0;
+                if (iteration < MaxTimes)
+                {
+                    foreach (var m in inner(input, startIndex))
+                        foreach (var m2 in matcher(input, startIndex + m, iteration + 1, inner))
+                            yield return m + m2;
+                }
+                if (Greedy && iteration >= MinTimes)
+                    yield return 0;
+            }
+        }
+
+        /// <summary>Turns the current regular expression into a zero-width negative look-ahead assertion.</summary>
+        public TGenerex LookAheadNegative() { return lookNegative(behind: false, defaultMatch: Generex.ZeroWidthMatch); }
+        /// <summary>Turns the current regular expression into a zero-width negative look-ahead assertion.</summary>
+        public TGenerex LookBehindNegative() { return lookNegative(behind: true, defaultMatch: Generex.ZeroWidthMatch); }
+
+        /// <summary>Returns a successful zero-width match.</summary>
+        internal static IEnumerable<int> emptyMatch(T[] input, int startIndex) { return Generex.ZeroWidthMatch; }
+
+        /// <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)
+        {
+            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))
+            );
+        }
+
+        /// <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 = newGenerex(
+                (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;
+        }
+    }
+}