Browse Source

Merge branch 'master' into features/NetAnalyzers/CA1820

pull/9188/head
workgroupengineering 4 years ago
committed by GitHub
parent
commit
d7e1bf64cb
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 49
      src/Avalonia.Base/Media/Color.cs
  2. 30
      src/Avalonia.Base/Media/HslColor.cs
  3. 30
      src/Avalonia.Base/Media/HsvColor.cs
  4. 49
      src/Avalonia.Base/Utilities/SpanHelpers.cs
  5. 72
      src/Avalonia.Build.Tasks/SpanCompat.cs
  6. 18
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
  7. 1
      tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
  8. 6
      tests/Directory.Build.props

49
src/Avalonia.Base/Media/Color.cs

@ -9,6 +9,7 @@ using System;
using System.Globalization; using System.Globalization;
#if !BUILDTASK #if !BUILDTASK
using Avalonia.Animation.Animators; using Avalonia.Animation.Animators;
using static Avalonia.Utilities.SpanHelpers;
#endif #endif
namespace Avalonia.Media namespace Avalonia.Media
@ -295,9 +296,7 @@ namespace Avalonia.Media
return false; return false;
} }
// TODO: (netstandard 2.1) Can use allocation free parsing. if (!input.TryParseUInt(NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsed))
if (!uint.TryParse(input.ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture,
out var parsed))
{ {
return false; return false;
} }
@ -382,9 +381,9 @@ namespace Avalonia.Media
if (components.Length == 3) // RGB if (components.Length == 3) // RGB
{ {
if (InternalTryParseByte(components[0], out byte red) && if (InternalTryParseByte(components[0].AsSpan(), out byte red) &&
InternalTryParseByte(components[1], out byte green) && InternalTryParseByte(components[1].AsSpan(), out byte green) &&
InternalTryParseByte(components[2], out byte blue)) InternalTryParseByte(components[2].AsSpan(), out byte blue))
{ {
color = new Color(0xFF, red, green, blue); color = new Color(0xFF, red, green, blue);
return true; return true;
@ -392,10 +391,10 @@ namespace Avalonia.Media
} }
else if (components.Length == 4) // RGBA else if (components.Length == 4) // RGBA
{ {
if (InternalTryParseByte(components[0], out byte red) && if (InternalTryParseByte(components[0].AsSpan(), out byte red) &&
InternalTryParseByte(components[1], out byte green) && InternalTryParseByte(components[1].AsSpan(), out byte green) &&
InternalTryParseByte(components[2], out byte blue) && InternalTryParseByte(components[2].AsSpan(), out byte blue) &&
InternalTryParseDouble(components[3], out double alpha)) InternalTryParseDouble(components[3].AsSpan(), out double alpha))
{ {
color = new Color((byte)Math.Round(alpha * 255.0), red, green, blue); color = new Color((byte)Math.Round(alpha * 255.0), red, green, blue);
return true; return true;
@ -403,17 +402,14 @@ namespace Avalonia.Media
} }
// Local function to specially parse a byte value with an optional percentage sign // Local function to specially parse a byte value with an optional percentage sign
bool InternalTryParseByte(string inString, out byte outByte) bool InternalTryParseByte(ReadOnlySpan<char> inString, out byte outByte)
{ {
// The percent sign, if it exists, must be at the end of the number // The percent sign, if it exists, must be at the end of the number
int percentIndex = inString.IndexOf("%", StringComparison.Ordinal); int percentIndex = inString.IndexOf("%".AsSpan(), StringComparison.Ordinal);
if (percentIndex >= 0) if (percentIndex >= 0)
{ {
var result = double.TryParse( var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
inString.Substring(0, percentIndex),
NumberStyles.Number,
CultureInfo.InvariantCulture,
out double percentage); out double percentage);
outByte = (byte)Math.Round((percentage / 100.0) * 255.0); outByte = (byte)Math.Round((percentage / 100.0) * 255.0);
@ -421,37 +417,28 @@ namespace Avalonia.Media
} }
else else
{ {
return byte.TryParse( return inString.TryParseByte(NumberStyles.Number, CultureInfo.InvariantCulture,
inString,
NumberStyles.Number,
CultureInfo.InvariantCulture,
out outByte); out outByte);
} }
} }
// Local function to specially parse a double value with an optional percentage sign // Local function to specially parse a double value with an optional percentage sign
bool InternalTryParseDouble(string inString, out double outDouble) bool InternalTryParseDouble(ReadOnlySpan<char> inString, out double outDouble)
{ {
// The percent sign, if it exists, must be at the end of the number // The percent sign, if it exists, must be at the end of the number
int percentIndex = inString.IndexOf("%", StringComparison.Ordinal); int percentIndex = inString.IndexOf("%".AsSpan(), StringComparison.Ordinal);
if (percentIndex >= 0) if (percentIndex >= 0)
{ {
var result = double.TryParse( var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
inString.Substring(0, percentIndex), out double percentage);
NumberStyles.Number,
CultureInfo.InvariantCulture,
out double percentage);
outDouble = percentage / 100.0; outDouble = percentage / 100.0;
return result; return result;
} }
else else
{ {
return double.TryParse( return inString.TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
inString,
NumberStyles.Number,
CultureInfo.InvariantCulture,
out outDouble); out outDouble);
} }
} }

30
src/Avalonia.Base/Media/HslColor.cs

@ -302,9 +302,9 @@ namespace Avalonia.Media
if (components.Length == 3) // HSL if (components.Length == 3) // HSL
{ {
if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) && if (components[0].AsSpan().TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
TryInternalParse(components[1], out double saturation) && TryInternalParse(components[1].AsSpan(), out double saturation) &&
TryInternalParse(components[2], out double lightness)) TryInternalParse(components[2].AsSpan(), out double lightness))
{ {
hslColor = new HslColor(1.0, hue, saturation, lightness); hslColor = new HslColor(1.0, hue, saturation, lightness);
return true; return true;
@ -312,10 +312,10 @@ namespace Avalonia.Media
} }
else if (components.Length == 4) // HSLA else if (components.Length == 4) // HSLA
{ {
if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) && if (components[0].AsSpan().TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
TryInternalParse(components[1], out double saturation) && TryInternalParse(components[1].AsSpan(), out double saturation) &&
TryInternalParse(components[2], out double lightness) && TryInternalParse(components[2].AsSpan(), out double lightness) &&
TryInternalParse(components[3], out double alpha)) TryInternalParse(components[3].AsSpan(), out double alpha))
{ {
hslColor = new HslColor(alpha, hue, saturation, lightness); hslColor = new HslColor(alpha, hue, saturation, lightness);
return true; return true;
@ -323,28 +323,22 @@ namespace Avalonia.Media
} }
// Local function to specially parse a double value with an optional percentage sign // Local function to specially parse a double value with an optional percentage sign
bool TryInternalParse(string inString, out double outDouble) bool TryInternalParse(ReadOnlySpan<char> inString, out double outDouble)
{ {
// The percent sign, if it exists, must be at the end of the number // The percent sign, if it exists, must be at the end of the number
int percentIndex = inString.IndexOf("%", StringComparison.Ordinal); int percentIndex = inString.IndexOf("%".AsSpan(), StringComparison.Ordinal);
if (percentIndex >= 0) if (percentIndex >= 0)
{ {
var result = double.TryParse( var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
inString.Substring(0, percentIndex), out double percentage);
NumberStyles.Number,
CultureInfo.InvariantCulture,
out double percentage);
outDouble = percentage / 100.0; outDouble = percentage / 100.0;
return result; return result;
} }
else else
{ {
return double.TryParse( return inString.TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
inString,
NumberStyles.Number,
CultureInfo.InvariantCulture,
out outDouble); out outDouble);
} }
} }

