Skip to content

Commit 429f896

Browse files
committed
Expand APIs for ReadOnlyMemory<char> to match/replace StringSegment
1 parent 44755cc commit 429f896

11 files changed

Lines changed: 944 additions & 51 deletions

File tree

src/ServiceStack.Memory/NetCoreProvider.cs

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,33 +89,72 @@ public override Task WriteAsync(Stream stream, ReadOnlySpan<char> value, Cancell
8989
return TypeConstants.EmptyTask;
9090
}
9191

92-
public override async Task<object> DeserializeAsync(Type type, Stream stream, TypeDeserializer deserializer)
92+
public override async Task WriteAsync(Stream stream, ReadOnlyMemory<byte> value, CancellationToken token = default)
9393
{
94-
//TODO optimize Stream -> UTF-8 ReadOnlySpan<char>
94+
await stream.WriteAsync(value, token);
95+
}
9596

96-
if (stream is MemoryStream ms)
97+
public override async Task<object> DeserializeAsync(Stream stream, Type type, TypeDeserializer deserializer)
98+
{
99+
var fromPool = false;
100+
101+
if (!(stream is MemoryStream ms))
97102
{
98-
var body = await ms.ReadToEndAsync(Encoding.UTF8);
99-
var ret = deserializer(type, body.AsSpan());
100-
return ret;
103+
fromPool = true;
104+
105+
if (stream.CanSeek)
106+
stream.Position = 0;
107+
108+
ms = await stream.CopyToNewMemoryStreamAsync();
101109
}
102110

103-
if (stream.CanSeek)
111+
return Deserialize(type, deserializer, ms, fromPool);
112+
}
113+
114+
private static object Deserialize(Type type, TypeDeserializer deserializer, MemoryStream memoryStream, bool fromPool)
115+
{
116+
var bytes = memoryStream.GetBufferAsSpan();
117+
var chars = CharPool.GetBuffer(Encoding.UTF8.GetCharCount(bytes));
118+
try
104119
{
105-
stream.Position = 0;
120+
Encoding.UTF8.GetChars(bytes, chars);
121+
var ret = deserializer(type, chars);
122+
return ret;
106123
}
107-
108-
using (var reader = new StreamReader(stream, Encoding.UTF8, true, StreamExtensions.DefaultBufferSize, leaveOpen:true))
124+
finally
109125
{
110-
var body = await reader.ReadToEndAsync();
111-
var ret = deserializer(type, body.AsSpan());
112-
return ret;
126+
CharPool.ReleaseBufferToPool(ref chars);
127+
128+
if (fromPool)
129+
memoryStream.Dispose();
113130
}
114131
}
115132

