1+ using Microsoft . Extensions . Primitives ;
2+ using System ;
3+ using System . Runtime . CompilerServices ;
4+ using System . Diagnostics . CodeAnalysis ;
5+
6+ namespace Open . Text ;
7+ /// <summary>
8+ /// Represents a search operation within a string segment.
9+ /// </summary>
10+ public readonly ref struct StringSegmentSearch
11+ {
12+ /// <summary>
13+ /// The source segment where the search is to be performed.
14+ /// </summary>
15+ public StringSegment Source { get ; }
16+
17+ /// <summary>
18+ /// The character sequences to be searched within the source.
19+ /// </summary>
20+ public ReadOnlySpan < char > Sequence { get ; }
21+
22+ /// <summary>
23+ /// The string comparison option used for the search.
24+ /// </summary>
25+ public StringComparison Comparison { get ; }
26+
27+ /// <summary>
28+ /// Indicates whether the search is to be performed from right to left.
29+ /// </summary>
30+ public bool RightToLeft { get ; }
31+
32+ /// <summary>
33+ /// Initializes a new instance of the <see cref="StringSegmentSearch"/> struct.
34+ /// </summary>
35+ /// <param name="source">
36+ /// <inheritdoc cref="Source" path="/summary"/>
37+ /// </param>
38+ /// <param name="search">
39+ /// <inheritdoc cref="Sequence" path="/summary"/>
40+ /// </param>
41+ /// <param name="comparisonType">
42+ /// <inheritdoc cref="Comparison" path="/summary"/>
43+ /// </param>
44+ /// <param name="rightToLeft">
45+ /// <inheritdoc cref="RightToLeft" path="/summary"/>
46+ /// </param>
47+ internal StringSegmentSearch (
48+ StringSegment source ,
49+ ReadOnlySpan < char > search ,
50+ StringComparison comparisonType ,
51+ bool rightToLeft )
52+ {
53+ Source = source ;
54+ Sequence = search ;
55+ Comparison = comparisonType ;
56+ RightToLeft = rightToLeft ;
57+ }
58+ }
59+
60+ /// <summary>
61+ /// Represents a captured substring within a string segment based on a search operation.
62+ /// </summary>
63+ public readonly ref struct StringSegmentCapture
64+ {
65+ /// <summary>
66+ /// Gets the search operation that resulted in this capture.
67+ /// </summary>
68+ public StringSegmentSearch Search { get ; }
69+
70+ /// <summary>
71+ /// Gets the captured substring as a string subsegment.
72+ /// </summary>
73+ public StringSubsegment Value { get ; }
74+
75+ /// <summary>
76+ /// Returns <see langword="true"/> if the capture was successful; otherwise, <see langword="false"/>.
77+ /// </summary>
78+ public bool Success => Value . HasValue ;
79+
80+ /// <summary>
81+ /// The index of the first character in the source <see cref="StringSegment"/> that is included in this subsegment.
82+ /// </summary>
83+ public int Index => Value . HasValue ? Value . Offset : - 1 ;
84+
85+ /// <summary>
86+ /// Implicitly converts a <see cref="StringSegmentSearch"/> to a <see cref="StringSegmentCapture"/>.
87+ /// </summary>
88+ [ SuppressMessage ( "Usage" , "CA2225:Operator overloads have named alternates" ) ]
89+ public static implicit operator StringSegmentCapture ( StringSegmentSearch capture )
90+ => capture . First ( ) ;
91+
92+ /// <summary>
93+ /// Implicitly converts a <see cref="StringSegmentCapture"/> to a <see cref="StringSubsegment"/>.
94+ /// </summary>
95+ [ SuppressMessage ( "Usage" , "CA2225:Operator overloads have named alternates" ) ]
96+ public static implicit operator StringSubsegment ( StringSegmentCapture capture )
97+ => capture . Value ;
98+
99+ /// <summary>
100+ /// Implicitly converts a <see cref="StringSegmentCapture"/> to a <see cref="StringSegment"/>.
101+ /// </summary>
102+ [ SuppressMessage ( "Usage" , "CA2225:Operator overloads have named alternates" ) ]
103+ public static implicit operator StringSegment ( StringSegmentCapture capture )
104+ => capture . Value ;
105+
106+ /// <inheritdoc />
107+ public override string ToString ( ) => Value . ToString ( ) ;
108+
109+ /// <summary>
110+ /// Initializes a new instance of the <see cref="StringSegmentCapture"/> struct.
111+ /// </summary>
112+ /// <param name="search">
113+ /// <inheritdoc cref="Search" path="/summary"/>
114+ /// </param>
115+ /// <param name="value">
116+ /// <inheritdoc cref="Value" path="/summary"/>
117+ /// </param>
118+ internal StringSegmentCapture ( StringSegmentSearch search , StringSubsegment value )
119+ {
120+ Search = search ;
121+ Value = value ;
122+ }
123+ }
124+
125+ public static partial class TextExtensions
126+ {
127+ /// <summary>
128+ /// Starts a search for the specified character sequence within the source segment.
129+ /// </summary>
130+ /// <param name="source">
131+ /// <inheritdoc cref="StringSegmentSearch.Source" path="/summary"/>
132+ /// </param>
133+ /// <param name="search">
134+ /// <inheritdoc cref="StringSegmentSearch.Sequence" path="/summary"/>
135+ /// </param>
136+ /// <param name="comparisonType">
137+ /// <inheritdoc cref="StringSegmentSearch.Comparison" path="/summary"/>
138+ /// </param>
139+ /// <param name="rightToLeft">
140+ /// <inheritdoc cref="StringSegmentSearch.RightToLeft" path="/summary"/>
141+ /// </param>
142+ public static StringSegmentSearch Find (
143+ this StringSegment source ,
144+ ReadOnlySpan < char > search ,
145+ StringComparison comparisonType = StringComparison . Ordinal ,
146+ bool rightToLeft = false )
147+ => new ( source , search , comparisonType , rightToLeft ) ;
148+
149+ /// <inheritdoc cref="Find(StringSegment, ReadOnlySpan{char}, StringComparison, bool)"/>
150+ public static StringSegmentSearch Find (
151+ this string source ,
152+ ReadOnlySpan < char > search ,
153+ StringComparison comparisonType = StringComparison . Ordinal ,
154+ bool rightToLeft = false )
155+ => new ( source , search , comparisonType , rightToLeft ) ;
156+
157+ /// <summary>
158+ /// Finds the next occurrence of the specified character sequence within the source segment.
159+ /// </summary>
160+ public static StringSegmentCapture First (
161+ this StringSegmentSearch search )
162+ {
163+ if ( search . Source . Length == 0 || search . Sequence . Length == 0 )
164+ return default ;
165+
166+ var i = search . RightToLeft
167+ ? search . Source . LastIndexOf ( search . Sequence , search . Comparison )
168+ : search . Source . IndexOf ( search . Sequence , search . Comparison ) ;
169+
170+ return new ( search , i == - 1
171+ ? default
172+ : new ( search . Source , i , search . Sequence . Length ) ) ;
173+ }
174+
175+ /// <summary>
176+ /// Finds the next occurrence of the specified character sequence within the source segment.
177+ /// </summary>
178+ public static StringSegmentCapture Next (
179+ this StringSegmentCapture capture )
180+ {
181+ var value = capture . Value ;
182+ var len = value . Length ;
183+ if ( len == 0 )
184+ return default ;
185+
186+ var search = capture . Search ;
187+ var source = search . Source ;
188+ var i = capture . Search . RightToLeft
189+ ? source . Subsegment ( 0 , value . Offset ) . LastIndexOf ( search . Sequence , search . Comparison )
190+ : source . IndexOf ( search . Sequence , value . Offset + value . Length , search . Comparison ) ;
191+
192+ return new ( search , i == - 1
193+ ? default
194+ : new ( source , i , len ) ) ;
195+ }
196+
197+ /// <summary>
198+ /// Finds the next occurrence after the first occurrence of the specified character sequence within the source segment.
199+ /// </summary>
200+ public static StringSegmentCapture Next (
201+ this StringSegmentSearch search )
202+ => search . First ( ) . Next ( ) ;
203+
204+ /// <summary>
205+ /// Finds the last occurrence of the specified character sequence within the source segment.
206+ /// </summary>
207+ public static StringSegmentCapture Last (
208+ this StringSegmentSearch search )
209+ {
210+ if ( search . Source . Length == 0 || search . Sequence . Length == 0 )
211+ return default ;
212+
213+ var i = search . RightToLeft
214+ ? search . Source . IndexOf ( search . Sequence , search . Comparison )
215+ : search . Source . LastIndexOf ( search . Sequence , search . Comparison ) ;
216+
217+ return new ( search , i == - 1
218+ ? default
219+ : new ( search . Source , i , search . Sequence . Length ) ) ;
220+ }
221+
222+ /// <summary>
223+ /// Returns <see langword="true"/> if the capture has a value; otherwise <see langword="false"/>.
224+ /// </summary>
225+ public static bool Exists ( this StringSegmentCapture capture )
226+ => capture . Value . HasValue ;
227+
228+ /// <summary>
229+ /// Returns <see langword="true"/> if the search has a value; otherwise <see langword="false"/>.
230+ /// </summary>
231+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
232+ public static bool Exists ( this StringSegmentSearch search )
233+ => search . First ( ) . Exists ( ) ;
234+
235+ /// <summary>
236+ /// Resolves the value of the capture, or returns the specified default value.
237+ /// </summary>
238+ public static StringSubsegment Or (
239+ this StringSegmentCapture capture ,
240+ StringSubsegment defaultValue )
241+ {
242+ var value = capture . Value ;
243+ return value . HasValue ? value : defaultValue ;
244+ }
245+
246+ /// <summary>
247+ /// Resolves the value of the search, or returns the specified default value.
248+ /// </summary>
249+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
250+ public static StringSubsegment Or (
251+ this StringSegmentSearch capture ,
252+ StringSubsegment defaultValue )
253+ => capture . First ( ) . Or ( defaultValue ) ;
254+ }
0 commit comments