Skip to content

Commit

Permalink
Expanding StringTokenizator with ReadOnlySpan. (#17645)
Browse files Browse the repository at this point in the history
* Tokenizer returns a ReadOnly Span instead of a string.

* Returning the old property for no API break changes

---------

Co-authored-by: Meloman19 <[email protected]>
  • Loading branch information
Meloman19 and Meloman19 authored Dec 5, 2024
1 parent ab1d77a commit 18fcfcc
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 26 deletions.
8 changes: 4 additions & 4 deletions src/Avalonia.Base/Media/Fonts/FontCollectionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ internal static bool TryGetNearestMatch(
glyphTypeface = typeface;

return true;
}
}
}

return false;
Expand Down Expand Up @@ -297,7 +297,7 @@ internal static bool TryGetWeight(ref string familyName, out FontWeight weight)

var tokenizer = new StringTokenizer(familyName, ' ');

tokenizer.ReadString();
tokenizer.ReadSpan();

while (tokenizer.TryReadString(out var weightString))
{
Expand Down Expand Up @@ -325,7 +325,7 @@ internal static bool TryGetStyle(ref string familyName, out FontStyle style)

var tokenizer = new StringTokenizer(familyName, ' ');

tokenizer.ReadString();
tokenizer.ReadSpan();

while (tokenizer.TryReadString(out var styleString))
{
Expand Down Expand Up @@ -354,7 +354,7 @@ internal static bool TryGetStretch(ref string familyName, out FontStretch stretc

var tokenizer = new StringTokenizer(familyName, ' ');

tokenizer.ReadString();
tokenizer.ReadSpan();

while (tokenizer.TryReadString(out var stretchString))
{
Expand Down
6 changes: 3 additions & 3 deletions src/Avalonia.Base/Media/TextDecorationCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static TextDecorationCollection Parse(string s)

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);

Expand Down Expand Up @@ -59,9 +59,9 @@ public static TextDecorationCollection Parse(string s)
/// </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;
}
Expand Down
26 changes: 13 additions & 13 deletions src/Avalonia.Base/RelativeRect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public Rect ToPixels(Size size)
Rect.Width * size.Width,
Rect.Height * size.Height);
}

/// <summary>
/// Converts a <see cref="RelativeRect"/> into pixels.
/// </summary>
Expand All @@ -178,18 +178,18 @@ public static RelativeRect Parse(string s)
{
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)
{
Expand All @@ -207,10 +207,10 @@ public static RelativeRect Parse(string s)
}

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);
}
}
Expand Down
30 changes: 30 additions & 0 deletions src/Avalonia.Base/Utilities/SpanHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ public static bool TryParseInt(this ReadOnlySpan<char> span, out int value)
#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)
{
Expand All @@ -39,13 +49,33 @@ public static bool TryParseDouble(this ReadOnlySpan<char> span, NumberStyles sty
#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)
{
#if NETSTANDARD2_0
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
}
}
Expand Down
31 changes: 25 additions & 6 deletions src/Avalonia.Base/Utilities/StringTokenizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public StringTokenizer(string s, char separator = DefaultSeparatorChar, string?

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)
Expand All @@ -56,8 +58,8 @@ public void Dispose()

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;
}
Expand All @@ -80,8 +82,8 @@ public int ReadInt32(char? separator = null)

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;
}
Expand All @@ -102,10 +104,10 @@ public double ReadDouble(char? separator = null)
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;
}

Expand All @@ -119,6 +121,23 @@ public string ReadString(char? separator = null)
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;
Expand Down
10 changes: 10 additions & 0 deletions tests/Avalonia.Base.UnitTests/Utilities/StringTokenizerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,15 @@ public void TryReadDouble_Doesnt_Throw()

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()));
}
}
}

0 comments on commit 18fcfcc

Please sign in to comment.