Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added dependency on System.Memory and enabled Span<T> support in net451 and netstandard2.0 #49

Merged
merged 3 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
ICU4N.Text.StringHelper: Refactored business logic and added tests
  • Loading branch information
NightOwl888 committed Nov 16, 2023
commit 6419a142e7b2dbf4fc4d482df3e62634b856f45c
192 changes: 119 additions & 73 deletions src/ICU4N/Support/Text/StringHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
#if FEATURE_SPAN
using System.Buffers;
using System.Linq;
#endif
#nullable enable

Expand All @@ -16,49 +17,34 @@ internal static class StringHelper

private const int CharStackBufferSize = 64;

#if !FEATURE_STRING_IMPLCIT_TO_READONLYSPAN
public static ReadOnlySpan<char> Concat(string str0, string str1)
=> string.Concat(str0, str1).AsSpan();

public static ReadOnlySpan<char> Concat(string str0, string str1, string str2)
=> string.Concat(str0, str1, str2).AsSpan();

public static ReadOnlySpan<char> Concat(string str0, string str1, string str2, string str3)
=> string.Concat(str0, str1, str2, str3).AsSpan();
#endif


public unsafe static string Concat(ReadOnlySpan<char> str0, ReadOnlySpan<char> str1)
{
#if FEATURE_STRING_CONCAT_READONLYSPAN
return string.Concat(str0, str1);
#else
int length = str0.Length + str1.Length;
char[]? arrayToReturnToPool = null;
Span<char> buffer;
if (length <= CharStackBufferSize)
{
#pragma warning disable CS9081 // The result of this expression may be exposed outside of the containing method
buffer = stackalloc char[length];
#pragma warning restore CS9081 // The result of this expression may be exposed outside of the containing method
}
else
if (length == 0)
{
arrayToReturnToPool = ArrayPool<char>.Shared.Rent(length);
buffer = arrayToReturnToPool;
return string.Empty;
}

str0.CopyTo(buffer.Slice(0, str0.Length));
str1.CopyTo(buffer.Slice(str0.Length));

string result;
fixed (char* pBuffer = buffer)
result = new string(pBuffer, startIndex: 0, length);
bool usePool = length > CharStackBufferSize;
char[]? arrayToReturnToPool = usePool ? ArrayPool<char>.Shared.Rent(length) : null;
try
{
Span<char> buffer = usePool ? arrayToReturnToPool : stackalloc char[length];

if (arrayToReturnToPool is not null)
ArrayPool<char>.Shared.Return(arrayToReturnToPool);
str0.CopyTo(buffer.Slice(0, str0.Length));
str1.CopyTo(buffer.Slice(str0.Length));

return result;
fixed (char* pBuffer = buffer)
return new string(pBuffer, startIndex: 0, length);
}
finally
{
if (arrayToReturnToPool is not null)
ArrayPool<char>.Shared.Return(arrayToReturnToPool);
}
#endif
}

Expand All @@ -68,70 +54,130 @@ public unsafe static string Concat(ReadOnlySpan<char> str0, ReadOnlySpan<char> s
return string.Concat(str0, str1, str2);
#else
int length = str0.Length + str1.Length + str2.Length;
char[]? arrayToReturnToPool = null;
Span<char> buffer;
if (length <= CharStackBufferSize)
if (length == 0)
{
#pragma warning disable CS9081 // The result of this expression may be exposed outside of the containing method
buffer = stackalloc char[length];
#pragma warning restore CS9081 // The result of this expression may be exposed outside of the containing method
}
else
{
arrayToReturnToPool = ArrayPool<char>.Shared.Rent(length);
buffer = arrayToReturnToPool;
return string.Empty;
}

str0.CopyTo(buffer.Slice(0, str0.Length));
str1.CopyTo(buffer.Slice(str0.Length, str1.Length));
str2.CopyTo(buffer.Slice(str0.Length + str1.Length));
bool usePool = length > CharStackBufferSize;
char[]? arrayToReturnToPool = usePool ? ArrayPool<char>.Shared.Rent(length) : null;
try
{
Span<char> buffer = usePool ? arrayToReturnToPool : stackalloc char[length];

string result;
fixed (char* pBuffer = buffer)
result = new string(pBuffer, startIndex: 0, length);
str0.CopyTo(buffer.Slice(0, str0.Length));
str1.CopyTo(buffer.Slice(str0.Length, str1.Length));
str2.CopyTo(buffer.Slice(str0.Length + str1.Length));

if (arrayToReturnToPool is not null)
ArrayPool<char>.Shared.Return(arrayToReturnToPool);
fixed (char* pBuffer = buffer)
return new string(pBuffer, startIndex: 0, length);

return result;
}
finally
{
if (arrayToReturnToPool is not null)
ArrayPool<char>.Shared.Return(arrayToReturnToPool);
}
#endif
}

