Browse Source

Expanding StringTokenizator with ReadOnlySpan. (#17645)

* Tokenizer returns a ReadOnly Span instead of a string.

* Returning the old property for no API break changes

---------

Co-authored-by: Meloman19 <23280622+Meloman19@users.noreply.github.com>
pull/16577/merge
Meloman19 1 year ago
committed by GitHub
parent
commit
18fcfcc163
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 8
      src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs
  2. 6
      src/Avalonia.Base/Media/TextDecorationCollection.cs
  3. 26
      src/Avalonia.Base/RelativeRect.cs
  4. 30
      src/Avalonia.Base/Utilities/SpanHelpers.cs
  5. 31
      src/Avalonia.Base/Utilities/StringTokenizer.cs
  6. 10
      tests/Avalonia.Base.UnitTests/Utilities/StringTokenizerTests.cs

8
src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs

@ -132,7 +132,7 @@ namespace Avalonia.Media.Fonts
glyphTypeface = typeface;
return true;
}
}
}
return false;
@ -297,7 +297,7 @@ namespace Avalonia.Media.Fonts
var tokenizer = new StringTokenizer(familyName, ' ');
tokenizer.ReadString();
tokenizer.ReadSpan();
while (tokenizer.TryReadString(out var weightString))
{
@ -325,7 +325,7 @@ namespace Avalonia.Media.Fonts
var tokenizer = new StringTokenizer(familyName, ' ');
tokenizer.ReadString();
tokenizer.ReadSpan();
while (tokenizer.TryReadString(out var styleString))
{
@ -354,7 +354,7 @@ namespace Avalonia.Media.Fonts
var tokenizer = new StringTokenizer(familyName, ' ');
tokenizer.ReadString();
tokenizer.ReadSpan();
while (tokenizer.TryReadString(out var stretchString))
{

6
src/Avalonia.Base/Media/TextDecorationCollection.cs

@ -31,7 +31,7 @@ namespace Avalonia.Media
using (var tokenizer = new StringTokenizer(s, ',', "Invalid text decoration."))
{
while (tokenizer.TryReadString(out var name))
while (tokenizer.TryReadSpan(out var name))
{
var location = GetTextDecorationLocation(name);
@ -59,9 +59,9 @@ namespace Avalonia.Media
/// </summary>
/// <param name="s">The string.</param>
/// <returns>The <see cref="TextDecorationLocation"/>.</returns>
private static TextDecorationLocation GetTextDecorationLocation(string s)
private static TextDecorationLocation GetTextDecorationLocation(ReadOnlySpan<char> s)
{
if (Enum.TryParse<TextDecorationLocation>(s,true, out var location))
if (SpanHelpers.TryParseEnum<TextDecorationLocation>(s,true, out var location))
{
return location;
}

26
src/Avalonia.Base/RelativeRect.cs

@ -152,7 +152,7 @@ namespace Avalonia
Rect.Width * size.Width,
Rect.Height * size.Height);
}
/// <summary>
/// Converts a <see cref="RelativeRect"/> into pixels.
/// </summary>
@ -178,18 +178,18 @@ namespace Avalonia
{
using (var tokenizer = new StringTokenizer(s, exceptionMessage: "Invalid RelativeRect."))
{
var x = tokenizer.ReadString();
var y = tokenizer.ReadString();
var width = tokenizer.ReadString();
var height = tokenizer.ReadString();
var x = tokenizer.ReadSpan();
var y = tokenizer.ReadSpan();
var width = tokenizer.ReadSpan();
var height = tokenizer.ReadSpan();
var unit = RelativeUnit.Absolute;
var scale = 1.0;
var xRelative = x.EndsWith("%", StringComparison.Ordinal);
var yRelative = y.EndsWith("%", StringComparison.Ordinal);
var widthRelative = width.EndsWith("%", StringComparison.Ordinal);
var heightRelative = height.EndsWith("%", StringComparison.Ordinal);
var xRelative = x.EndsWith(PercentChar, StringComparison.Ordinal);
var yRelative = y.EndsWith(PercentChar, StringComparison.Ordinal);
var widthRelative = width.EndsWith(PercentChar, StringComparison.Ordinal);
var heightRelative = height.EndsWith(PercentChar, StringComparison.Ordinal);
if (xRelative && yRelative && widthRelative && heightRelative)
{
@ -207,10 +207,10 @@ namespace Avalonia
}
return new RelativeRect(
double.Parse(x, CultureInfo.InvariantCulture) * scale,
double.Parse(y, CultureInfo.InvariantCulture) * scale,
double.Parse(width, CultureInfo.InvariantCulture) * scale,
double.Parse(height, CultureInfo.InvariantCulture) * scale,
SpanHelpers.ParseDouble(x, CultureInfo.InvariantCulture) * scale,
SpanHelpers.ParseDouble(y, CultureInfo.InvariantCulture) * scale,
SpanHelpers.ParseDouble(width, CultureInfo.InvariantCulture) * scale,
SpanHelpers.ParseDouble(height, CultureInfo.InvariantCulture) * scale,
unit);
}
}

30
src/Avalonia.Base/Utilities/SpanHelpers.cs

@ -29,6 +29,16 @@ namespace Avalonia.Utilities
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseInt(this ReadOnlySpan<char> span, NumberStyles style, IFormatProvider provider, out int value)
{
#if NETSTANDARD2_0
return int.TryParse(span.ToString(), style, provider, out value);
#else
return int.TryParse(span, style, provider, out value);
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseDouble(this ReadOnlySpan<char> span, NumberStyles style, IFormatProvider provider, out double value)
{
@ -39,6 +49,16 @@ namespace Avalonia.Utilities
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double ParseDouble(this ReadOnlySpan<char> span, IFormatProvider provider)
{
#if NETSTANDARD2_0
return double.Parse(span.ToString(), provider);
#else
return double.Parse(span, provider: provider);
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseByte(this ReadOnlySpan<char> span, NumberStyles style, IFormatProvider provider, out byte value)
{
@ -46,6 +66,16 @@ namespace Avalonia.Utilities
return byte.TryParse(span.ToString(), style, provider, out value);
#else
return byte.TryParse(span, style, provider, out value);
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseEnum<TEnum>(this ReadOnlySpan<char> span, bool ignoreCase, out TEnum value) where TEnum : struct
{
#if NETSTANDARD2_0
return Enum.TryParse<TEnum>(span.ToString(), ignoreCase, out value);
#else
return Enum.TryParse<TEnum>(span, ignoreCase, out value);
#endif
}
}

31
src/Avalonia.Base/Utilities/StringTokenizer.cs

@ -46,6 +46,8 @@ namespace Avalonia.Utilities
public string? CurrentToken => _tokenIndex < 0 ? null : _s.Substring(_tokenIndex, _tokenLength);
public ReadOnlySpan<char> CurrentTokenSpan => _tokenIndex < 0 ? ReadOnlySpan<char>.Empty : _s.AsSpan().Slice(_tokenIndex, _tokenLength);
public void Dispose()
{
if (_index != _length)
@ -56,8 +58,8 @@ namespace Avalonia.Utilities
public bool TryReadInt32(out Int32 result, char? separator = null)
{
if (TryReadString(out var stringResult, separator) &&
int.TryParse(stringResult, NumberStyles.Integer, _formatProvider, out result))
if (TryReadSpan(out var stringResult, separator) &&
SpanHelpers.TryParseInt(stringResult, NumberStyles.Integer, _formatProvider, out result))
{
return true;
}
@ -80,8 +82,8 @@ namespace Avalonia.Utilities
public bool TryReadDouble(out double result, char? separator = null)
{
if (TryReadString(out var stringResult, separator) &&
double.TryParse(stringResult, NumberStyles.Float, _formatProvider, out result))
if (TryReadSpan(out var stringResult, separator) &&
SpanHelpers.TryParseDouble(stringResult, NumberStyles.Float, _formatProvider, out result))
{
return true;
}
@ -102,10 +104,10 @@ namespace Avalonia.Utilities
return result;
}
public bool TryReadString([MaybeNullWhen(false)] out string result, char? separator = null)
public bool TryReadString([NotNull] out string result, char? separator = null)
{
var success = TryReadToken(separator ?? _separator);
result = CurrentToken;
result = CurrentTokenSpan.ToString();
return success;
}
@ -119,6 +121,23 @@ namespace Avalonia.Utilities
return result;
}
public bool TryReadSpan(out ReadOnlySpan<char> result, char? separator = null)
{
var success = TryReadToken(separator ?? _separator);
result = CurrentTokenSpan;
return success;
}
public ReadOnlySpan<char> ReadSpan(char? separator = null)
{
if (!TryReadSpan(out var result, separator))
{
throw GetFormatException();
}
return result;
}
private bool TryReadToken(char separator)
{
_tokenIndex = -1;

10
tests/Avalonia.Base.UnitTests/Utilities/StringTokenizerTests.cs

@ -65,5 +65,15 @@ namespace Avalonia.Base.UnitTests.Utilities
Assert.False(target.TryReadDouble(out var value));
}
[Fact]
public void ReadSpan_And_ReadString_Reads_Same()
{
var target1 = new StringTokenizer("abc,def");
var target2 = new StringTokenizer("abc,def");
Assert.Equal(target1.ReadString(), target2.ReadSpan().ToString());
Assert.True(target1.ReadSpan().SequenceEqual(target2.ReadString()));
}
}
}

Loading…
Cancel
Save