30
src/Avalonia.Base/Media/HsvColor.cs

@ -302,9 +302,9 @@ namespace Avalonia.Media
if (components.Length == 3) // HSV if (components.Length == 3) // HSV
{ {
if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) && if (components[0].AsSpan().TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
TryInternalParse(components[1], out double saturation) && TryInternalParse(components[1].AsSpan(), out double saturation) &&
TryInternalParse(components[2], out double value)) TryInternalParse(components[2].AsSpan(), out double value))
{ {
hsvColor = new HsvColor(1.0, hue, saturation, value); hsvColor = new HsvColor(1.0, hue, saturation, value);
return true; return true;
@ -312,10 +312,10 @@ namespace Avalonia.Media
} }
else if (components.Length == 4) // HSVA else if (components.Length == 4) // HSVA
{ {
if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) && if (components[0].AsSpan().TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
TryInternalParse(components[1], out double saturation) && TryInternalParse(components[1].AsSpan(), out double saturation) &&
TryInternalParse(components[2], out double value) && TryInternalParse(components[2].AsSpan(), out double value) &&
TryInternalParse(components[3], out double alpha)) TryInternalParse(components[3].AsSpan(), out double alpha))
{ {
hsvColor = new HsvColor(alpha, hue, saturation, value); hsvColor = new HsvColor(alpha, hue, saturation, value);
return true; return true;
@ -323,28 +323,22 @@ namespace Avalonia.Media
} }
// Local function to specially parse a double value with an optional percentage sign // Local function to specially parse a double value with an optional percentage sign
bool TryInternalParse(string inString, out double outDouble) bool TryInternalParse(ReadOnlySpan<char> inString, out double outDouble)
{ {
// The percent sign, if it exists, must be at the end of the number // The percent sign, if it exists, must be at the end of the number
int percentIndex = inString.IndexOf("%", StringComparison.Ordinal); int percentIndex = inString.IndexOf("%".AsSpan(), StringComparison.Ordinal);
if (percentIndex >= 0) if (percentIndex >= 0)
{ {
var result = double.TryParse( var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
inString.Substring(0, percentIndex), out double percentage);
NumberStyles.Number,
CultureInfo.InvariantCulture,
out double percentage);
outDouble = percentage / 100.0; outDouble = percentage / 100.0;
return result; return result;
} }
else else
{ {
return double.TryParse( return inString.TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
inString,
NumberStyles.Number,
CultureInfo.InvariantCulture,
out outDouble); out outDouble);
} }
} }

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