116133
public override StringBuilder Append(StringBuilder sb, ReadOnlySpan<char> value)
117134
{
118135
return sb.Append(value);
119136
}
137+
138+
public override int GetUtf8CharCount(ReadOnlySpan<byte> bytes) => Encoding.UTF8.GetCharCount(bytes);
139+
140+
public override int GetUtf8ByteCount(ReadOnlySpan<char> chars) => Encoding.UTF8.GetByteCount(chars);
141+
142+
public override ReadOnlyMemory<byte> ToUtf8(ReadOnlySpan<char> source)
143+
{
144+
Memory<byte> bytes = new byte[Encoding.UTF8.GetByteCount(source)];
145+
var bytesWritten = Encoding.UTF8.GetBytes(source, bytes.Span);
146+
return bytes.Slice(0, bytesWritten);
147+
}
148+
149+
public override ReadOnlyMemory<char> FromUtf8(ReadOnlySpan<byte> source)
150+
{
151+
Memory<char> chars = new char[Encoding.UTF8.GetCharCount(source)];
152+
var charsWritten = Encoding.UTF8.GetChars(source, chars.Span);
153+
return chars.Slice(0, charsWritten);
154+
}
155+
156+
public override int ToUtf8(ReadOnlySpan<char> source, Span<byte> destination) => Encoding.UTF8.GetBytes(source, destination);
157+
158+
public override int FromUtf8(ReadOnlySpan<byte> source, Span<char> destination) => Encoding.UTF8.GetChars(source, destination);
120159
}
121160
}
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
4+
using System.Text;
5+
6+
namespace ServiceStack.Text
7+
{
8+
public static class CharMemoryExtensions
9+
{
10+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
11+
public static bool IsNullOrEmpty(this ReadOnlyMemory<char> value) => value.IsEmpty;
12+
13+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
14+
public static bool IsWhiteSpace(this ReadOnlyMemory<char> value)
15+
{
16+
var span = value.Span;
17+
for (int i = 0; i < span.Length; i++)
18+
{
19+
if (!char.IsWhiteSpace(span[i]))
20+
return false;
21+
}
22+
return true;
23+
}
24+
25+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
26+
public static bool IsNullOrWhiteSpace(this ReadOnlyMemory<char> value) => value.IsEmpty || value.IsWhiteSpace();
27+
28+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
29+
public static ReadOnlyMemory<char> Advance(this ReadOnlyMemory<char> text, int to) => text.Slice(to, text.Length - to);
30+
31+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
32+
public static ReadOnlyMemory<char> AdvancePastWhitespace(this ReadOnlyMemory<char> literal)
33+
{
34+
var span = literal.Span;
35+
var i = 0;
36+
while (i < span.Length && char.IsWhiteSpace(span[i]))
37+
i++;
38+
39+
return i == 0 ? literal : literal.Slice(i < literal.Length ? i : literal.Length);
40+
}
41+
42+
public static ReadOnlyMemory<char> AdvancePastChar(this ReadOnlyMemory<char> literal, char delim)
43+
{
44+
var i = 0;
45+
var c = (char) 0;
46+
var span = literal.Span;
47+
while (i < span.Length && (c = span[i]) != delim)
48+
i++;
49+
50+
if (c == delim)
51+
return literal.Slice(i + 1);
52+
53+
return i == 0 ? literal : literal.Slice(i < literal.Length ? i : literal.Length);
54+
}
55+
56+
public static bool TryReadLine(this ReadOnlyMemory<char> text, out ReadOnlyMemory<char> line, ref int startIndex)
57+
{
58+
if (startIndex >= text.Length)
59+
{
60+
line = TypeConstants.NullMemory;
61+
return false;
62+
}
63+
64+
text = text.Slice(startIndex);
65+
66+
var nextLinePos = text.Span.IndexOfAny('\r', '\n');
67+
if (nextLinePos == -1)
68+
{
69+
var nextLine = text.Slice(0, text.Length);
70+
startIndex += text.Length;
71+
line = nextLine;
72+
return true;
73+
}
74+
else
75+
{
76+
var nextLine = text.Slice(0, nextLinePos);
77+
78+
startIndex += nextLinePos + 1;
79+
80+
var span = text.Span;
81+
if (span[nextLinePos] == '\r' && span.Length > nextLinePos + 1 && span[nextLinePos + 1] == '\n')
82+
startIndex += 1;
83+
84+
line = nextLine;
85+
return true;
86+
}
87+
}
88+
89+
public static bool TryReadPart(this ReadOnlyMemory<char> text, ReadOnlyMemory<char> needle, out ReadOnlyMemory<char> part, ref int startIndex)
90+
{
91+
if (startIndex >= text.Length)
92+
{
93+
part = TypeConstants.NullMemory;
94+
return false;
95+
}
96+
97+
text = text.Slice(startIndex);
98+
var nextPartPos = text.Span.IndexOf(needle.Span);
99+
if (nextPartPos == -1)
100+
{
101+
var nextPart = text.Slice(0, text.Length);
102+
startIndex += text.Length;
103+
part = nextPart;
104+
return true;
105+
}
106+
else
107+
{
108+
var nextPart = text.Slice(0, nextPartPos);
109+
startIndex += nextPartPos + needle.Length;
110+
part = nextPart;
111+
return true;
112+
}
113+
}
114+
115+
public static void SplitOnFirst(this ReadOnlyMemory<char> strVal, char needle, out ReadOnlyMemory<char> first, out ReadOnlyMemory<char> last)
116+
{
117+
first = default;
118+
last = default;
119+
if (strVal.IsEmpty) return;
120+
121+
var pos = strVal.Span.IndexOf(needle);
122+
if (pos == -1)
123+
{
124+
first = strVal;
125+
}
126+
else
127+
{
128+
first = strVal.Slice(0, pos);
129+
last = strVal.Slice(pos + 1);
130+
}
131+
}
132+
133+
public static void SplitOnFirst(this ReadOnlyMemory<char> strVal, ReadOnlyMemory<char> needle, out ReadOnlyMemory<char> first, out ReadOnlyMemory<char> last)
134+
{
135+
first = default;
136+
last = default;
137+
if (strVal.IsEmpty) return;
138+
139+
var pos = strVal.Span.IndexOf(needle.Span);
140+
if (pos == -1)
141+
{
142+
first = strVal;
143+
}
144+
else
145+
{
146+
first = strVal.Slice(0, pos);
147+
last = strVal.Slice(pos + needle.Length);
148+
}
149+
}
150+
151+
public static void SplitOnLast(this ReadOnlyMemory<char> strVal, char needle, out ReadOnlyMemory<char> first, out ReadOnlyMemory<char> last)
152+
{
153+
first = default;
154+
last = default;
155+
if (strVal.IsEmpty) return;
156+
157+
var pos = strVal.Span.LastIndexOf(needle);
158+
if (pos == -1)
159+
{
160+
first = strVal;
161+
}
162+
else
163+
{
164+
first = strVal.Slice(0, pos);
165+
last = strVal.Slice(pos + 1);
166+
}
167+
}
168+
169+
public static void SplitOnLast(this ReadOnlyMemory<char> strVal, ReadOnlyMemory<char> needle, out ReadOnlyMemory<char> first, out ReadOnlyMemory<char> last)
170+
{
171+
first = default;
172+
last = default;
173+
if (strVal.IsEmpty) return;
174+
175+
var pos = strVal.Span.LastIndexOf(needle.Span);
176+
if (pos == -1)
177+
{
178+
first = strVal;
179+
}
180+
else
181+
{
182+
first = strVal.Slice(0, pos);
183+
last = strVal.Slice(pos + needle.Length);
184+
}
185+
}
186+
187+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
188+
public static int IndexOf(this ReadOnlyMemory<char> value, string needle) => value.Span.IndexOf(needle.AsSpan());
189+
190+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
191+
public static int IndexOf(this ReadOnlyMemory<char> value, string needle, int start)
192+
{
193+
var pos = value.Slice(start).Span.IndexOf(needle.AsSpan());
194+
return pos == -1 ? -1 : start + pos;
195+
}
196+
197+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
198+
public static int LastIndexOf(this ReadOnlyMemory<char> value, string needle) => value.Span.LastIndexOf(needle.AsSpan());
199+
200+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
201+
public static int LastIndexOf(this ReadOnlyMemory<char> value, string needle, int start)
202+
{
203+
var pos = value.Slice(start).Span.LastIndexOf(needle.AsSpan());
204+
return pos == -1 ? -1 : start + pos;
205+
}
206+
207+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
208+
public static bool StartsWith(this ReadOnlyMemory<char> value, string other) => value.Span.StartsWith(other.AsSpan(), StringComparison.OrdinalIgnoreCase);
209+
210+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
211+
public static bool EndsWith(this ReadOnlyMemory<char> value, string other) => value.Span.EndsWith(other.AsSpan(), StringComparison.OrdinalIgnoreCase);
212+
213+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
214+
public static bool EqualsOrdinal(this ReadOnlyMemory<char> value, string other) => value.Span.Equals(other.AsSpan(), StringComparison.Ordinal);
215+
216+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
217+
public static bool EqualsOrdinal(this ReadOnlyMemory<char> value, ReadOnlyMemory<char> other) => value.Span.Equals(other.Span, StringComparison.Ordinal);
218+
219+
public static ReadOnlyMemory<char> Trim(this ReadOnlyMemory<char> span)
220+
{
221+
return span.TrimStart().TrimEnd();
222+
}
223+
224+
public static ReadOnlyMemory<char> TrimStart(this ReadOnlyMemory<char> value)
225+
{
226+
var span = value.Span;
227+
int start = 0;
228+
for (; start < span.Length; start++)
229+
{
230+
if (!char.IsWhiteSpace(span[start]))
231+
break;
232+
}
233+
return value.Slice(start);
234+
}
235+
236+
public static ReadOnlyMemory<char> TrimEnd(this ReadOnlyMemory<char> value)
237+
{
238+
var span = value.Span;
239+
int end = span.Length - 1;
240+
for (; end >= 0; end--)
241+
{
242+
if (!char.IsWhiteSpace(span[end]))
243+
break;
244+
}
245+
return value.Slice(0, end + 1);
246+
}
247+
248+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
249+
public static ReadOnlyMemory<char> SafeSubsegment(this ReadOnlyMemory<char> value, int startIndex) => SafeSubsegment(value, startIndex, value.Length);
250+
251+
public static ReadOnlyMemory<char> SafeSubsegment(this ReadOnlyMemory<char> value, int startIndex, int length)
252+
{
253+
if (value.IsEmpty) return TypeConstants.NullMemory;
254+
if (startIndex < 0) startIndex = 0;
255+
if (value.Length >= startIndex + length)
256+
return value.Slice(startIndex, length);
257+
258+
return value.Length > startIndex ? value.Slice(startIndex) : TypeConstants.NullMemory;
259+
}
260+
261+
public static ReadOnlyMemory<byte> ToUtf8(this ReadOnlyMemory<char> chars) =>
262+
MemoryProvider.Instance.ToUtf8(chars.Span);
263+
264+
public static ReadOnlyMemory<char> FromUtf8(this ReadOnlyMemory<byte> bytes) =>
265+
MemoryProvider.Instance.FromUtf8(bytes.Span);
266+
}
267+
}

0 commit comments

Comments
 (0)