public unsafe static string Concat(ReadOnlySpan<char> str0, ReadOnlySpan<char> str1, ReadOnlySpan<char> str2, ReadOnlySpan<char> str3)
{
#if FEATURE_STRING_CONCAT_READONLYSPAN
return string.Concat(str0, str1, str2);
return string.Concat(str0, str1, str2, str3);
#else
int length = str0.Length + str1.Length + str2.Length + str3.Length;
char[]? arrayToReturnToPool = null;
Span<char> buffer;
if (length <= CharStackBufferSize)
if (length == 0)
{
#pragma warning disable CS9081 // The result of this expression may be exposed outside of the containing method
buffer = stackalloc char[length];
#pragma warning restore CS9081 // The result of this expression may be exposed outside of the containing method
return string.Empty;
}
else

bool usePool = length > CharStackBufferSize;
char[]? arrayToReturnToPool = usePool ? ArrayPool<char>.Shared.Rent(length) : null;
try
{
arrayToReturnToPool = ArrayPool<char>.Shared.Rent(length);
buffer = arrayToReturnToPool;
}
Span<char> buffer = usePool ? arrayToReturnToPool : stackalloc char[length];

str0.CopyTo(buffer.Slice(0, str0.Length));
str1.CopyTo(buffer.Slice(str0.Length, str1.Length));
str2.CopyTo(buffer.Slice(str0.Length + str1.Length, str2.Length));
str3.CopyTo(buffer.Slice(str0.Length + str1.Length + str2.Length));
str0.CopyTo(buffer.Slice(0, str0.Length));
str1.CopyTo(buffer.Slice(str0.Length, str1.Length));
str2.CopyTo(buffer.Slice(str0.Length + str1.Length, str2.Length));
str3.CopyTo(buffer.Slice(str0.Length + str1.Length + str2.Length));

string result;
fixed (char* pBuffer = buffer)
result = new string(pBuffer, startIndex: 0, length);
fixed (char* pBuffer = buffer)
return new string(pBuffer, startIndex: 0, length);

if (arrayToReturnToPool is not null)
ArrayPool<char>.Shared.Return(arrayToReturnToPool);
}
finally
{
if (arrayToReturnToPool is not null)
ArrayPool<char>.Shared.Return(arrayToReturnToPool);
}

return result;
#endif
}

public unsafe static string Concat(ReadOnlySpan<char> str0, ReadOnlySpan<char> str1, ReadOnlySpan<char> str2, ReadOnlySpan<char> str3, ReadOnlySpan<char> str4)
{
int length = str0.Length + str1.Length + str2.Length + str3.Length + str4.Length;
if (length == 0)
{
return string.Empty;
}

bool usePool = length > CharStackBufferSize;
char[]? arrayToReturnToPool = usePool ? ArrayPool<char>.Shared.Rent(length) : null;
try
{
Span<char> buffer = usePool ? arrayToReturnToPool : stackalloc char[length];

str0.CopyTo(buffer.Slice(0, str0.Length));
str1.CopyTo(buffer.Slice(str0.Length, str1.Length));
str2.CopyTo(buffer.Slice(str0.Length + str1.Length, str2.Length));
str3.CopyTo(buffer.Slice(str0.Length + str1.Length + str2.Length, str3.Length));
str4.CopyTo(buffer.Slice(str0.Length + str1.Length + str2.Length + str3.Length));

fixed (char* pBuffer = buffer)
return new string(pBuffer, startIndex: 0, length);
}
finally
{
if (arrayToReturnToPool is not null)
ArrayPool<char>.Shared.Return(arrayToReturnToPool);
}


}

