-
Notifications
You must be signed in to change notification settings - Fork 103
/
Copy pathTextSpan.cs
321 lines (286 loc) · 10.9 KB
/
TextSpan.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
// Copyright 2016 Datalust, Superpower Contributors, Sprache Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
namespace Superpower.Model
{
/// <summary>
/// A span of text within a larger string.
/// </summary>
public readonly struct TextSpan : IEquatable<TextSpan>
{
/// <summary>
/// The source string containing the span.
/// </summary>
public string? Source { get; }
/// <summary>
/// The position of the start of the span within the string.
/// </summary>
public Position Position { get; }
/// <summary>
/// The length of the span.
/// </summary>
public int Length { get; }
/// <summary>
/// Construct a span encompassing an entire string.
/// </summary>
/// <param name="source">The source string.</param>
public TextSpan(string source)
: this(source, Position.Zero, source.Length)
{
}
/// <summary>
/// Construct a string span for a substring of <paramref name="source"/>.
/// </summary>
/// <param name="source">The source string.</param>
/// <param name="position">The start of the span.</param>
/// <param name="length">The length of the span.</param>
public TextSpan(string source, Position position, int length)
{
#if CHECKED
if (source == null) throw new ArgumentNullException(nameof(source));
if (length < 0)
throw new ArgumentOutOfRangeException(nameof(length), "The length must be non-negative.");
if (source.Length < position.Absolute + length)
throw new ArgumentOutOfRangeException(nameof(length), "The token extends beyond the end of the input.");
#endif
Source = source;
Position = position;
Length = length;
}
/// <summary>
/// A span with no value.
/// </summary>
public static TextSpan None { get; } = default;
/// <summary>
/// A span corresponding to the empty string.
/// </summary>
public static TextSpan Empty { get; } = new TextSpan(string.Empty, Position.Zero, 0);
/// <summary>
/// True if the span has no content.
/// </summary>
public bool IsAtEnd
{
get
{
EnsureHasValue();
return Length == 0;
}
}
void EnsureHasValue()
{
if (Source == null)
throw new InvalidOperationException("String span has no value.");
}
/// <summary>
/// Consume a character from the start of the span.
/// </summary>
/// <returns>A result with the character and remainder.</returns>
public Result<char> ConsumeChar()
{
EnsureHasValue();
if (IsAtEnd)
return Result.Empty<char>(this);
var ch = Source![Position.Absolute];
return Result.Value(ch, this, new TextSpan(Source, Position.Advance(ch), Length - 1));
}
/// <inheritdoc/>
public override bool Equals(object? obj)
{
if (!(obj is TextSpan other))
return false;
return Equals(other);
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
return ((Source?.GetHashCode() ?? 0) * 397) ^ Position.Absolute;
}
}
/// <summary>
/// Compare a string span with another using source identity
/// semantics - same source, same position, same length.
/// </summary>
/// <param name="other">The other span.</param>
/// <returns>True if the spans are the same.</returns>
public bool Equals(TextSpan other)
{
return ReferenceEquals(Source, other.Source) &&
Position.Absolute == other.Position.Absolute &&
Length == other.Length;
}
/// <summary>
/// Compare two spans using source identity semantics.
/// </summary>
/// <param name="lhs">One span.</param>
/// <param name="rhs">Another span.</param>
/// <returns>True if the spans are the same.</returns>
public static bool operator ==(TextSpan lhs, TextSpan rhs)
{
return lhs.Equals(rhs);
}
/// <summary>
/// Compare two spans using source identity semantics.
/// </summary>
/// <param name="lhs">One span.</param>
/// <param name="rhs">Another span.</param>
/// <returns>True if the spans are the different.</returns>
public static bool operator !=(TextSpan lhs, TextSpan rhs)
{
return !(lhs == rhs);
}
/// <summary>
/// Return a new span from the start of this span to the beginning of another.
/// </summary>
/// <param name="next">The next span.</param>
/// <returns>A sub-span.</returns>
public TextSpan Until(TextSpan next)
{
#if CHECKED
next.EnsureHasValue();
if (next.Source != Source) throw new ArgumentException("The spans are on different source strings.", nameof(next));
#endif
var charCount = next.Position.Absolute - Position.Absolute;
return First(charCount);
}
/// <summary>
/// Return a span comprising the first <paramref name="length"/> characters of this span.
/// </summary>
/// <param name="length">The number of characters to return.</param>
/// <returns>The sub-span.</returns>
public TextSpan First(int length)
{
#if CHECKED
if (length > Length)
throw new ArgumentOutOfRangeException(nameof(length), "Length exceeds the source span's length.");
#endif
return new TextSpan(Source!, Position, length);
}
/// <summary>
/// Skip a specified number of characters. Note, this is an O(count) operation.
/// </summary>
/// <param name="count"></param>
public TextSpan Skip(int count)
{
EnsureHasValue();
#if CHECKED
if (count > Length)
throw new ArgumentOutOfRangeException(nameof(count), "Count exceeds the source span's length.");
#endif
var p = Position;
for (var i = 0; i < count; ++i)
{
p = p.Advance(Source![p.Absolute]);
}
return new TextSpan(Source!, p, Length - count);
}
/// <inheritdoc/>
public override string ToString()
{
if (Source == null)
return "(empty source span)";
return ToStringValue();
}
/// <summary>
/// Compute the string value of this span.
/// </summary>
/// <returns>A string with the value of this span.</returns>
public string ToStringValue()
{
EnsureHasValue();
return Source!.Substring(Position.Absolute, Length);
}
/// <summary>
/// Compare the contents of this span with <paramref name="otherValue"/>.
/// </summary>
/// <param name="otherValue">The string value to compare.</param>
/// <returns>True if the values are the same.</returns>
public bool EqualsValue(string otherValue)
{
if (otherValue == null) throw new ArgumentNullException(nameof(otherValue));
EnsureHasValue();
if (Length != otherValue.Length)
return false;
for (var i = 0; i < Length; ++i)
{
if (Source![Position.Absolute + i] != otherValue[i])
return false;
}
return true;
}
/// <summary>
/// Compare the contents of this span with <paramref name="otherValue"/>, ignoring invariant character case.
/// </summary>
/// <param name="otherValue">The string value to compare.</param>
/// <returns>True if the values are the same ignoring case.</returns>
public bool EqualsValueIgnoreCase(string otherValue)
{
if (otherValue == null) throw new ArgumentNullException(nameof(otherValue));
EnsureHasValue();
if (Length != otherValue.Length)
return false;
for (var i = 0; i < Length; ++i)
{
if (char.ToUpperInvariant(Source![Position.Absolute + i]) != char.ToUpperInvariant(otherValue[i]))
return false;
}
return true;
}
/// <summary>
/// Gets the character at the specified index in the text span.
/// </summary>
/// <param name="index">
/// The zero-based index of the character to get.
/// </param>
/// <returns>
/// The character at the specified index in the text span.
/// </returns>
public char this[int index]
{
get
{
this.EnsureHasValue();
#if CHECKED
if ((uint)index >= (uint)Length)
throw new ArgumentOutOfRangeException(nameof(index), index, "Index exceeds the source span's length.");
#endif
return Source![Position.Absolute + index];
}
}
/// <summary>
/// Forms a slice out of the current text span starting at the specified index.
/// </summary>
/// <param name="index">
/// The index at which to begin the slice.
/// </param>
/// <returns>
/// An text span that consists of all elements of the current array segment from <paramref name="index"/> to the end of the text span.
/// </returns>
public TextSpan Slice(int index)
{
return Skip(index);
}
/// <summary>
/// Forms a slice of the specified length out of the current text span starting at the specified index.
/// </summary>
/// <param name="index">The index at which to begin the slice.</param>
/// <param name="count">The desired length of the slice.</param>
/// <returns>An text span of <paramref name="count"/> elements starting at <paramref name="index"/>.</returns>
public TextSpan Slice(int index, int count)
{
return Skip(index).First(count);
}
}
}