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; glyphTypeface = typeface;
return true; return true;
} }
} }
return false; return false;
@ -297,7 +297,7 @@ namespace Avalonia.Media.Fonts
var tokenizer = new StringTokenizer(familyName, ' '); var tokenizer = new StringTokenizer(familyName, ' ');
tokenizer.ReadString(); tokenizer.ReadSpan();
while (tokenizer.TryReadString(out var weightString)) while (tokenizer.TryReadString(out var weightString))
{ {
@ -325,7 +325,7 @@ namespace Avalonia.Media.Fonts
var tokenizer = new StringTokenizer(familyName, ' '); var tokenizer = new StringTokenizer(familyName, ' ');
tokenizer.ReadString(); tokenizer.ReadSpan();
while (tokenizer.TryReadString(out var styleString)) while (tokenizer.TryReadString(out var styleString))
{ {
@ -354,7 +354,7 @@ namespace Avalonia.Media.Fonts
var tokenizer = new StringTokenizer(familyName, ' '); var tokenizer = new StringTokenizer(familyName, ' ');
tokenizer.ReadString(); tokenizer.ReadSpan();
while (tokenizer.TryReadString(out var stretchString)) 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.")) 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); var location = GetTextDecorationLocation(name);
@ -59,9 +59,9 @@ namespace Avalonia.Media
/// </summary> /// </summary>
/// <param name="s">The string.</param> /// <param name="s">The string.</param>
/// <returns>The <see cref="TextDecorationLocation"/>.</returns> /// <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; return location;
} }

26
src/Avalonia.Base/RelativeRect.cs

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

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

@ -29,6 +29,16 @@ namespace Avalonia.Utilities
#endif #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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseDouble(this ReadOnlySpan<char> span, NumberStyles style, IFormatProvider provider, out double value) public static bool TryParseDouble(this ReadOnlySpan<char> span, NumberStyles style, IFormatProvider provider, out double value)
{ {
@ -39,6 +49,16 @@ namespace Avalonia.Utilities
#endif #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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseByte(this ReadOnlySpan<char> span, NumberStyles style, IFormatProvider provider, out byte value) 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); return byte.TryParse(span.ToString(), style, provider, out value);
#else #else
return byte.TryParse(span, style, provider, out value); 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 #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 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() public void Dispose()
{ {
if (_index != _length) if (_index != _length)
@ -56,8 +58,8 @@ namespace Avalonia.Utilities
public bool TryReadInt32(out Int32 result, char? separator = null) public bool TryReadInt32(out Int32 result, char? separator = null)
{ {
if (TryReadString(out var stringResult, separator) && if (TryReadSpan(out var stringResult, separator) &&
int.TryParse(stringResult, NumberStyles.Integer, _formatProvider, out result)) SpanHelpers.TryParseInt(stringResult, NumberStyles.Integer, _formatProvider, out result))
{ {
return true; return true;
} }
@ -80,8 +82,8 @@ namespace Avalonia.Utilities
public bool TryReadDouble(out double result, char? separator = null) public bool TryReadDouble(out double result, char? separator = null)
{ {
if (TryReadString(out var stringResult, separator) && if (TryReadSpan(out var stringResult, separator) &&
double.TryParse(stringResult, NumberStyles.Float, _formatProvider, out result)) SpanHelpers.TryParseDouble(stringResult, NumberStyles.Float, _formatProvider, out result))
{ {
return true; return true;
} }
@ -102,10 +104,10 @@ namespace Avalonia.Utilities
return result; 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); var success = TryReadToken(separator ?? _separator);
result = CurrentToken; result = CurrentTokenSpan.ToString();
return success; return success;
} }
@ -119,6 +121,23 @@ namespace Avalonia.Utilities
return result; 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) private bool TryReadToken(char separator)
{ {
_tokenIndex = -1; _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)); 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