public unsafe static string Concat(ReadOnlySpan<char> str0, ReadOnlySpan<char> str1, ReadOnlySpan<char> str2, ReadOnlySpan<char> str3, ReadOnlySpan<char> str4, ReadOnlySpan<char> str5)
{
int length = str0.Length + str1.Length + str2.Length + str3.Length + str4.Length + str5.Length;
if (length == 0)
{
return string.Empty;
}

bool usePool = length > CharStackBufferSize;
char[]? arrayToReturnToPool = usePool ? ArrayPool<char>.Shared.Rent(length) : null;
try
{
Span<char> buffer = usePool ? arrayToReturnToPool : stackalloc char[length];

str0.CopyTo(buffer.Slice(0, str0.Length));
str1.CopyTo(buffer.Slice(str0.Length, str1.Length));
str2.CopyTo(buffer.Slice(str0.Length + str1.Length, str2.Length));
str3.CopyTo(buffer.Slice(str0.Length + str1.Length + str2.Length, str3.Length));
str4.CopyTo(buffer.Slice(str0.Length + str1.Length + str2.Length + str3.Length, str4.Length));
str5.CopyTo(buffer.Slice(str0.Length + str1.Length + str2.Length + str3.Length + str4.Length));

fixed (char* pBuffer = buffer)
return new string(pBuffer, startIndex: 0, length);
}
finally
{
if (arrayToReturnToPool is not null)
ArrayPool<char>.Shared.Return(arrayToReturnToPool);
}
}
}
#endif
}
4 changes: 2 additions & 2 deletions src/ICU4N/Text/PluralRules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1886,12 +1886,12 @@ private unsafe static ParseRuleStatus TryParseConstraint(ReadOnlySpan<char> desc
// at this point, either we are out of tokens, or t is ','
if (low > high)
{
token = StringHelper.Concat(low.ToString(CultureInfo.InvariantCulture), "~", high.ToString(CultureInfo.InvariantCulture));
token = string.Concat(low.ToString(CultureInfo.InvariantCulture), "~", high.ToString(CultureInfo.InvariantCulture)).AsSpan();
return ParseRuleStatus.ConstraintUnexpectedToken;
}
else if (mod != 0 && high >= mod)
{
token = StringHelper.Concat(low.ToString(CultureInfo.InvariantCulture), ">mod=", mod.ToString(CultureInfo.InvariantCulture));
token = string.Concat(low.ToString(CultureInfo.InvariantCulture), ">mod=", mod.ToString(CultureInfo.InvariantCulture)).AsSpan();
return ParseRuleStatus.ConstraintUnexpectedToken;
}
valueList.Add(low);
Expand Down
82 changes: 82 additions & 0 deletions tests/ICU4N.Tests/Support/Text/StringHelperTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ICU4N.Text
{

public class StringHelperTest
{
private const string a = "ayz";
private const string b = "byz";
private const string c = "cyz";
private const string d = "dyz";
private const string e = "eyz";

private const string s = "ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss";
private const string Empty = "";


#if FEATURE_SPAN

[Test]
[TestCase(a + b, a, b)]
[TestCase(a + c, a, c)]
[TestCase(d + e, d, e)]
[TestCase(b + s, b, s)]
[TestCase(Empty, Empty, Empty)]
public void TestConcat_ReadOnlySpan_x2(string expected, string str0, string str1)
{
Assert.AreEqual(expected, StringHelper.Concat(str0.AsSpan(), str1.AsSpan()));
}

[Test]
[TestCase(a + b + c, a, b, c)]
[TestCase(a + c + e, a, c, e)]
[TestCase(d + e + s, d, e, s)]
[TestCase(b + d + s, b, d, s)]
[TestCase(Empty, Empty, Empty, Empty)]
public void TestConcat_ReadOnlySpan_x3(string expected, string str0, string str1, string str2)
{
Assert.AreEqual(expected, StringHelper.Concat(str0.AsSpan(), str1.AsSpan(), str2.AsSpan()));
}

[Test]
[TestCase(a + b + c + d, a, b, c, d)]
[TestCase(a + c + e + b, a, c, e, b)]
[TestCase(d + e + s + b, d, e, s, b)]
[TestCase(b + d + s + c, b, d, s, c)]
[TestCase(Empty, Empty, Empty, Empty, Empty)]
public void TestConcat_ReadOnlySpan_x4(string expected, string str0, string str1, string str2, string str3)
{
Assert.AreEqual(expected, StringHelper.Concat(str0.AsSpan(), str1.AsSpan(), str2.AsSpan(), str3.AsSpan()));
}

[Test]
[TestCase(a + b + c + d + e, a, b, c, d, e)]
[TestCase(a + c + e + b + c, a, c, e, b, c)]
[TestCase(d + e + s + b + d, d, e, s, b, d)]
[TestCase(b + d + s + c + a, b, d, s, c, a)]
[TestCase(Empty, Empty, Empty, Empty, Empty, Empty)]
public void TestConcat_ReadOnlySpan_x5(string expected, string str0, string str1, string str2, string str3, string str4)
{
Assert.AreEqual(expected, StringHelper.Concat(str0.AsSpan(), str1.AsSpan(), str2.AsSpan(), str3.AsSpan(), str4.AsSpan()));
}

[Test]
[TestCase(a + b + c + d + e + a, a, b, c, d, e, a)]
[TestCase(a + c + e + b + c + b, a, c, e, b, c, b)]
[TestCase(d + e + s + b + d + c, d, e, s, b, d, c)]
[TestCase(b + d + s + c + a + d, b, d, s, c, a, d)]
[TestCase(Empty, Empty, Empty, Empty, Empty, Empty, Empty)]
public void TestConcat_ReadOnlySpan_x6(string expected, string str0, string str1, string str2, string str3, string str4, string str5)
{
Assert.AreEqual(expected, StringHelper.Concat(str0.AsSpan(), str1.AsSpan(), str2.AsSpan(), str3.AsSpan(), str4.AsSpan(), str5.AsSpan()));
}
#endif

}
}