@ -0,0 +1,49 @@
using System;
using System.Globalization;
using System.Runtime.CompilerServices;
namespace Avalonia.Utilities
{
public static class SpanHelpers
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseUInt(this ReadOnlySpan<char> span, NumberStyles style, IFormatProvider provider, out uint value)
{
#if NETSTANDARD2_0
return uint.TryParse(span.ToString(), style, provider, out value);
#else
return uint.TryParse(span, style, provider, out value);
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseInt(this ReadOnlySpan<char> span, out int value)
{
#if NETSTANDARD2_0
return int.TryParse(span.ToString(), out value);
#else
return int.TryParse(span, out value);
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseDouble(this ReadOnlySpan<char> span, NumberStyles style, IFormatProvider provider, out double value)
{
#if NETSTANDARD2_0
return double.TryParse(span.ToString(), style, provider, out value);
#else
return double.TryParse(span, style, provider, out value);
#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
}
}
}

72
src/Avalonia.Build.Tasks/SpanCompat.cs

@ -1,4 +1,7 @@
#if !NETCOREAPP3_1_OR_GREATER #if !NETCOREAPP3_1_OR_GREATER
using System.Globalization;
using System.Runtime.CompilerServices;
namespace System namespace System
{ {
// This is a hack to enable our span code to work inside MSBuild task without referencing System.Memory // This is a hack to enable our span code to work inside MSBuild task without referencing System.Memory
@ -9,6 +12,8 @@ namespace System
private int _length; private int _length;
public int Length => _length; public int Length => _length;
public static implicit operator ReadOnlySpan<T>(string s) => new ReadOnlySpan<T>(s);
public ReadOnlySpan(string s) : this(s, 0, s.Length) public ReadOnlySpan(string s) : this(s, 0, s.Length)
{ {
@ -63,8 +68,75 @@ namespace System
return Slice(start); return Slice(start);
} }
public ReadOnlySpan<char> TrimEnd()
{
int end = Length - 1;
for (; end >= 0; end--)
{
if (!char.IsWhiteSpace(this[end]))
{
break;
}
}
return Slice(0, end + 1);
}
public ReadOnlySpan<char> Trim()
{
return TrimStart().TrimEnd();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryParseUInt(NumberStyles style, IFormatProvider provider, out uint value)
{
return uint.TryParse(ToString(), style, provider, out value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryParseInt(out int value)
{
return int.TryParse(ToString(), out value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryParseDouble(NumberStyles style, IFormatProvider provider, out double value)
{
return double.TryParse(ToString(), style, provider, out value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryParseByte(NumberStyles style, IFormatProvider provider, out byte value)
{
return byte.TryParse(ToString(), style, provider, out value);
}
public override string ToString() => _length == 0 ? string.Empty : _s.Substring(_start, _length); public override string ToString() => _length == 0 ? string.Empty : _s.Substring(_start, _length);
internal int IndexOf(ReadOnlySpan<char> v, StringComparison ordinal, int start = 0)
{
if(Length == 0 || v.IsEmpty)
{
return -1;
}
for (var c = start; c < _length; c++)
{
if (this[c] == v[0])
{
for(var i = 0; i < v.Length; i++)
{
if (this[c + i] != v[i])
{
break;
}
}
return c;
}
}
return -1;
}
public static implicit operator ReadOnlySpan<T>(char[] arr) => new ReadOnlySpan<T>(new string(arr)); public static implicit operator ReadOnlySpan<T>(char[] arr) => new ReadOnlySpan<T>(new string(arr));
} }

18
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs

@ -376,28 +376,28 @@ namespace Avalonia.Markup.Parsers
if (r.Peek == 'o') if (r.Peek == 'o')
{ {
var constArg = r.TakeUntil(')').ToString().Trim(); var constArg = r.TakeUntil(')').Trim();
if (constArg.Equals("odd", StringComparison.Ordinal)) if (constArg.SequenceEqual("odd".AsSpan()))
{ {
step = 2; step = 2;
offset = 1; offset = 1;
} }
else else
{ {
throw new ExpressionParseException(r.Position, $"Expected nth-child(odd). Actual '{constArg}'."); throw new ExpressionParseException(r.Position, $"Expected nth-child(odd). Actual '{constArg.ToString()}'.");
} }
} }
else if (r.Peek == 'e') else if (r.Peek == 'e')
{ {
var constArg = r.TakeUntil(')').ToString().Trim(); var constArg = r.TakeUntil(')').Trim();
if (constArg.Equals("even", StringComparison.Ordinal)) if (constArg.SequenceEqual("even".AsSpan()))
{ {
step = 2; step = 2;
offset = 0; offset = 0;
} }
else else
{ {
throw new ExpressionParseException(r.Position, $"Expected nth-child(even). Actual '{constArg}'."); throw new ExpressionParseException(r.Position, $"Expected nth-child(even). Actual '{constArg.ToString()}'.");
} }
} }
else else
@ -405,7 +405,7 @@ namespace Avalonia.Markup.Parsers
r.SkipWhitespace(); r.SkipWhitespace();
var stepOrOffset = 0; var stepOrOffset = 0;
var stepOrOffsetStr = r.TakeWhile(c => char.IsDigit(c) || c == '-' || c == '+').ToString(); var stepOrOffsetStr = r.TakeWhile(c => char.IsDigit(c) || c == '-' || c == '+');
if (stepOrOffsetStr.Length == 0 if (stepOrOffsetStr.Length == 0
|| (stepOrOffsetStr.Length == 1 || (stepOrOffsetStr.Length == 1
&& stepOrOffsetStr[0] == '+')) && stepOrOffsetStr[0] == '+'))
@ -417,7 +417,7 @@ namespace Avalonia.Markup.Parsers
{ {
stepOrOffset = -1; stepOrOffset = -1;
} }
else if (!int.TryParse(stepOrOffsetStr.ToString(), out stepOrOffset)) else if (!stepOrOffsetStr.TryParseInt(out stepOrOffset))
{ {
throw new ExpressionParseException(r.Position, "Couldn't parse nth-child step or offset value. Integer was expected."); throw new ExpressionParseException(r.Position, "Couldn't parse nth-child step or offset value. Integer was expected.");
} }
@ -462,7 +462,7 @@ namespace Avalonia.Markup.Parsers
r.SkipWhitespace(); r.SkipWhitespace();
if (sign != 0 if (sign != 0
&& !int.TryParse(r.TakeUntil(')').ToString(), out offset)) && !r.TakeUntil(')').TryParseInt(out offset))
{ {
throw new ExpressionParseException(r.Position, "Couldn't parse nth-child offset value. Integer was expected."); throw new ExpressionParseException(r.Position, "Couldn't parse nth-child offset value. Integer was expected.");
} }

1
tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj

@ -1,6 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>

6
tests/Directory.Build.props

@ -0,0 +1,6 @@
<Project>
<Import Project="..\Directory.Build.props" />
<PropertyGroup>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
</PropertyGroup>
</Project>
Loading…
Cancel
Save