浏览代码

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 <[email protected]>
Meloman19 10 月之前
父节点
当前提交
18fcfcc163

+ 4 - 4
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))
             {
             {

+ 3 - 3
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;
             }
             }

+ 13 - 13
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 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 unit = RelativeUnit.Absolute;
                 var scale = 1.0;
                 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)
                 if (xRelative && yRelative && widthRelative && heightRelative)
                 {
                 {
@@ -207,10 +207,10 @@ namespace Avalonia
                 }
                 }
 
 
                 return new RelativeRect(
                 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);
                     unit);
             }
             }
         }
         }

+ 30 - 0
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
         }
         }
     }
     }

+ 25 - 6
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) &&
-                int.TryParse(stringResult, NumberStyles.Integer, _formatProvider, out result))
+            if (TryReadSpan(out var stringResult, separator) &&
+                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) &&
-                double.TryParse(stringResult, NumberStyles.Float, _formatProvider, out result))
+            if (TryReadSpan(out var stringResult, separator) &&
+                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 - 0
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()));
+        }
     }
     }
 }
 }