From a2e974ac96c3f158852a7f52bbca389376b46cd3 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 17 May 2022 11:25:48 -0400 Subject: [PATCH 1/2] Add more char.Is helpers for common ASCII sets --- .../src/System/Reflection/Emit/SymbolType.cs | 8 +- .../Globalization/FormatProvider.Number.cs | 13 +-- .../src/System/IO/PathInternal.Windows.cs | 2 +- .../System/Net/IPv4AddressHelper.Common.cs | 4 +- .../System/Net/IPv6AddressHelper.Common.cs | 6 +- .../DataAnnotations/CreditCardAttribute.cs | 4 +- .../ComponentModel/MaskedTextProvider.cs | 24 +--- .../src/System/ConsolePal.Unix.cs | 5 +- .../src/System/IO/StdInReader.cs | 8 +- .../System.Console/src/System/TermInfo.cs | 8 +- .../src/System/Data/SQLTypes/SQLDecimal.cs | 6 +- .../Diagnostics/FileVersionInfo.Unix.cs | 2 +- .../Net/Http/Headers/AltSvcHeaderParser.cs | 2 +- .../Http/Headers/RetryConditionHeaderValue.cs | 2 +- .../src/System/Net/Http/HttpRuleParser.cs | 2 +- .../src/System/Net/Http/MultipartContent.cs | 10 +- .../Managed/HttpListenerRequest.Managed.cs | 6 +- .../src/System/Net/Mime/QEncoder.cs | 8 +- .../src/System/Net/Cookie.cs | 6 +- .../src/System/Net/CookieContainer.cs | 6 +- .../src/System/Net/WebClient.cs | 2 +- .../System.Private.CoreLib/src/System/Char.cs | 110 +++++++++++++++--- .../System.Private.CoreLib/src/System/Enum.cs | 2 +- .../src/System/Environment.OSVersion.Unix.cs | 9 +- .../System/Globalization/CompareInfo.Icu.cs | 4 +- .../System/Globalization/CultureData.Icu.cs | 2 +- .../System/Globalization/CultureData.Nls.cs | 2 +- .../src/System/Globalization/CultureData.cs | 4 +- .../DateTimeFormatInfoScanner.cs | 19 ++- .../src/System/Globalization/DateTimeParse.cs | 30 ++--- .../src/System/Globalization/IcuLocaleData.cs | 2 +- .../src/System/Globalization/IdnMapping.cs | 14 +-- .../src/System/Globalization/Ordinal.cs | 9 +- .../src/System/Globalization/TextInfo.cs | 8 +- .../src/System/Globalization/TimeSpanParse.cs | 13 ++- .../System/IO/DriveInfoInternal.Windows.cs | 3 +- .../src/System/Net/WebUtility.cs | 10 +- .../src/System/Number.Formatting.cs | 7 +- .../src/System/Number.NumberBuffer.cs | 2 +- .../src/System/Reflection/AssemblyName.cs | 16 +-- .../System/Reflection/AssemblyNameParser.cs | 14 +-- .../src/System/Text/StringBuilder.cs | 8 +- .../Text/ValueStringBuilder.AppendFormat.cs | 8 +- .../TimeZoneInfo.FullGlobalizationData.cs | 2 +- .../System/TimeZoneInfo.StringSerializer.cs | 5 +- .../src/System/TimeZoneInfo.Unix.Android.cs | 4 +- .../src/System/TimeZoneInfo.Unix.cs | 6 +- .../Runtime/Serialization/DataContract.cs | 14 +-- .../src/System/Xml/XmlBaseReader.cs | 2 +- .../src/System/Xml/XmlBinaryWriter.cs | 10 +- .../src/System/DomainNameHelper.cs | 10 +- .../src/System/IPv6AddressHelper.cs | 2 +- .../src/System/UncNameHelper.cs | 7 +- .../System.Private.Uri/src/System/Uri.cs | 27 ++--- .../System.Private.Uri/src/System/UriExt.cs | 2 +- .../src/System/UriHelper.cs | 13 +-- .../src/System/Xml/Core/XmlTextReaderImpl.cs | 2 +- .../src/System/Xml/Schema/Inference/Infer.cs | 82 ++++++------- .../src/System/Xml/Schema/XsdDuration.cs | 4 +- .../Xml/Xsl/Runtime/DecimalFormatter.cs | 6 +- .../tests/System/Convert.cs | 6 +- .../src/System/Numerics/BigNumber.cs | 6 +- .../BigInteger/BigIntegerToStringTests.cs | 2 +- .../tests/BigInteger/parse.cs | 2 +- .../SerializationTestTypes/DataContract.cs | 14 +-- .../System.Runtime/ref/System.Runtime.cs | 9 ++ .../System.Runtime/tests/System/CharTests.cs | 93 +++++++++++++++ .../Security/Cryptography/PemEncoding.cs | 6 +- .../Cryptography/X509Certificates/FindPal.cs | 2 +- .../OpenSslX509ChainProcessor.cs | 4 +- .../X500DistinguishedNameBuilder.cs | 8 +- .../gen/RegexGenerator.Emitter.cs | 36 +++++- .../Text/RegularExpressions/RegexCharClass.cs | 12 +- .../Text/RegularExpressions/RegexCompiler.cs | 47 +++++++- .../src/System/Web/Util/HttpEncoderUtility.cs | 2 +- .../tests/HttpUtility/HttpUtilityTest.cs | 2 +- 76 files changed, 516 insertions(+), 363 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/SymbolType.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/SymbolType.cs index f6a1dfb3bff5e5..2b8da3fe5da997 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/SymbolType.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/SymbolType.cs @@ -87,7 +87,7 @@ public override bool IsAssignableFrom([NotNullWhen(true)] TypeInfo? typeInfo) curIndex++; } // consume, one dimension at a time - if ((format[curIndex] >= '0' && format[curIndex] <= '9') || format[curIndex] == '-') + if (char.IsAsciiDigit(format[curIndex]) || format[curIndex] == '-') { bool isNegative = false; if (format[curIndex] == '-') @@ -97,7 +97,7 @@ public override bool IsAssignableFrom([NotNullWhen(true)] TypeInfo? typeInfo) } // lower bound is specified. Consume the low bound - while (format[curIndex] >= '0' && format[curIndex] <= '9') + while (char.IsAsciiDigit(format[curIndex])) { iLowerBound *= 10; iLowerBound += format[curIndex] - '0'; @@ -126,7 +126,7 @@ public override bool IsAssignableFrom([NotNullWhen(true)] TypeInfo? typeInfo) curIndex++; // consume the upper bound - if ((format[curIndex] >= '0' && format[curIndex] <= '9') || format[curIndex] == '-') + if (char.IsAsciiDigit(format[curIndex]) || format[curIndex] == '-') { bool isNegative = false; iUpperBound = 0; @@ -137,7 +137,7 @@ public override bool IsAssignableFrom([NotNullWhen(true)] TypeInfo? typeInfo) } // lower bound is specified. Consume the low bound - while (format[curIndex] >= '0' && format[curIndex] <= '9') + while (char.IsAsciiDigit(format[curIndex])) { iUpperBound *= 10; iUpperBound += format[curIndex] - '0'; diff --git a/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs b/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs index b097ff36758fd0..07fda3d0c06d67 100644 --- a/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs +++ b/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs @@ -440,7 +440,7 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles int digEnd = 0; while (true) { - if ((ch >= '0' && ch <= '9') || (((options & NumberStyles.AllowHexSpecifier) != 0) && ((ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')))) + if (char.IsAsciiDigit(ch) || (((options & NumberStyles.AllowHexSpecifier) != 0) && char.IsBetween((char)(ch | 0x20), 'a', 'f'))) { state |= StateDigits; @@ -510,7 +510,7 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles ch = (p = next) < strEnd ? *p : '\0'; negExp = true; } - if (ch >= '0' && ch <= '9') + if (char.IsAsciiDigit(ch)) { int exp = 0; do @@ -520,12 +520,12 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles if (exp > 1000) { exp = 9999; - while (ch >= '0' && ch <= '9') + while (char.IsAsciiDigit(ch)) { ch = ++p < strEnd ? *p : '\0'; } } - } while (ch >= '0' && ch <= '9'); + } while (char.IsAsciiDigit(ch)); if (negExp) { exp = -exp; @@ -678,8 +678,7 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig // If the format begins with a symbol, see if it's a standard format // with or without a specified number of digits. c = format[0]; - if ((uint)(c - 'A') <= 'Z' - 'A' || - (uint)(c - 'a') <= 'z' - 'a') + if (char.IsAsciiLetter(c)) { // Fast path for sole symbol, e.g. "D" if (format.Length == 1) @@ -714,7 +713,7 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig // digits. Further, for compat, we need to stop when we hit a null char. int n = 0; int i = 1; - while (i < format.Length && (((uint)format[i] - '0') < 10)) + while ((uint)i < (uint)format.Length && char.IsAsciiDigit(format[i])) { int temp = (n * 10) + format[i++] - '0'; if (temp < n) diff --git a/src/libraries/Common/src/System/IO/PathInternal.Windows.cs b/src/libraries/Common/src/System/IO/PathInternal.Windows.cs index 48fb35304cd7d4..9015df805583e5 100644 --- a/src/libraries/Common/src/System/IO/PathInternal.Windows.cs +++ b/src/libraries/Common/src/System/IO/PathInternal.Windows.cs @@ -69,7 +69,7 @@ internal static partial class PathInternal /// internal static bool IsValidDriveChar(char value) { - return (value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z'); + return (uint)((value | 0x20) - 'a') <= (uint)('z' - 'a'); } internal static bool EndsWithPeriodOrSpace(string? path) diff --git a/src/libraries/Common/src/System/Net/IPv4AddressHelper.Common.cs b/src/libraries/Common/src/System/Net/IPv4AddressHelper.Common.cs index cbcb01c4c105c2..f4dd4b77772349 100644 --- a/src/libraries/Common/src/System/Net/IPv4AddressHelper.Common.cs +++ b/src/libraries/Common/src/System/Net/IPv4AddressHelper.Common.cs @@ -126,7 +126,7 @@ internal static unsafe bool IsValidCanonical(char* name, int start, ref int end, break; } - if (ch <= '9' && ch >= '0') + if (char.IsAsciiDigit(ch)) { if (!haveNumber && (ch == '0')) { @@ -217,7 +217,7 @@ internal static unsafe long ParseNonCanonical(char* name, int start, ref int end ch = name[current]; int digitValue; - if ((numberBase == Decimal || numberBase == Hex) && '0' <= ch && ch <= '9') + if ((numberBase == Decimal || numberBase == Hex) && char.IsAsciiDigit(ch)) { digitValue = ch - '0'; } diff --git a/src/libraries/Common/src/System/Net/IPv6AddressHelper.Common.cs b/src/libraries/Common/src/System/Net/IPv6AddressHelper.Common.cs index bba81ba68f94db..2753240ccc7b38 100644 --- a/src/libraries/Common/src/System/Net/IPv6AddressHelper.Common.cs +++ b/src/libraries/Common/src/System/Net/IPv6AddressHelper.Common.cs @@ -123,7 +123,7 @@ internal static unsafe bool IsValidStrict(char* name, int start, ref int end) int i; for (i = start; i < end; ++i) { - if (Uri.IsHexDigit(name[i])) + if (char.IsAsciiHexDigit(name[i])) { ++sequenceLength; expectingNumber = false; @@ -176,7 +176,7 @@ internal static unsafe bool IsValidStrict(char* name, int start, ref int end) i += 4; for (; i < end; i++) { - if (!Uri.IsHexDigit(name[i])) + if (!char.IsAsciiHexDigit(name[i])) { return false; } @@ -187,7 +187,7 @@ internal static unsafe bool IsValidStrict(char* name, int start, ref int end) i += 2; for (; i < end; i++) { - if (name[i] < '0' || name[i] > '9') + if (!char.IsAsciiDigit(name[i])) { return false; } diff --git a/src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/CreditCardAttribute.cs b/src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/CreditCardAttribute.cs index 4715007eaf8b37..446861729f5471 100644 --- a/src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/CreditCardAttribute.cs +++ b/src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/CreditCardAttribute.cs @@ -35,8 +35,8 @@ public override bool IsValid(object? value) // Note: string.Reverse() does not exist for WinPhone for (var i = ccValue.Length - 1; i >= 0; i--) { - var digit = ccValue[i]; - if (digit < '0' || digit > '9') + char digit = ccValue[i]; + if (!char.IsAsciiDigit(digit)) { return false; } diff --git a/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/MaskedTextProvider.cs b/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/MaskedTextProvider.cs index f210c54d5dede9..1de8d049e2e528 100644 --- a/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/MaskedTextProvider.cs +++ b/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/MaskedTextProvider.cs @@ -1395,14 +1395,6 @@ private static bool IsAscii(char c) return (c >= '!' && c <= '~'); } - /// - /// Helper function for alphanumeric char in ascii mode. - /// - private static bool IsAciiAlphanumeric(char c) - { - return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); - } - /// /// Helper function for testing mask language alphanumeric identifiers. /// @@ -1411,14 +1403,6 @@ private static bool IsAlphanumeric(char c) return char.IsLetter(c) || char.IsDigit(c); } - /// - /// Helper function for testing letter char in ascii mode. - /// - private static bool IsAsciiLetter(char c) - { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); - } - /// /// Checks whether the specified position is available for assignment. Returns false if it is assigned /// or it is not editable, true otherwise. @@ -2302,7 +2286,7 @@ private bool TestChar(char input, int position, out MaskedTextResultHint resultH resultHint = MaskedTextResultHint.LetterExpected; return false; } - if (!IsAsciiLetter(input) && AsciiOnly) + if (!char.IsAsciiLetter(input) && AsciiOnly) { resultHint = MaskedTextResultHint.AsciiCharacterExpected; return false; @@ -2315,7 +2299,7 @@ private bool TestChar(char input, int position, out MaskedTextResultHint resultH resultHint = MaskedTextResultHint.LetterExpected; return false; } - if (!IsAsciiLetter(input) && AsciiOnly) + if (!char.IsAsciiLetter(input) && AsciiOnly) { resultHint = MaskedTextResultHint.AsciiCharacterExpected; return false; @@ -2344,7 +2328,7 @@ private bool TestChar(char input, int position, out MaskedTextResultHint resultH resultHint = MaskedTextResultHint.AlphanumericCharacterExpected; return false; } - if (!IsAciiAlphanumeric(input) && AsciiOnly) + if (!char.IsAsciiLetterOrDigit(input) && AsciiOnly) { resultHint = MaskedTextResultHint.AsciiCharacterExpected; return false; @@ -2357,7 +2341,7 @@ private bool TestChar(char input, int position, out MaskedTextResultHint resultH resultHint = MaskedTextResultHint.AlphanumericCharacterExpected; return false; } - if (!IsAciiAlphanumeric(input) && AsciiOnly) + if (!char.IsAsciiLetterOrDigit(input) && AsciiOnly) { resultHint = MaskedTextResultHint.AsciiCharacterExpected; return false; diff --git a/src/libraries/System.Console/src/System/ConsolePal.Unix.cs b/src/libraries/System.Console/src/System/ConsolePal.Unix.cs index 3e3691f3b4f070..69baefb0ba431b 100644 --- a/src/libraries/System.Console/src/System/ConsolePal.Unix.cs +++ b/src/libraries/System.Console/src/System/ConsolePal.Unix.cs @@ -629,7 +629,7 @@ static void ReadRowOrCol(int startExclusive, int endExclusive, StdInReader reade for (int i = startExclusive + 1; i < endExclusive; i++) { byte b = source[i]; - if (IsDigit(b)) + if (char.IsAsciiDigit((char)b)) { try { @@ -675,9 +675,6 @@ public static void MoveBufferArea(int sourceLeft, int sourceTop, int sourceWidth throw new PlatformNotSupportedException(); } - /// Gets whether the specified character is a digit 0-9. - private static bool IsDigit(byte c) => c >= '0' && c <= '9'; - /// /// Gets whether the specified file descriptor was redirected. /// It's considered redirected if it doesn't refer to a terminal. diff --git a/src/libraries/System.Console/src/System/IO/StdInReader.cs b/src/libraries/System.Console/src/System/IO/StdInReader.cs index f44fd7d47ded13..f7e2810b699538 100644 --- a/src/libraries/System.Console/src/System/IO/StdInReader.cs +++ b/src/libraries/System.Console/src/System/IO/StdInReader.cs @@ -344,27 +344,27 @@ internal static ConsoleKey GetKeyFromCharValue(char x, out bool isShift, out boo default: // 1. Ctrl A to Ctrl Z. - if (x >= 1 && x <= 26) + if (char.IsBetween(x, (char)1, (char)26)) { isCtrl = true; return ConsoleKey.A + x - 1; } // 2. Numbers from 0 to 9. - if (x >= '0' && x <= '9') + if (char.IsAsciiDigit(x)) { return ConsoleKey.D0 + x - '0'; } //3. A to Z - if (x >= 'A' && x <= 'Z') + if (char.IsAsciiLetterUpper(x)) { isShift = true; return ConsoleKey.A + (x - 'A'); } // 4. a to z. - if (x >= 'a' && x <= 'z') + if (char.IsAsciiLetterLower(x)) { return ConsoleKey.A + (x - 'a'); } diff --git a/src/libraries/System.Console/src/System/TermInfo.cs b/src/libraries/System.Console/src/System/TermInfo.cs index 1f7d67188c882b..38021b6747b72c 100644 --- a/src/libraries/System.Console/src/System/TermInfo.cs +++ b/src/libraries/System.Console/src/System/TermInfo.cs @@ -687,7 +687,7 @@ private static string EvaluateInternal( // Stack pushing operations case 'p': // Push the specified parameter (1-based) onto the stack pos++; - Debug.Assert(format[pos] >= '0' && format[pos] <= '9'); + Debug.Assert(char.IsAsciiDigit(format[pos])); stack.Push(args[format[pos] - '1']); break; case 'l': // Pop a string and push its length @@ -698,7 +698,7 @@ private static string EvaluateInternal( int intLit = 0; while (format[pos] != '}') { - Debug.Assert(format[pos] >= '0' && format[pos] <= '9'); + Debug.Assert(char.IsAsciiDigit(format[pos])); intLit = (intLit * 10) + (format[pos] - '0'); pos++; } @@ -900,12 +900,12 @@ private static unsafe string FormatPrintF(string format, object arg) private static FormatParam[] GetDynamicOrStaticVariables( char c, ref FormatParam[]? dynamicVars, ref FormatParam[]? staticVars, out int index) { - if (c >= 'A' && c <= 'Z') + if (char.IsAsciiLetterUpper(c)) { index = c - 'A'; return staticVars ?? (staticVars = new FormatParam[26]); // one slot for each letter of alphabet } - else if (c >= 'a' && c <= 'z') + else if (char.IsAsciiLetterLower(c)) { index = c - 'a'; return dynamicVars ?? (dynamicVars = new FormatParam[26]); // one slot for each letter of alphabet diff --git a/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SQLDecimal.cs b/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SQLDecimal.cs index 138a7685ca08da..44687a102bb392 100644 --- a/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SQLDecimal.cs +++ b/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SQLDecimal.cs @@ -1073,15 +1073,19 @@ public static SqlDecimal Parse(string s) usChar = rgwchStr[iCurChar]; iCurChar++; - if (usChar >= '0' && usChar <= '9') + if (char.IsAsciiDigit(usChar)) + { usChar -= '0'; + } else if (usChar == '.' && lDecPnt < 0) { lDecPnt = iData; continue; } else + { throw new FormatException(SQLResource.FormatMessage); + } snResult.MultByULong(s_ulBase10); snResult.AddULong(usChar); diff --git a/src/libraries/System.Diagnostics.FileVersionInfo/src/System/Diagnostics/FileVersionInfo.Unix.cs b/src/libraries/System.Diagnostics.FileVersionInfo/src/System/Diagnostics/FileVersionInfo.Unix.cs index f666e971e3f8ed..f49d489965533f 100644 --- a/src/libraries/System.Diagnostics.FileVersionInfo/src/System/Diagnostics/FileVersionInfo.Unix.cs +++ b/src/libraries/System.Diagnostics.FileVersionInfo/src/System/Diagnostics/FileVersionInfo.Unix.cs @@ -234,7 +234,7 @@ private static ushort ParseUInt16UntilNonDigit(string s, out bool endedEarly) for (int index = 0; index < s.Length; index++) { char c = s[index]; - if (c < '0' || c > '9') + if (!char.IsAsciiDigit(c)) { endedEarly = true; break; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderParser.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderParser.cs index 30fe2d650b64c9..beef88449c336f 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderParser.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderParser.cs @@ -442,7 +442,7 @@ private static bool TryReadQuotedInt32Value(ReadOnlySpan value, out int re // The port shouldn't ever need a quoted-pair, but they're still valid... skip if found. if (ch == '\\') continue; - if ((uint)(ch - '0') > '9' - '0') // ch < '0' || ch > '9' + if (!char.IsAsciiDigit(ch)) { result = 0; return false; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/RetryConditionHeaderValue.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/RetryConditionHeaderValue.cs index 5343cb23bc502c..77b3e18195733b 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/RetryConditionHeaderValue.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/RetryConditionHeaderValue.cs @@ -126,7 +126,7 @@ internal static int GetRetryConditionLength(string? input, int startIndex, out o // If it is a number, we have a timespan, otherwise we assume we have a date. char firstChar = input[current]; - if ((firstChar >= '0') && (firstChar <= '9')) + if (char.IsAsciiDigit(firstChar)) { int deltaStartIndex = current; int deltaLength = HttpRuleParser.GetNumberLength(input, current, false); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpRuleParser.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpRuleParser.cs index e64125f0fb5f27..deadd8b5b25e7b 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/HttpRuleParser.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpRuleParser.cs @@ -177,7 +177,7 @@ internal static int GetNumberLength(string input, int startIndex, bool allowDeci while (current < input.Length) { c = input[current]; - if ((c >= '0') && (c <= '9')) + if (char.IsAsciiDigit(c)) { current++; } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/MultipartContent.cs b/src/libraries/System.Net.Http/src/System/Net/Http/MultipartContent.cs index 6bbff7ba2d872f..863d7ab9ec3dc4 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/MultipartContent.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/MultipartContent.cs @@ -89,14 +89,8 @@ private static void ValidateBoundary(string boundary) foreach (char ch in boundary) { - if (('0' <= ch && ch <= '9') || // Digit. - ('a' <= ch && ch <= 'z') || // alpha. - ('A' <= ch && ch <= 'Z') || // ALPHA. - (AllowedMarks.Contains(ch))) // Marks. - { - // Valid. - } - else + if (!char.IsAsciiLetterOrDigit(ch) && + !AllowedMarks.Contains(ch)) // Marks. { throw new ArgumentException(SR.Format(System.Globalization.CultureInfo.InvariantCulture, SR.net_http_headers_invalid_value, boundary), nameof(boundary)); } diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpListenerRequest.Managed.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpListenerRequest.Managed.cs index aa1f0ee2ae3700..d0159245503a9c 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpListenerRequest.Managed.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpListenerRequest.Managed.cs @@ -87,10 +87,8 @@ internal void SetRequestLine(string req) _method = parts[0]; foreach (char c in _method) { - int ic = (int)c; - - if ((ic >= 'A' && ic <= 'Z') || - (ic > 32 && c < 127 && c != '(' && c != ')' && c != '<' && + if (char.IsAsciiLetterUpper(c) || + (c > 32 && c < 127 && c != '(' && c != ')' && c != '<' && c != '<' && c != '>' && c != '@' && c != ',' && c != ';' && c != ':' && c != '\\' && c != '"' && c != '/' && c != '[' && c != ']' && c != '?' && c != '=' && c != '{' && c != '}')) diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mime/QEncoder.cs b/src/libraries/System.Net.Mail/src/System/Net/Mime/QEncoder.cs index ce4a0f57227fe7..c831ea82e375dc 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mime/QEncoder.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mime/QEncoder.cs @@ -85,7 +85,7 @@ protected override void ApppendEncodedByte(byte b) } // RFC 2047 Section 5 part 3 also allows for !*+-/ but these arn't required in headers. // Conservatively encode anything but letters or digits. - else if (IsAsciiLetterOrDigit((char)b)) + else if (char.IsAsciiLetterOrDigit((char)b)) { // Just a regular printable ascii char. WriteState.Append(b); @@ -100,11 +100,5 @@ protected override void ApppendEncodedByte(byte b) WriteState.Append((byte)HexConverter.ToCharUpper(b)); } } - - private static bool IsAsciiLetterOrDigit(char character) => - IsAsciiLetter(character) || (character >= '0' && character <= '9'); - - private static bool IsAsciiLetter(char character) => - (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z'); } } diff --git a/src/libraries/System.Net.Primitives/src/System/Net/Cookie.cs b/src/libraries/System.Net.Primitives/src/System/Net/Cookie.cs index 4d0bfa918fd242..01e82b6286a8da 100644 --- a/src/libraries/System.Net.Primitives/src/System/Net/Cookie.cs +++ b/src/libraries/System.Net.Primitives/src/System/Net/Cookie.cs @@ -561,11 +561,7 @@ private static bool DomainCharsTest(string name) for (int i = 0; i < name.Length; ++i) { char ch = name[i]; - if (!((ch >= '0' && ch <= '9') || - (ch == '.' || ch == '-') || - (ch >= 'a' && ch <= 'z') || - (ch >= 'A' && ch <= 'Z') || - (ch == '_'))) + if (!(char.IsAsciiLetterOrDigit(ch) || ch == '.' || ch == '-' || ch == '_')) { return false; } diff --git a/src/libraries/System.Net.Primitives/src/System/Net/CookieContainer.cs b/src/libraries/System.Net.Primitives/src/System/Net/CookieContainer.cs index d61dfaea829a68..3825092e21c2b3 100644 --- a/src/libraries/System.Net.Primitives/src/System/Net/CookieContainer.cs +++ b/src/libraries/System.Net.Primitives/src/System/Net/CookieContainer.cs @@ -624,21 +624,21 @@ internal bool IsLocalDomain(string host) switch (part.Length) { case 3: - if (part[2] < '0' || part[2] > '9') + if (!char.IsAsciiDigit(part[2])) { break; } goto case 2; case 2: - if (part[1] < '0' || part[1] > '9') + if (!char.IsAsciiDigit(part[1])) { break; } goto case 1; case 1: - if (part[0] < '0' || part[0] > '9') + if (!char.IsAsciiDigit(part[0])) { break; } diff --git a/src/libraries/System.Net.WebClient/src/System/Net/WebClient.cs b/src/libraries/System.Net.WebClient/src/System/Net/WebClient.cs index 24c700e76658e8..24584ac2a2c1a1 100644 --- a/src/libraries/System.Net.WebClient/src/System/Net/WebClient.cs +++ b/src/libraries/System.Net.WebClient/src/System/Net/WebClient.cs @@ -1252,7 +1252,7 @@ private static byte[] UrlEncodeBytesToBytesInternal(byte[] bytes, int offset, in private static bool IsSafe(char ch) { - if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9') + if (char.IsAsciiLetterOrDigit(ch)) { return true; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Char.cs b/src/libraries/System.Private.CoreLib/src/System/Char.cs index aa3a244fbc4866..59c73b82dcb520 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Char.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Char.cs @@ -225,6 +225,76 @@ public static bool TryParse([NotNullWhen(true)] string? s, out char result) // // Static Methods // + + /// Indicates whether a character is categorized as an ASCII letter. + /// The character to evaluate. + /// true if is an ASCII letter; otherwise, false. + /// + /// This determines whether the character is in the range 'A' through 'Z', inclusive, + /// or 'a' through 'z', inclusive. + /// + public static bool IsAsciiLetter(char c) => (uint)((c | 0x20) - 'a') <= 'z' - 'a'; + + /// Indicates whether a character is categorized as a lowercase ASCII letter. + /// The character to evaluate. + /// true if is a lowercase ASCII letter; otherwise, false. + /// + /// This determines whether the character is in the range 'a' through 'z', inclusive. + /// + public static bool IsAsciiLetterLower(char c) => IsBetween(c, 'a', 'z'); + + /// Indicates whether a character is categorized as an uppercase ASCII letter. + /// The character to evaluate. + /// true if is a lowercase ASCII letter; otherwise, false. + /// + /// This determines whether the character is in the range 'a' through 'z', inclusive. + /// + public static bool IsAsciiLetterUpper(char c) => IsBetween(c, 'A', 'Z'); + + /// Indicates whether a character is categorized as an ASCII digit. + /// The character to evaluate. + /// true if is an ASCII digit; otherwise, false. + /// + /// This determines whether the character is in the range '0' through '9', inclusive. + /// + public static bool IsAsciiDigit(char c) => IsBetween(c, '0', '9'); + + /// Indicates whether a character is categorized as an ASCII letter or digit. + /// The character to evaluate. + /// true if is an ASCII letter or digit; otherwise, false. + /// + /// This determines whether the character is in the range 'A' through 'Z', inclusive, + /// 'a' through 'z', inclusive, or '0' through '9', inclusive. + /// + public static bool IsAsciiLetterOrDigit(char c) => IsAsciiLetter(c) | IsBetween(c, '0', '9'); + + /// Indicates whether a character is categorized as an ASCII hexademical digit. + /// The character to evaluate. + /// true if is a hexademical digit; otherwise, false. + /// + /// This determines whether the character is in the range '0' through '9', inclusive, + /// 'A' through 'F', inclusive, or 'a' through 'f', inclusive. + /// + public static bool IsAsciiHexDigit(char c) => HexConverter.IsHexChar(c); + + /// Indicates whether a character is categorized as an ASCII upper-case hexademical digit. + /// The character to evaluate. + /// true if is a hexademical digit; otherwise, false. + /// + /// This determines whether the character is in the range '0' through '9', inclusive, + /// or 'A' through 'F', inclusive. + /// + public static bool IsAsciiHexDigitUpper(char c) => HexConverter.IsHexUpperChar(c); + + /// Indicates whether a character is categorized as an ASCII lower-case hexademical digit. + /// The character to evaluate. + /// true if is a lower-case hexademical digit; otherwise, false. + /// + /// This determines whether the character is in the range '0' through '9', inclusive, + /// or 'a' through 'f', inclusive. + /// + public static bool IsAsciiHexDigitLower(char c) => HexConverter.IsHexLowerChar(c); + /*=================================IsDigit====================================== **A wrapper for char. Returns a boolean indicating whether ** **character c is considered to be a digit. ** @@ -234,21 +304,33 @@ public static bool IsDigit(char c) { if (IsLatin1(c)) { - return IsInRange(c, '0', '9'); + return IsBetween(c, '0', '9'); } return CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.DecimalDigitNumber; } - internal static bool IsInRange(char c, char min, char max) => (uint)(c - min) <= (uint)(max - min); + /// Indicates whether a character is within the specified inclusive range. + /// The character to evaluate. + /// The lower bound, inclusive. + /// The upper bound, inclusive. + /// true if is within the specified range; otherwise, false. + /// + /// The method does not validate that is greater than or equal + /// to . If is less than + /// , the behavior is undefined. + /// + public static bool IsBetween(char c, char minInclusive, char maxInclusive) => + (uint)(c - minInclusive) <= (uint)(maxInclusive - minInclusive); - private static bool IsInRange(UnicodeCategory c, UnicodeCategory min, UnicodeCategory max) => (uint)(c - min) <= (uint)(max - min); + private static bool IsBetween(UnicodeCategory c, UnicodeCategory min, UnicodeCategory max) => + (uint)(c - min) <= (uint)(max - min); /*=================================CheckLetter===================================== ** Check if the specified UnicodeCategory belongs to the letter categories. ==============================================================================*/ internal static bool CheckLetter(UnicodeCategory uc) { - return IsInRange(uc, UnicodeCategory.UppercaseLetter, UnicodeCategory.OtherLetter); + return IsBetween(uc, UnicodeCategory.UppercaseLetter, UnicodeCategory.OtherLetter); } /*=================================IsLetter===================================== @@ -318,7 +400,7 @@ public static bool IsLower(char c) internal static bool CheckPunctuation(UnicodeCategory uc) { - return IsInRange(uc, UnicodeCategory.ConnectorPunctuation, UnicodeCategory.OtherPunctuation); + return IsBetween(uc, UnicodeCategory.ConnectorPunctuation, UnicodeCategory.OtherPunctuation); } /*================================IsPunctuation================================= @@ -532,7 +614,7 @@ public static bool IsDigit(string s, int index) char c = s[index]; if (IsLatin1(c)) { - return IsInRange(c, '0', '9'); + return IsBetween(c, '0', '9'); } return CharUnicodeInfo.GetUnicodeCategoryInternal(s, index) == UnicodeCategory.DecimalDigitNumber; @@ -606,7 +688,7 @@ public static bool IsLower(string s, int index) internal static bool CheckNumber(UnicodeCategory uc) { - return IsInRange(uc, UnicodeCategory.DecimalDigitNumber, UnicodeCategory.OtherNumber); + return IsBetween(uc, UnicodeCategory.DecimalDigitNumber, UnicodeCategory.OtherNumber); } public static bool IsNumber(char c) @@ -615,7 +697,7 @@ public static bool IsNumber(char c) { if (IsAscii(c)) { - return IsInRange(c, '0', '9'); + return IsBetween(c, '0', '9'); } return CheckNumber(GetLatin1UnicodeCategory(c)); } @@ -638,7 +720,7 @@ public static bool IsNumber(string s, int index) { if (IsAscii(c)) { - return IsInRange(c, '0', '9'); + return IsBetween(c, '0', '9'); } return CheckNumber(GetLatin1UnicodeCategory(c)); @@ -681,7 +763,7 @@ public static bool IsPunctuation(string s, int index) internal static bool CheckSeparator(UnicodeCategory uc) { - return IsInRange(uc, UnicodeCategory.SpaceSeparator, UnicodeCategory.ParagraphSeparator); + return IsBetween(uc, UnicodeCategory.SpaceSeparator, UnicodeCategory.ParagraphSeparator); } private static bool IsSeparatorLatin1(char c) @@ -722,7 +804,7 @@ public static bool IsSeparator(string s, int index) public static bool IsSurrogate(char c) { - return IsInRange(c, CharUnicodeInfo.HIGH_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END); + return IsBetween(c, CharUnicodeInfo.HIGH_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END); } public static bool IsSurrogate(string s, int index) @@ -745,7 +827,7 @@ public static bool IsSurrogate(string s, int index) internal static bool CheckSymbol(UnicodeCategory uc) { - return IsInRange(uc, UnicodeCategory.MathSymbol, UnicodeCategory.OtherSymbol); + return IsBetween(uc, UnicodeCategory.MathSymbol, UnicodeCategory.OtherSymbol); } public static bool IsSymbol(char c) @@ -866,7 +948,7 @@ public static double GetNumericValue(string s, int index) ==============================================================================*/ public static bool IsHighSurrogate(char c) { - return IsInRange(c, CharUnicodeInfo.HIGH_SURROGATE_START, CharUnicodeInfo.HIGH_SURROGATE_END); + return IsBetween(c, CharUnicodeInfo.HIGH_SURROGATE_START, CharUnicodeInfo.HIGH_SURROGATE_END); } public static bool IsHighSurrogate(string s, int index) @@ -888,7 +970,7 @@ public static bool IsHighSurrogate(string s, int index) ==============================================================================*/ public static bool IsLowSurrogate(char c) { - return IsInRange(c, CharUnicodeInfo.LOW_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END); + return IsBetween(c, CharUnicodeInfo.LOW_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END); } public static bool IsLowSurrogate(string s, int index) diff --git a/src/libraries/System.Private.CoreLib/src/System/Enum.cs b/src/libraries/System.Private.CoreLib/src/System/Enum.cs index cf475b20de5b80..194f0b69068358 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Enum.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Enum.cs @@ -1008,7 +1008,7 @@ private static bool TryParseByName(RuntimeType enumType, ReadOnlySpan valu } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool StartsNumber(char c) => char.IsInRange(c, '0', '9') || c == '-' || c == '+'; + private static bool StartsNumber(char c) => char.IsAsciiDigit(c) || c == '-' || c == '+'; public static object ToObject(Type enumType, object value) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.Unix.cs index ecbf02022a3a27..086cdd4726baf3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.Unix.cs @@ -30,10 +30,9 @@ private static OperatingSystem GetOperatingSystem(string release) private static int FindAndParseNextNumber(string text, ref int pos) { // Move to the beginning of the number - for (; pos < text.Length; pos++) + for (; (uint)pos < (uint)text.Length; pos++) { - char c = text[pos]; - if ('0' <= c && c <= '9') + if (char.IsAsciiDigit(text[pos])) { break; } @@ -41,10 +40,10 @@ private static int FindAndParseNextNumber(string text, ref int pos) // Parse the number; int num = 0; - for (; pos < text.Length; pos++) + for (; (uint)pos < (uint)text.Length; pos++) { char c = text[pos]; - if ('0' > c || c > '9') + if (!char.IsAsciiDigit(c)) break; try diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs index 573b8c80d96ccd..445d82c548b976 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs @@ -154,9 +154,9 @@ private unsafe int IndexOfOrdinalIgnoreCaseHelper(ReadOnlySpan source, Rea } // uppercase both chars - notice that we need just one compare per char - if ((uint)(valueChar - 'a') <= ('z' - 'a')) + if (char.IsAsciiLetterLower(valueChar)) valueChar = (char)(valueChar - 0x20); - if ((uint)(targetChar - 'a') <= ('z' - 'a')) + if (char.IsAsciiLetterLower(targetChar)) targetChar = (char)(targetChar - 0x20); if (valueChar == targetChar) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs index 7505201d3576e5..09e3ed1dda3c26 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs @@ -431,7 +431,7 @@ private static bool IsValidCultureName(string subject, out int indexOfUnderscore { char c = subject[i]; - if ((uint)(c - 'A') <= ('Z' - 'A') || (uint)(c - 'a') <= ('z' - 'a') || (uint)(c - '0') <= ('9' - '0') || c == '\0') + if (char.IsAsciiLetterOrDigit(c) || c == '\0') { continue; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Nls.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Nls.cs index f4fe86a2ae956c..9614d2289cc7ad 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Nls.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Nls.cs @@ -315,7 +315,7 @@ private static int[] ConvertWin32GroupString(string win32Str) { // Note that this # shouldn't ever be zero, 'cause 0 is only at end // But we'll test because its registry that could be anything - if (win32Str[i] < '1' || win32Str[i] > '9') + if (!char.IsBetween(win32Str[i], '1', '9')) return new int[] { 3 }; values[j] = (int)(win32Str[i] - '0'); diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs index 8067feb448b6d1..dd5e58becae9f7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs @@ -740,7 +740,7 @@ private static string NormalizeCultureName(string name, out bool isNeutralName) while (i < name.Length && name[i] != '-' && name[i] != '_') { - if (name[i] >= 'A' && name[i] <= 'Z') + if (char.IsAsciiLetterUpper(name[i])) { // lowercase characters before '-' normalizedName[i] = (char)(((int)name[i]) + 0x20); @@ -761,7 +761,7 @@ private static string NormalizeCultureName(string name, out bool isNeutralName) while (i < name.Length) { - if (name[i] >= 'a' && name[i] <= 'z') + if (char.IsAsciiLetterLower(name[i])) { normalizedName[i] = (char)(((int)name[i]) - 0x20); changed = true; diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfoScanner.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfoScanner.cs index e459993ae3da32..463a5da4aadb84 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfoScanner.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfoScanner.cs @@ -655,30 +655,29 @@ private static bool ArrayElementsHaveSpace(string[] array) //////////////////////////////////////////////////////////////////////////// private static bool ArrayElementsBeginWithDigit(string[] array) { - for (int i = 0; i < array.Length; i++) + foreach (string s in array) { // it is faster to check for space character manually instead of calling IndexOf // so we don't have to go to native code side. - if (array[i].Length > 0 && - array[i][0] >= '0' && array[i][0] <= '9') + if (s.Length != 0 && char.IsAsciiDigit(s[0])) { int index = 1; - while (index < array[i].Length && array[i][index] >= '0' && array[i][index] <= '9') + while ((uint)index < (uint)s.Length && char.IsAsciiDigit(s[index])) { // Skip other digits. index++; } - if (index == array[i].Length) + if (index == s.Length) { return false; } - if (index == array[i].Length - 1) + if (index == s.Length - 1) { // Skip known CJK month suffix. // CJK uses month name like "1\x6708", since \x6708 is a known month suffix, // we don't need the UseDigitPrefixInTokens since it is slower. - switch (array[i][index]) + switch (s[index]) { case CJKMonthSuff: case KoreanMonthSuff: @@ -686,13 +685,13 @@ private static bool ArrayElementsBeginWithDigit(string[] array) } } - if (index == array[i].Length - 4) + if (index == s.Length - 4) { // Skip known CJK month suffix. // Starting with Windows 8, the CJK months for some cultures looks like: "1' \x6708'" // instead of just "1\x6708" - if (array[i][index] == '\'' && array[i][index + 1] == ' ' && - array[i][index + 2] == CJKMonthSuff && array[i][index + 3] == '\'') + if (s[index] == '\'' && s[index + 1] == ' ' && + s[index + 2] == CJKMonthSuff && s[index + 3] == '\'') { return false; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs index a74b448775c624..a67fa5cca0be58 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs @@ -454,8 +454,6 @@ private static bool GetTimeZoneName(ref __DTString str) return false; } - internal static bool IsDigit(char ch) => (uint)(ch - '0') <= 9; - /*=================================ParseFraction========================== **Action: Starting at the str.Index, which should be a decimal symbol. ** if the current character is a digit, parse the remaining @@ -473,8 +471,7 @@ private static bool ParseFraction(ref __DTString str, out double result) double decimalBase = 0.1; int digits = 0; char ch; - while (str.GetNext() - && IsDigit(ch = str.m_current)) + while (str.GetNext() && char.IsAsciiDigit(ch = str.m_current)) { result += (ch - '0') * decimalBase; decimalBase *= 0.1; @@ -5332,7 +5329,7 @@ internal void GetRegularToken(out TokenType tokenType, out int tokenValue, DateT } Start: - if (DateTimeParse.IsDigit(m_current)) + if (char.IsAsciiDigit(m_current)) { // This is a digit. tokenValue = m_current - '0'; @@ -5423,7 +5420,7 @@ internal TokenType GetSeparatorToken(DateTimeFormatInfo dtfi, out int indexBefor // Reach the end of the string. return TokenType.SEP_End; } - if (!DateTimeParse.IsDigit(m_current)) + if (!char.IsAsciiDigit(m_current)) { // Not a digit. Tokenize it. bool found = dtfi.Tokenize(TokenType.SeparatorTokenMask, out tokenType, out _, ref this); @@ -5629,7 +5626,7 @@ internal int GetRepeatCount() [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool GetNextDigit() => ++Index < Length && - DateTimeParse.IsDigit(Value[Index]); + char.IsAsciiDigit(Value[Index]); // // Get the current character. @@ -5646,7 +5643,7 @@ internal char GetChar() internal int GetDigit() { Debug.Assert(Index >= 0 && Index < Length, "Index >= 0 && Index < len"); - Debug.Assert(DateTimeParse.IsDigit(Value[Index]), "IsDigit(Value[Index])"); + Debug.Assert(char.IsAsciiDigit(Value[Index]), "IsDigit(Value[Index])"); return Value[Index] - '0'; } @@ -5776,26 +5773,17 @@ internal DTSubString GetSubString() { DTSubStringType currentType; char ch = Value[Index + sub.length]; - if (ch >= '0' && ch <= '9') - { - currentType = DTSubStringType.Number; - } - else - { - currentType = DTSubStringType.Other; - } + currentType = char.IsAsciiDigit(ch) ? DTSubStringType.Number : DTSubStringType.Other; if (sub.length == 0) { sub.type = currentType; } - else + else if (sub.type != currentType) { - if (sub.type != currentType) - { - break; - } + break; } + sub.length++; if (currentType == DTSubStringType.Number) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/IcuLocaleData.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/IcuLocaleData.cs index 370e4589ad4d78..83659149292f85 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/IcuLocaleData.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/IcuLocaleData.cs @@ -3818,7 +3818,7 @@ private static int SearchCultureName(string name) char ch = name[i]; Debug.Assert(ch <= 'z'); - lower_case[i] = ((uint)(ch - 'A') <= 'Z' - 'A') ? (byte)(ch | 0x20) : (byte)ch; + lower_case[i] = char.IsAsciiLetterUpper(ch) ? (byte)(ch | 0x20) : (byte)ch; } ReadOnlySpan lname = lower_case; diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/IdnMapping.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/IdnMapping.cs index c832591b094878..be74bb7a1dfdc2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/IdnMapping.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/IdnMapping.cs @@ -657,7 +657,7 @@ private static string PunycodeDecode(string ascii) throw new ArgumentException(SR.Argument_IdnBadPunycode, nameof(ascii)); // When appending make sure they get lower cased - output.Append((char)(ascii[copyAscii] >= 'A' && ascii[copyAscii] <= 'Z' ? ascii[copyAscii] - 'A' + 'a' : ascii[copyAscii])); + output.Append((char)(char.IsAsciiLetterUpper(ascii[copyAscii]) ? ascii[copyAscii] - 'A' + 'a' : ascii[copyAscii])); } } @@ -817,14 +817,14 @@ private static string PunycodeDecode(string ascii) private static int DecodeDigit(char cp) { - if (cp >= '0' && cp <= '9') + if (char.IsAsciiDigit(cp)) return cp - '0' + 26; // Two flavors for case differences - if (cp >= 'a' && cp <= 'z') + if (char.IsAsciiLetterLower(cp)) return cp - 'a'; - if (cp >= 'A' && cp <= 'Z') + if (char.IsAsciiLetterUpper(cp)) return cp - 'A'; // Expected 0-9, A-Z or a-z, everything else is illegal @@ -856,16 +856,12 @@ private static int Adapt(int delta, int numpoints, bool firsttime) private static char EncodeBasic(char bcp) { - if (HasUpperCaseFlag(bcp)) + if (char.IsAsciiLetterUpper(bcp)) bcp += (char)('a' - 'A'); return bcp; } - // Return whether a punycode code point is flagged as being upper case. - private static bool HasUpperCaseFlag(char punychar) => - punychar >= 'A' && punychar <= 'Z'; - /* EncodeDigit(d,flag) returns the basic code point whose value */ /* (when used for representing integers) is d, which needs to be in */ /* the range 0 to punycodeBase-1. The lowercase form is used unless flag is */ diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs index 2b982b0b0ed907..b31bb25c9e80ae 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs @@ -24,8 +24,7 @@ internal static int CompareStringIgnoreCase(ref char strA, int lengthA, ref char { // Ordinal equals or lowercase equals if the result ends up in the a-z range if (charA == charB || - ((charA | 0x20) == (charB | 0x20) && - (uint)((charA | 0x20) - 'a') <= (uint)('z' - 'a'))) + ((charA | 0x20) == (charB | 0x20) && char.IsAsciiLetter(charA))) { length--; charA = ref Unsafe.Add(ref charA, 1); @@ -37,11 +36,11 @@ internal static int CompareStringIgnoreCase(ref char strA, int lengthA, ref char int currentB = charB; // Uppercase both chars if needed - if ((uint)(charA - 'a') <= 'z' - 'a') + if (char.IsAsciiLetterLower(charA)) { currentA -= 0x20; } - if ((uint)(charB - 'a') <= 'z' - 'a') + if (char.IsAsciiLetterLower(charB)) { currentB -= 0x20; } @@ -256,7 +255,7 @@ internal static int IndexOfOrdinalIgnoreCase(ReadOnlySpan source, ReadOnly nint offset = 0; bool isLetter = false; - if ((uint)((valueChar | 0x20) - 'a') <= 'z' - 'a') + if (char.IsAsciiLetter(valueChar)) { valueCharU = (char)(valueChar & ~0x20); valueCharL = (char)(valueChar | 0x20); diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.cs index 3ce857988a217f..f7064b15ad6d66 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.cs @@ -431,7 +431,7 @@ internal static unsafe string ToLowerAsciiInvariant(string s) int i = 0; while (i < s.Length) { - if ((uint)(pSource[i] - 'A') <= (uint)('Z' - 'A')) + if (char.IsAsciiLetterUpper(pSource[i])) { break; } @@ -487,7 +487,7 @@ private static unsafe string ToUpperAsciiInvariant(string s) int i = 0; while (i < s.Length) { - if ((uint)(pSource[i] - 'a') <= (uint)('z' - 'a')) + if (char.IsAsciiLetterLower(pSource[i])) { break; } @@ -534,7 +534,7 @@ internal static void ToUpperAsciiInvariant(ReadOnlySpan source, Span [MethodImpl(MethodImplOptions.AggressiveInlining)] private static char ToLowerAsciiInvariant(char c) { - if (UnicodeUtility.IsInRangeInclusive(c, 'A', 'Z')) + if (char.IsAsciiLetterUpper(c)) { // on x86, extending BYTE -> DWORD is more efficient than WORD -> DWORD c = (char)(byte)(c | 0x20); @@ -591,7 +591,7 @@ public string ToUpper(string str) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static char ToUpperAsciiInvariant(char c) { - if (UnicodeUtility.IsInRangeInclusive(c, 'a', 'z')) + if (char.IsAsciiLetterLower(c)) { c = (char)(c & 0x5F); // = low 7 bits of ~0x20 } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs index ec5e39f76938d3..362d80323aced8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs @@ -228,7 +228,7 @@ internal TimeSpanToken GetNextToken() int length = 1; while (true) { - if (++_pos >= _value.Length || (uint)(_value[_pos] - '0') <= 9) + if (++_pos >= _value.Length || char.IsAsciiDigit(_value[_pos])) { break; } @@ -1421,7 +1421,7 @@ private static bool ParseExactDigits(ref TimeSpanTokenizer tokenizer, int minDig while (tokenLength < maxDigitLength) { char ch = tokenizer.NextChar(); - if (ch < '0' || ch > '9') + if (!char.IsAsciiDigit(ch)) { tokenizer.BackOne(); break; @@ -1483,7 +1483,10 @@ internal char NextNonDigit() while (i < _len) { char ch = _str[i]; - if (ch < '0' || ch > '9') return ch; + if (!char.IsAsciiDigit(ch)) + { + return ch; + } i++; } @@ -1567,7 +1570,7 @@ internal bool ParseInt(int max, out int i, ref TimeSpanResult result) { i = 0; int p = _pos; - while (_ch >= '0' && _ch <= '9') + while (char.IsAsciiDigit(_ch)) { if ((i & 0xF0000000) != 0) { @@ -1638,7 +1641,7 @@ internal bool ParseTime(out long time, ref TimeSpanResult result) { NextChar(); int f = (int)TimeSpan.TicksPerSecond; - while (f > 1 && _ch >= '0' && _ch <= '9') + while (f > 1 && char.IsAsciiDigit(_ch)) { f /= 10; time += (_ch - '0') * f; diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/DriveInfoInternal.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/DriveInfoInternal.Windows.cs index 52395356da93b6..6eda93b0052834 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/DriveInfoInternal.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/DriveInfoInternal.Windows.cs @@ -67,8 +67,7 @@ public static string NormalizeDriveName(string driveName) // Now verify that the drive letter could be a real drive name. // On Windows this means it's between A and Z, ignoring case. - char letter = driveName[0]; - if (!((letter >= 'A' && letter <= 'Z') || (letter >= 'a' && letter <= 'z'))) + if (!char.IsAsciiLetter(driveName[0])) { throw new ArgumentException(SR.Arg_MustBeDriveLetterOrRootDir, nameof(driveName)); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Net/WebUtility.cs b/src/libraries/System.Private.CoreLib/src/System/Net/WebUtility.cs index 4ff4af7bc4a634..398873d249a506 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Net/WebUtility.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Net/WebUtility.cs @@ -679,13 +679,9 @@ private static bool IsUrlSafeChar(char ch) 1 << ((int)'-' - 0x20) | // 0x2D 1 << ((int)'.' - 0x20); // 0x2E - unchecked - { - return ((uint)(code - 'a') <= (uint)('z' - 'a')) || - ((uint)(code - 'A') <= (uint)('Z' - 'A')) || - ((uint)(code - 0x20) <= (uint)('9' - 0x20) && ((1 << (code - 0x20)) & safeSpecialCharMask) != 0) || - (code == (int)'_'); - } + return char.IsAsciiLetter(ch) || + ((uint)(code - 0x20) <= (uint)('9' - 0x20) && ((1 << (code - 0x20)) & safeSpecialCharMask) != 0) || + (code == (int)'_'); } private static bool ValidateUrlEncodingParameters(byte[]? bytes, int offset, int count) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs index 09475bfce26643..728a680aefd640 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -1796,8 +1796,7 @@ internal static unsafe char ParseFormatSpecifier(ReadOnlySpan format, out // If the format begins with a symbol, see if it's a standard format // with or without a specified number of digits. c = format[0]; - if ((uint)(c - 'A') <= 'Z' - 'A' || - (uint)(c - 'a') <= 'z' - 'a') + if (char.IsAsciiLetter(c)) { // Fast path for sole symbol, e.g. "D" if (format.Length == 1) @@ -1832,7 +1831,7 @@ internal static unsafe char ParseFormatSpecifier(ReadOnlySpan format, out // digits. Further, for compat, we need to stop when we hit a null char. int n = 0; int i = 1; - while (i < format.Length && (((uint)format[i] - '0') < 10)) + while ((uint)i < (uint)format.Length && char.IsAsciiDigit(format[i])) { int temp = ((n * 10) + format[i++] - '0'); if (temp < n) @@ -1844,7 +1843,7 @@ internal static unsafe char ParseFormatSpecifier(ReadOnlySpan format, out // If we're at the end of the digits rather than having stopped because we hit something // other than a digit or overflowed, return the standard format info. - if (i == format.Length || format[i] == '\0') + if ((uint)i >= (uint)format.Length || format[i] == '\0') { digits = n; return c; diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs b/src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs index dd7829e188a609..b68fe37cef6f0e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs @@ -66,7 +66,7 @@ public void CheckConsistency() break; } - Debug.Assert((digit >= '0') && (digit <= '9'), "Unexpected character found in Number"); + Debug.Assert(char.IsAsciiDigit((char)digit), $"Unexpected character found in Number: {digit}"); } Debug.Assert(numDigits == DigitsCount, "Null terminator found in unexpected location in Number"); diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyName.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyName.cs index 084805b66ce482..b5034994b45134 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyName.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyName.cs @@ -385,7 +385,7 @@ internal static string EscapeCodeBase(string? codebase) // Means we don't reEncode '%' but check for the possible escaped sequence dest = EnsureDestinationSize(pStr, dest, i, c_EncodedCharsPerByte, c_MaxAsciiCharsReallocate * c_EncodedCharsPerByte, ref destPos, prevInputPos); - if (i + 2 < end && HexConverter.IsHexChar(pStr[i + 1]) && HexConverter.IsHexChar(pStr[i + 2])) + if (i + 2 < end && char.IsAsciiHexDigit(pStr[i + 1]) && char.IsAsciiHexDigit(pStr[i + 2])) { // leave it escaped dest[destPos++] = '%'; @@ -459,25 +459,13 @@ private static bool IsReservedUnreservedOrHash(char c) internal static bool IsUnreserved(char c) { - if (IsAsciiLetterOrDigit(c)) + if (char.IsAsciiLetterOrDigit(c)) { return true; } return RFC3986UnreservedMarks.Contains(c); } - // Only consider ASCII characters - internal static bool IsAsciiLetter(char character) - { - return (character >= 'a' && character <= 'z') || - (character >= 'A' && character <= 'Z'); - } - - internal static bool IsAsciiLetterOrDigit(char character) - { - return IsAsciiLetter(character) || (character >= '0' && character <= '9'); - } - internal const char c_DummyChar = (char)0xFFFF; // An Invalid Unicode character used as a dummy char passed into the parameter private const short c_MaxAsciiCharsReallocate = 40; private const short c_MaxUnicodeCharsReallocate = 40; diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyNameParser.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyNameParser.cs index 05cbc2af87b42e..e245714e12017e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyNameParser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyNameParser.cs @@ -269,14 +269,12 @@ private ProcessorArchitecture ParseProcessorArchitecture(string attributeValue) private byte ParseHexNybble(char c) { - if (c >= '0' && c <= '9') - return (byte)(c - '0'); - if (c >= 'a' && c <= 'f') - return (byte)(c - 'a' + 10); - if (c >= 'A' && c <= 'F') - return (byte)(c - 'A' + 10); - ThrowInvalidAssemblyName(); - return default; // unreachable + int value = HexConverter.FromChar(c); + if (value == 0xFF) + { + ThrowInvalidAssemblyName(); + } + return (byte)value; } // diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs index f2527902847d7a..2ef8aca4a795dc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs @@ -1525,7 +1525,7 @@ internal StringBuilder AppendFormatHelper(IFormatProvider? provider, string form pos++; // If reached end of text then error (Unexpected end of text) // or character is not a digit then error (Unexpected Character) - if (pos == len || (ch = format[pos]) < '0' || ch > '9') FormatError(); + if (pos == len || !char.IsAsciiDigit(ch = format[pos])) FormatError(); int index = 0; do { @@ -1539,7 +1539,7 @@ internal StringBuilder AppendFormatHelper(IFormatProvider? provider, string form ch = format[pos]; // so long as character is digit and value of the index is less than 1000000 ( index limit ) } - while (ch >= '0' && ch <= '9' && index < IndexLimit); + while (char.IsAsciiDigit(ch) && index < IndexLimit); // If value of index is not within the range of the arguments passed in then error (Index out of range) if (index >= args.Length) @@ -1587,7 +1587,7 @@ internal StringBuilder AppendFormatHelper(IFormatProvider? provider, string form } // If current character is not a digit then error (Unexpected character) - if (ch < '0' || ch > '9') + if (!char.IsAsciiDigit(ch)) { FormatError(); } @@ -1604,7 +1604,7 @@ internal StringBuilder AppendFormatHelper(IFormatProvider? provider, string form ch = format[pos]; // So long a current character is a digit and the value of width is less than 100000 ( width limit ) } - while (ch >= '0' && ch <= '9' && width < WidthLimit); + while (char.IsAsciiDigit(ch) && width < WidthLimit); // end of parsing Argument Alignment } diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/ValueStringBuilder.AppendFormat.cs b/src/libraries/System.Private.CoreLib/src/System/Text/ValueStringBuilder.AppendFormat.cs index 84f355c519744e..652e73086cbb80 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/ValueStringBuilder.AppendFormat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/ValueStringBuilder.AppendFormat.cs @@ -112,7 +112,7 @@ internal void AppendFormatHelper(IFormatProvider? provider, string format, Param pos++; // If reached end of text then error (Unexpected end of text) // or character is not a digit then error (Unexpected Character) - if (pos == len || (ch = format[pos]) < '0' || ch > '9') ThrowFormatError(); + if (pos == len || !char.IsAsciiDigit(ch = format[pos])) ThrowFormatError(); int index = 0; do { @@ -126,7 +126,7 @@ internal void AppendFormatHelper(IFormatProvider? provider, string format, Param ch = format[pos]; // so long as character is digit and value of the index is less than 1000000 ( index limit ) } - while (ch >= '0' && ch <= '9' && index < IndexLimit); + while (char.IsAsciiDigit(ch) && index < IndexLimit); // If value of index is not within the range of the arguments passed in then error (Index out of range) if (index >= args.Length) @@ -174,7 +174,7 @@ internal void AppendFormatHelper(IFormatProvider? provider, string format, Param } // If current character is not a digit then error (Unexpected character) - if (ch < '0' || ch > '9') + if (!char.IsAsciiDigit(ch)) { ThrowFormatError(); } @@ -191,7 +191,7 @@ internal void AppendFormatHelper(IFormatProvider? provider, string format, Param ch = format[pos]; // So long a current character is a digit and the value of width is less than 100000 ( width limit ) } - while (ch >= '0' && ch <= '9' && width < WidthLimit); + while (char.IsAsciiDigit(ch) && width < WidthLimit); // end of parsing Argument Alignment } diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.FullGlobalizationData.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.FullGlobalizationData.cs index e47060d7e6aa0c..eb29e849aeb45e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.FullGlobalizationData.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.FullGlobalizationData.cs @@ -78,7 +78,7 @@ private static unsafe bool TryConvertWindowsIdToIanaId(string windowsId, string? int i = 0; for (; i < region.Length && region[i] <= '\u007F'; i++) { - regionInAscii[i] = (uint)(region[i] - 'a') <= ('z' - 'a') ? (byte)((region[i] - 'a') + 'A') : (byte)region[i]; + regionInAscii[i] = char.IsAsciiLetterLower(region[i]) ? (byte)((region[i] - 'a') + 'A') : (byte)region[i]; } if (i >= region.Length) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.StringSerializer.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.StringSerializer.cs index 405df10d08f55b..d85d93f91a6df1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.StringSerializer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.StringSerializer.cs @@ -443,14 +443,13 @@ private int GetNextInt32Value() } // Check if we have baseUtcOffsetDelta in the serialized string and then deserialize it - if ((_serializedText[_currentTokenStartIndex] >= '0' && _serializedText[_currentTokenStartIndex] <= '9') || - _serializedText[_currentTokenStartIndex] == '-' || _serializedText[_currentTokenStartIndex] == '+') + if (char.IsAsciiDigit(_serializedText[_currentTokenStartIndex]) || _serializedText[_currentTokenStartIndex] is '-' or '+') { baseUtcOffsetDelta = GetNextTimeSpanValue(); } // Check if we have NoDaylightTransitions in the serialized string and then deserialize it - if (_serializedText[_currentTokenStartIndex] >= '0' && _serializedText[_currentTokenStartIndex] <= '1') + if (_serializedText[_currentTokenStartIndex] is '0' or '1') { noDaylightTransitions = GetNextInt32Value(); } diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs index 5853a0718b6fbd..e03c67beaa58da 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs @@ -62,7 +62,7 @@ private static int ParseGMTNumericZone(string name) break; } - if (c >= '0' && c <= '9') + if (char.IsAsciiDigit(c)) { hour = hour * 10 + c - '0'; } @@ -77,7 +77,7 @@ private static int ParseGMTNumericZone(string name) { char c = name [where]; - if (c >= '0' && c <= '9') + if (char.IsAsciiDigit(c)) { min = min * 10 + c - '0'; } diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs index e989e9c92bd455..99aec155a29421 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs @@ -899,7 +899,7 @@ private static void TZif_ParseJulianDay(ReadOnlySpan date, out int month, int index = 1; - if (index >= date.Length || ((uint)(date[index] - '0') > '9'-'0')) + if ((uint)index >= (uint)date.Length || !char.IsAsciiDigit(date[index])) { throw new InvalidTimeZoneException(SR.InvalidTimeZone_InvalidJulianDay); } @@ -908,9 +908,9 @@ private static void TZif_ParseJulianDay(ReadOnlySpan date, out int month, do { - julianDay = julianDay * 10 + (int) (date[index] - '0'); + julianDay = julianDay * 10 + (int)(date[index] - '0'); index++; - } while (index < date.Length && ((uint)(date[index] - '0') <= '9'-'0')); + } while ((uint)index < (uint)date.Length && char.IsAsciiDigit(date[index])); int[] days = GregorianCalendarHelper.DaysToMonth365; diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/DataContract.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/DataContract.cs index 94259b2b79d940..c3db619138ee32 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/DataContract.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/DataContract.cs @@ -1272,26 +1272,16 @@ internal static Type UnwrapNullableType(Type type) return type; } - private static bool IsAlpha(char ch) - { - return (ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z'); - } - - private static bool IsDigit(char ch) - { - return (ch >= '0' && ch <= '9'); - } - private static bool IsAsciiLocalName(string localName) { if (localName.Length == 0) return false; - if (!IsAlpha(localName[0])) + if (!char.IsAsciiLetter(localName[0])) return false; for (int i = 1; i < localName.Length; i++) { char ch = localName[i]; - if (!IsAlpha(ch) && !IsDigit(ch)) + if (!char.IsAsciiLetterOrDigit(ch)) return false; } return true; diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBaseReader.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBaseReader.cs index 1b9dffa71635e6..e48fbcc012eb17 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBaseReader.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBaseReader.cs @@ -2994,7 +2994,7 @@ private static bool TryGetShortPrefix(string s, out PrefixHandleType shortPrefix if (length == 1) { char ch = s[0]; - if (ch >= 'a' && ch <= 'z') + if (char.IsAsciiLetterLower(ch)) { shortPrefix = PrefixHandle.GetAlphaPrefix(ch - 'a'); return true; diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryWriter.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryWriter.cs index d9c6458c95061e..2abfda13f19fa5 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryWriter.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryWriter.cs @@ -152,7 +152,7 @@ public override void WriteStartElement(string? prefix, string localName) { char ch = prefix[0]; - if (prefix.Length == 1 && ch >= 'a' && ch <= 'z') + if (prefix.Length == 1 && char.IsAsciiLetterLower(ch)) { WritePrefixNode(XmlBinaryNodeType.PrefixElementA, ch - 'a'); WriteName(localName); @@ -189,7 +189,7 @@ public override void WriteStartElement(string? prefix, XmlDictionaryString local { char ch = prefix[0]; - if (prefix.Length == 1 && ch >= 'a' && ch <= 'z') + if (prefix.Length == 1 && char.IsAsciiLetterLower(ch)) { WritePrefixNode(XmlBinaryNodeType.PrefixDictionaryElementA, ch - 'a'); WriteDictionaryString(localName, key); @@ -243,7 +243,7 @@ public override void WriteStartAttribute(string prefix, string localName) else { char ch = prefix[0]; - if (prefix.Length == 1 && ch >= 'a' && ch <= 'z') + if (prefix.Length == 1 && char.IsAsciiLetterLower(ch)) { WritePrefixNode(XmlBinaryNodeType.PrefixAttributeA, ch - 'a'); WriteName(localName); @@ -276,7 +276,7 @@ public override void WriteStartAttribute(string prefix, XmlDictionaryString loca else { char ch = prefix[0]; - if (prefix.Length == 1 && ch >= 'a' && ch <= 'z') + if (prefix.Length == 1 && char.IsAsciiLetterLower(ch)) { WritePrefixNode(XmlBinaryNodeType.PrefixDictionaryAttributeA, ch - 'a'); WriteDictionaryString(localName, key); @@ -939,7 +939,7 @@ public override void WriteQualifiedName(string prefix, XmlDictionaryString local { char ch = prefix[0]; int key; - if (prefix.Length == 1 && (ch >= 'a' && ch <= 'z') && TryGetKey(localName, out key)) + if (prefix.Length == 1 && char.IsAsciiLetterLower(ch) && TryGetKey(localName, out key)) { WriteTextNode(XmlBinaryNodeType.QNameDictionaryText); WriteByte((byte)(ch - 'a')); diff --git a/src/libraries/System.Private.Uri/src/System/DomainNameHelper.cs b/src/libraries/System.Private.Uri/src/System/DomainNameHelper.cs index 45f2bdb53c5c4a..2304b40fa865d5 100644 --- a/src/libraries/System.Private.Uri/src/System/DomainNameHelper.cs +++ b/src/libraries/System.Private.Uri/src/System/DomainNameHelper.cs @@ -23,7 +23,7 @@ internal static string ParseCanonicalName(string str, int start, int end, ref bo for (int i = end - 1; i >= start; --i) { - if (str[i] >= 'A' && str[i] <= 'Z') + if (char.IsAsciiLetterUpper(str[i])) { res = str.Substring(start, end - start).ToLowerInvariant(); break; @@ -349,12 +349,12 @@ internal static bool TryGetUnicodeEquivalent(string hostname, ref ValueStringBui [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsASCIILetterOrDigit(char character, ref bool notCanonical) { - if ((uint)(character - 'a') <= 'z' - 'a' || (uint)(character - '0') <= '9' - '0') + if (char.IsAsciiLetterLower(character) || char.IsAsciiDigit(character)) { return true; } - if ((uint)(character - 'A') <= 'Z' - 'A') + if (char.IsAsciiLetterUpper(character)) { notCanonical = true; return true; @@ -370,12 +370,12 @@ private static bool IsASCIILetterOrDigit(char character, ref bool notCanonical) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsValidDomainLabelCharacter(char character, ref bool notCanonical) { - if ((uint)(character - 'a') <= 'z' - 'a' || (uint)(character - '0') <= '9' - '0' || character == '-' || character == '_') + if (char.IsAsciiLetterLower(character) || char.IsAsciiDigit(character) || character == '-' || character == '_') { return true; } - if ((uint)(character - 'A') <= 'Z' - 'A') + if (char.IsAsciiLetterUpper(character)) { notCanonical = true; return true; diff --git a/src/libraries/System.Private.Uri/src/System/IPv6AddressHelper.cs b/src/libraries/System.Private.Uri/src/System/IPv6AddressHelper.cs index 136f2f3d49c09d..b684f0507bc425 100644 --- a/src/libraries/System.Private.Uri/src/System/IPv6AddressHelper.cs +++ b/src/libraries/System.Private.Uri/src/System/IPv6AddressHelper.cs @@ -163,7 +163,7 @@ private static unsafe bool InternalIsValid(char* name, int start, ref int end, b int i; for (i = start; i < end; ++i) { - if (havePrefix ? (name[i] >= '0' && name[i] <= '9') : Uri.IsHexDigit(name[i])) + if (havePrefix ? char.IsAsciiDigit(name[i]) : char.IsAsciiHexDigit(name[i])) { ++sequenceLength; expectingNumber = false; diff --git a/src/libraries/System.Private.Uri/src/System/UncNameHelper.cs b/src/libraries/System.Private.Uri/src/System/UncNameHelper.cs index ce714f928683cf..54cec4103e4077 100644 --- a/src/libraries/System.Private.Uri/src/System/UncNameHelper.cs +++ b/src/libraries/System.Private.Uri/src/System/UncNameHelper.cs @@ -59,12 +59,15 @@ public static unsafe bool IsValid(char* name, int start, ref int returnedEnd, bo ++i; break; } + if (char.IsLetter(name[i]) || name[i] == '-' || name[i] == '_') { validShortName = true; } - else if (name[i] < '0' || name[i] > '9') + else if (!char.IsAsciiDigit(name[i])) + { return false; + } } if (!validShortName) @@ -93,7 +96,7 @@ public static unsafe bool IsValid(char* name, int start, ref int returnedEnd, bo if (!validShortName) return false; } - else if (char.IsLetter(name[i]) || (name[i] >= '0' && name[i] <= '9')) + else if (char.IsLetter(name[i]) || char.IsAsciiDigit(name[i])) { if (!validShortName) validShortName = true; diff --git a/src/libraries/System.Private.Uri/src/System/Uri.cs b/src/libraries/System.Private.Uri/src/System/Uri.cs index 481522023fd942..655da9b8330767 100644 --- a/src/libraries/System.Private.Uri/src/System/Uri.cs +++ b/src/libraries/System.Private.Uri/src/System/Uri.cs @@ -1472,8 +1472,8 @@ public static bool IsHexEncoding(string pattern, int index) return (pattern.Length - index) >= 3 && pattern[index] == '%' && - IsHexDigit(pattern[index + 1]) && - IsHexDigit(pattern[index + 2]); + char.IsAsciiHexDigit(pattern[index + 1]) && + char.IsAsciiHexDigit(pattern[index + 2]); } // @@ -1485,14 +1485,14 @@ public static bool IsHexEncoding(string pattern, int index) // public static bool CheckSchemeName([NotNullWhen(true)] string? schemeName) { - if (string.IsNullOrEmpty(schemeName) || !UriHelper.IsAsciiLetter(schemeName[0])) + if (string.IsNullOrEmpty(schemeName) || !char.IsAsciiLetter(schemeName[0])) { return false; } for (int i = schemeName.Length - 1; i > 0; --i) { - if (!(UriHelper.IsAsciiLetterOrDigit(schemeName[i]) + if (!(char.IsAsciiLetterOrDigit(schemeName[i]) || (schemeName[i] == '+') || (schemeName[i] == '-') || (schemeName[i] == '.'))) @@ -1522,7 +1522,7 @@ public static bool CheckSchemeName([NotNullWhen(true)] string? schemeName) // public static bool IsHexDigit(char character) { - return HexConverter.IsHexChar(character); + return char.IsAsciiHexDigit(character); } // @@ -1952,7 +1952,7 @@ private unsafe ParsingError PrivateParseMinimal() } // DOS-like path? if (i + 1 < length && ((c = pUriString[i + 1]) == ':' || c == '|') && - UriHelper.IsAsciiLetter(pUriString[i])) + char.IsAsciiLetter(pUriString[i])) { if (i + 2 >= length || ((c = pUriString[i + 2]) != '\\' && c != '/')) { @@ -3570,7 +3570,7 @@ private static unsafe int ParseSchemeCheckImplicitFile(char* uriString, int leng if ((c = uriString[idx + 1]) == ':' || c == '|') { //DOS-like path? - if (UriHelper.IsAsciiLetter(uriString[idx])) + if (char.IsAsciiLetter(uriString[idx])) { if ((c = uriString[idx + 2]) == '\\' || c == '/') { @@ -3782,7 +3782,7 @@ private static unsafe bool CheckKnownSchemes(long* lptr, int nChars, ref UriPars // private static unsafe ParsingError CheckSchemeSyntax(ReadOnlySpan span, ref UriParser? syntax) { - static char ToLowerCaseAscii(char c) => (uint)(c - 'A') <= 'Z' - 'A' ? (char)(c | 0x20) : c; + static char ToLowerCaseAscii(char c) => char.IsAsciiLetterUpper(c) ? (char)(c | 0x20) : c; if (span.Length == 0) { @@ -3792,11 +3792,11 @@ private static unsafe ParsingError CheckSchemeSyntax(ReadOnlySpan span, re // The first character must be an alpha. Validate that and store it as lower-case, as // all of the fast-path checks need that value. char firstLower = span[0]; - if ((uint)(firstLower - 'A') <= 'Z' - 'A') + if (char.IsAsciiLetterUpper(firstLower)) { firstLower = (char)(firstLower | 0x20); } - else if ((uint)(firstLower - 'a') > 'z' - 'a') + else if (!char.IsAsciiLetterLower(firstLower)) { return ParsingError.BadScheme; } @@ -3861,10 +3861,7 @@ private static unsafe ParsingError CheckSchemeSyntax(ReadOnlySpan span, re for (int i = 1; i < span.Length; i++) { char c = span[i]; - if ((uint)(c - 'a') > 'z' - 'a' && - (uint)(c - 'A') > 'Z' - 'A' && - (uint)(c - '0') > '9' - '0' && - c != '+' && c != '-' && c != '.') + if (!char.IsAsciiLetterOrDigit(c) && c != '+' && c != '-' && c != '.') { return ParsingError.BadScheme; } @@ -4002,7 +3999,7 @@ private unsafe int CheckAuthorityHelper(char* pString, int idx, int length, justNormalized = true; } } - else if (ch <= '9' && ch >= '0' && syntax.InFact(UriSyntaxFlags.AllowIPv4Host) && + else if (char.IsAsciiDigit(ch) && syntax.InFact(UriSyntaxFlags.AllowIPv4Host) && IPv4AddressHelper.IsValid(pString, start, ref end, false, StaticNotAny(flags, Flags.ImplicitFile), syntax.InFact(UriSyntaxFlags.V1_UnknownUri))) { flags |= Flags.IPv4HostType; diff --git a/src/libraries/System.Private.Uri/src/System/UriExt.cs b/src/libraries/System.Private.Uri/src/System/UriExt.cs index aaa93e2e7f2f1f..ff5f5776c541ca 100644 --- a/src/libraries/System.Private.Uri/src/System/UriExt.cs +++ b/src/libraries/System.Private.Uri/src/System/UriExt.cs @@ -731,7 +731,7 @@ private Uri(Flags flags, UriParser? uriParser, string uri) // Check on the DOS path in the relative Uri (a special case) if (relativeStr.Length >= 3 && (relativeStr[1] == ':' || relativeStr[1] == '|') - && UriHelper.IsAsciiLetter(relativeStr[0]) + && char.IsAsciiLetter(relativeStr[0]) && (relativeStr[2] == '\\' || relativeStr[2] == '/')) { if (baseUri.IsImplicitFile) diff --git a/src/libraries/System.Private.Uri/src/System/UriHelper.cs b/src/libraries/System.Private.Uri/src/System/UriHelper.cs index b7d4335b848ebe..e6c6d4d4ecb564 100644 --- a/src/libraries/System.Private.Uri/src/System/UriHelper.cs +++ b/src/libraries/System.Private.Uri/src/System/UriHelper.cs @@ -238,10 +238,10 @@ private static void EscapeStringToBuilder( if (tmpEnumerator.MoveNext()) { Rune r1 = tmpEnumerator.Current; - if (r1.IsAscii && IsHexDigit((char)r1.Value) && tmpEnumerator.MoveNext()) + if (r1.IsAscii && char.IsAsciiHexDigit((char)r1.Value) && tmpEnumerator.MoveNext()) { Rune r2 = tmpEnumerator.Current; - if (r2.IsAscii && IsHexDigit((char)r2.Value)) + if (r2.IsAscii && char.IsAsciiHexDigit((char)r2.Value)) { vsb.Append('%'); vsb.Append((char)r1.Value); @@ -570,15 +570,6 @@ internal static bool IsLWS(char ch) return (ch <= ' ') && (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t'); } - internal static bool IsAsciiLetter(char character) => - (((uint)character - 'A') & ~0x20) < 26; - - internal static bool IsAsciiLetterOrDigit(char character) => - ((((uint)character - 'A') & ~0x20) < 26) || - (((uint)character - '0') < 10); - - internal static bool IsHexDigit(char character) => HexConverter.IsHexChar(character); - // // Is this a Bidirectional control char.. These get stripped // diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs index 5173cc001bb1ee..96eac65a2d365f 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs @@ -7330,7 +7330,7 @@ private int ParseNumericCharRefInline(int startPos, bool expand, StringBuilder? { digitPos = pos; badDigitExceptionString = SR.Xml_BadDecimalEntity; - while (chars[pos] >= '0' && chars[pos] <= '9') + while (char.IsAsciiDigit(chars[pos])) { val = checked(val * 10 + chars[pos] - '0'); pos++; diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/Inference/Infer.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/Inference/Infer.cs index fa8790edd65905..c5a5a88d116302 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/Inference/Infer.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/Inference/Infer.cs @@ -1798,7 +1798,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) case 'E': goto EXPONENT; default: - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto DEC_PART; else return TF_string; @@ -1811,7 +1811,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) case 'E': goto EXPONENT; default: - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto DEC_PART; else return TF_string; @@ -1824,20 +1824,20 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) case '-': goto E1; default: - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto EXP_PART; else return TF_string; } E1: i++; if (i == s.Length) return TF_string; //".9999e+" was matched - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto EXP_PART; else return TF_string; //".999e+X was matched EXP_PART: i++; if (i == s.Length) return TF_float | TF_double | TF_string; //".9999e+99" was matched - if (s[i] >= '0' && s[i] <= '9') //".9999e+9 + if (char.IsAsciiDigit(s[i])) //".9999e+9 goto EXP_PART; else return TF_string; //".9999e+999X" was matched @@ -1853,7 +1853,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) case 'P': goto DURATION; default: - if (s[i] >= '0' && s[i] <= '9') //-9 + if (char.IsAsciiDigit(s[i])) //-9 goto NUMBER; else return TF_string; } @@ -1867,7 +1867,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) case 'P': goto DURATION; default: - if (s[i] >= '0' && s[i] <= '9') //"+9 + if (char.IsAsciiDigit(s[i])) //"+9 goto NUMBER; else return TF_string; } @@ -1879,7 +1879,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) case 'T': goto D7; default: - if (s[i] >= '0' && s[i] <= '9') //"P9" + if (char.IsAsciiDigit(s[i])) //"P9" goto D1; else return TF_string; } @@ -1894,7 +1894,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) case 'D': goto D6; default: - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto D1; else return TF_string; @@ -1911,7 +1911,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) case 'T': goto D7; default: - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto D3; else return TF_string; @@ -1925,7 +1925,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) case 'D': goto D6; default: - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto D3; else return TF_string; @@ -1942,7 +1942,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) case 'T': goto D7; default: - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto D5; else return TF_string; @@ -1954,7 +1954,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) case 'D': goto D6; default: - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto D5; else return TF_string; @@ -1975,7 +1975,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) } D7: i++; if (i == s.Length) return TF_string; //"P999Y999M9999DT" was matched - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto D8; else return TF_string; @@ -1992,7 +1992,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) case 'S': goto D15; default: - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto D8; else return TF_string; @@ -2004,7 +2004,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) bNeedsRangeCheck = true; return TF_duration | TF_string; //"___T999H" was matched } - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto D10; else return TF_string; @@ -2019,7 +2019,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) case 'S': goto D15; default: - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto D10; else return TF_string; @@ -2031,7 +2031,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) bNeedsRangeCheck = true; return TF_duration | TF_string; //"___T999H999M" was matched } - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto D12; else return TF_string; @@ -2044,7 +2044,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) case 'S': goto D15; default: - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto D12; else return TF_string; @@ -2056,7 +2056,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) bNeedsRangeCheck = true; return TF_duration | TF_string; //"___T999H999M999." was matched } - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto D14; else return TF_string; @@ -2067,7 +2067,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) case 'S': goto D15; default: - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto D14; else return TF_string; @@ -2116,7 +2116,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) bNeedsRangeCheck = true; return TF_float | TF_double | TF_string; default: - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto N2; else return TF_string; @@ -2144,7 +2144,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) bNeedsRangeCheck = true; return TF_float | TF_double | TF_string; default: - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto N3; else return TF_string; @@ -2170,7 +2170,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) bNeedsRangeCheck = true; return TF_float | TF_double | TF_string; default: - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto N4; else return TF_string; @@ -2199,17 +2199,17 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) bNeedsRangeCheck = true; return TF_float | TF_double | TF_string; default: - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto N4; else return TF_string; } DATE: i++; if (i == s.Length) return TF_string; //"9999-" - if (s[i] < '0' || s[i] > '9') + if (!char.IsAsciiDigit(s[i])) return TF_string; i++; if (i == s.Length) return TF_string; //"9999-9" - if (s[i] < '0' || s[i] > '9') + if (!char.IsAsciiDigit(s[i])) return TF_string; i++; if (i == s.Length) @@ -2233,10 +2233,10 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) } DAY: i++; if (i == s.Length) return TF_string; //"9999-99-" - if (s[i] < '0' || s[i] > '9') + if (!char.IsAsciiDigit(s[i])) return TF_string; i++; if (i == s.Length) return TF_string; //"9999-99-9" - if (s[i] < '0' || s[i] > '9') + if (!char.IsAsciiDigit(s[i])) return TF_string; i++; if (i == s.Length) return DateTime(s, bDate, bTime); //"9999-99-99" switch (s[i]) @@ -2274,20 +2274,20 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) return TF_string; ZONE_SHIFT: i++; if (i == s.Length) return TF_string; - if (s[i] < '0' || s[i] > '9') + if (!char.IsAsciiDigit(s[i])) return TF_string; i++; if (i == s.Length) return TF_string; - if (s[i] < '0' || s[i] > '9') + if (!char.IsAsciiDigit(s[i])) return TF_string; i++; if (i == s.Length) return TF_string; if (s[i] != ':') return TF_string; ZONE_SHIFT_MINUTE: i++; if (i == s.Length) return TF_string; - if (s[i] < '0' || s[i] > '9') + if (!char.IsAsciiDigit(s[i])) return TF_string; i++; if (i == s.Length) return TF_string; - if (s[i] < '0' || s[i] > '9') + if (!char.IsAsciiDigit(s[i])) return TF_string; i++; if (i == s.Length) @@ -2305,29 +2305,29 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) else return TF_string; TIME: i++; if (i == s.Length) return TF_string; - if (s[i] < '0' || s[i] > '9') + if (!char.IsAsciiDigit(s[i])) return TF_string; i++; if (i == s.Length) return TF_string; - if (s[i] < '0' || s[i] > '9') + if (!char.IsAsciiDigit(s[i])) return TF_string; i++; if (i == s.Length) return TF_string; if (s[i] != ':') return TF_string; MINUTE: i++; if (i == s.Length) return TF_string; - if (s[i] < '0' || s[i] > '9') + if (!char.IsAsciiDigit(s[i])) return TF_string; i++; if (i == s.Length) return TF_string; - if (s[i] < '0' || s[i] > '9') + if (!char.IsAsciiDigit(s[i])) return TF_string; i++; if (i == s.Length) return TF_string; if (s[i] != ':') return TF_string; i++; if (i == s.Length) return TF_string; - if (s[i] < '0' || s[i] > '9') + if (!char.IsAsciiDigit(s[i])) return TF_string; i++; if (i == s.Length) return TF_string; - if (s[i] < '0' || s[i] > '9') + if (!char.IsAsciiDigit(s[i])) return TF_string; i++; if (i == s.Length) return DateTime(s, bDate, bTime); switch (s[i]) @@ -2345,7 +2345,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) } SECOND_FRACTION: i++; if (i == s.Length) return TF_string; - if (s[i] < '0' || s[i] > '9') + if (!char.IsAsciiDigit(s[i])) return TF_string; FRACT_DIGITS: i++; if (i == s.Length) return DateTime(s, bDate, bTime); @@ -2358,7 +2358,7 @@ internal static int InferSimpleType(string s, ref bool bNeedsRangeCheck) case '-': goto ZONE_SHIFT; default: - if (s[i] >= '0' && s[i] <= '9') + if (char.IsAsciiDigit(s[i])) goto FRACT_DIGITS; else return TF_string; diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs index 8b3851e888aa2b..a7c34f8b7d4e53 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs @@ -633,7 +633,7 @@ internal string ToString(DurationType durationType) result = 0; numDigits = 0; - while (offset < offsetEnd && s[offset] >= '0' && s[offset] <= '9') + while (offset < offsetEnd && char.IsAsciiDigit(s[offset])) { digit = s[offset] - '0'; @@ -647,7 +647,7 @@ internal string ToString(DurationType durationType) // Skip past any remaining digits numDigits = offset - offsetStart; - while (offset < offsetEnd && s[offset] >= '0' && s[offset] <= '9') + while (offset < offsetEnd && char.IsAsciiDigit(s[offset])) { offset++; } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/DecimalFormatter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/DecimalFormatter.cs index 24a74cfd38f769..b2bb03bbb5d712 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/DecimalFormatter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/DecimalFormatter.cs @@ -172,7 +172,7 @@ public DecimalFormatter(string formatPicture, DecimalFormat decimalFormat) continue; } // Escape literal digits with EscChar, double literal EscChar - if ('0' <= ch && ch <= '9' || ch == EscChar) + if (char.IsAsciiDigit(ch) || ch == EscChar) { if (decimalFormat.zeroDigit != '0') { @@ -256,7 +256,7 @@ public string Format(double value) for (int i = 0; i < result.Length; i++) { char ch = result[i]; - if ((uint)(ch - '0') <= 9) + if (char.IsAsciiDigit(ch)) { ch += (char)shift; } @@ -266,7 +266,7 @@ public string Format(double value) // of the fact that no extra EscChar could be inserted by value.ToString(). Debug.Assert(i + 1 < result.Length); ch = result[++i]; - Debug.Assert('0' <= ch && ch <= '9' || ch == EscChar); + Debug.Assert(char.IsAsciiDigit(ch) || ch == EscChar); } builder.Append(ch); } diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Convert.cs b/src/libraries/System.Runtime.Extensions/tests/System/Convert.cs index 66c32a13141be0..25f7b0f145aa78 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Convert.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Convert.cs @@ -492,11 +492,7 @@ private static string InsertSpaces(string text, int period) private static bool IsValidBase64Char(char c) { - return c >= 'A' && c <= 'Z' - || c >= 'a' && c <= 'z' - || c >= '0' && c <= '9' - || c == '+' - || c == '/'; + return char.IsAsciiLetterOrDigit(c) || c is '+' or '/'; } } } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs index c4fa725c6495e4..48fdce9235263a 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -865,15 +865,15 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig int i = 0; char ch = format[i]; - if (ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z') + if (char.IsAsciiLetter(ch)) { i++; int n = -1; - if (i < format.Length && format[i] >= '0' && format[i] <= '9') + if (i < format.Length && char.IsAsciiDigit(format[i])) { n = format[i++] - '0'; - while (i < format.Length && format[i] >= '0' && format[i] <= '9') + while (i < format.Length && char.IsAsciiDigit(format[i])) { int temp = n * 10 + (format[i++] - '0'); if (temp < n) diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs index 93fd87bcd22d01..7d7c45c55e9532 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs @@ -1596,7 +1596,7 @@ private static string Fix(string input, bool isHex) List out2 = new List(); for (int i = 0; i < output.Length; i++) { - if ((output[i] >= '0') & (output[i] <= '9')) + if (char.IsAsciiDigit(output[i])) { out2.Add(output[i]); } diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index 9b615391d9cfaf..0aa38f0824da16 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -694,7 +694,7 @@ private static string Fix(string input, bool isHex, bool failureNotExpected) List out2 = new List(); for (int i = 0; i < output.Length; i++) { - if ((output[i] >= '0') & (output[i] <= '9')) + if (char.IsAsciiDigit(output[i])) { out2.Add(output[i]); } diff --git a/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTestTypes/DataContract.cs b/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTestTypes/DataContract.cs index 1d3314ca1a25a1..7493bb07a979b9 100644 --- a/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTestTypes/DataContract.cs +++ b/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTestTypes/DataContract.cs @@ -656,26 +656,16 @@ public bool IsReference get { return GetIsReferenceValue(); } } - static bool IsAlpha(char ch) - { - return (ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z'); - } - - static bool IsDigit(char ch) - { - return (ch >= '0' && ch <= '9'); - } - static bool IsAsciiLocalName(string localName) { if (string.IsNullOrEmpty(localName) || localName.Length == 0) return false; - if (!IsAlpha(localName[0])) + if (!char.IsAsciiLetter(localName[0])) return false; for (int i = 1; i < localName.Length; i++) { char ch = localName[i]; - if (!IsAlpha(ch) && !IsDigit(ch)) + if (!char.IsAsciiLetterOrDigit(ch)) return false; } return true; diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index cb1861d7e74f40..c21459ea9a7717 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -853,6 +853,15 @@ public CannotUnloadAppDomainException(string? message, System.Exception? innerEx public static System.Globalization.UnicodeCategory GetUnicodeCategory(char c) { throw null; } public static System.Globalization.UnicodeCategory GetUnicodeCategory(string s, int index) { throw null; } public static bool IsAscii(char c) { throw null; } + public static bool IsAsciiDigit(char c) { throw null; } + public static bool IsAsciiHexDigit(char c) { throw null; } + public static bool IsAsciiHexDigitLower(char c) { throw null; } + public static bool IsAsciiHexDigitUpper(char c) { throw null; } + public static bool IsAsciiLetter(char c) { throw null; } + public static bool IsAsciiLetterLower(char c) { throw null; } + public static bool IsAsciiLetterOrDigit(char c) { throw null; } + public static bool IsAsciiLetterUpper(char c) { throw null; } + public static bool IsBetween(char c, char minInclusive, char maxInclusive) { throw null; } public static bool IsControl(char c) { throw null; } public static bool IsControl(string s, int index) { throw null; } public static bool IsDigit(char c) { throw null; } diff --git a/src/libraries/System.Runtime/tests/System/CharTests.cs b/src/libraries/System.Runtime/tests/System/CharTests.cs index 6c3183555ca4a2..e3583d200af190 100644 --- a/src/libraries/System.Runtime/tests/System/CharTests.cs +++ b/src/libraries/System.Runtime/tests/System/CharTests.cs @@ -207,6 +207,47 @@ public static void IsAscii_Char() Assert.False(char.IsAscii('\x0080')); Assert.False(char.IsAscii(char.MaxValue)); } + [Fact] + public static void IsAsciiDigit_Char() => + IsAsciiCategory( + char.IsAsciiDigit, + UnicodeCategory.DecimalDigitNumber); + + [Fact] + public static void IsAsciiLetter_Char() => + IsAsciiCategory( + char.IsAsciiLetter, + UnicodeCategory.UppercaseLetter, + UnicodeCategory.LowercaseLetter); + + [Fact] + public static void IsAsciiLetterLower_Char() => + IsAsciiCategory( + char.IsAsciiLetterLower, + UnicodeCategory.LowercaseLetter); + + [Fact] + public static void IsAsciiLetterUpper_Char() => + IsAsciiCategory( + char.IsAsciiLetterUpper, + UnicodeCategory.UppercaseLetter); + + [Fact] + public static void IsAsciiLetterOrDigit_Char() => + IsAsciiCategory( + char.IsAsciiLetterOrDigit, + UnicodeCategory.UppercaseLetter, + UnicodeCategory.LowercaseLetter, + UnicodeCategory.DecimalDigitNumber); + + private static void IsAsciiCategory(Func predicate, params UnicodeCategory[] categories) + { + foreach (char c in GetTestChars(categories)) + Assert.Equal(char.IsAscii(c), predicate(c)); + + foreach (char c in GetTestCharsNotInCategory(categories)) + Assert.False(predicate(c)); + } [Fact] public static void IsControl_Char() @@ -264,6 +305,58 @@ public static void IsDigit_String_Int_Invalid() AssertExtensions.Throws("index", () => char.IsDigit("abc", 3)); // Index >= string.Length } + [Fact] + public static void IsAsciiHexDigit_Char() => + IsAsciiHexDigit(new HashSet() { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f' }); + + [Fact] + public static void IsAsciiHexDigitLower_Char() => + IsAsciiHexDigit(new HashSet() { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }); + + [Fact] + public static void IsAsciiHexDigitUpper_Char() => + IsAsciiHexDigit(new HashSet() { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }); + + private static void IsAsciiHexDigit(HashSet hexDigits) + { + for (int i = 0; i < 128; i++) + { + Assert.Equal(hexDigits.Contains((char)i), char.IsAsciiHexDigit((char)i)); + } + foreach (char c in hexDigits) + { + Assert.False(char.IsAsciiHexDigit((char)(c | 0xFF00))); + } + } + + [Theory] + [InlineData('a', 'a', 'a', true)] + [InlineData((char)('a' - 1), 'a', 'a', false)] + [InlineData((char)('a' + 1), 'a', 'a', false)] + [InlineData('a', 'a', 'b', true)] + [InlineData('b', 'a', 'b', true)] + [InlineData((char)('a' - 1), 'a', 'b', false)] + [InlineData((char)('b' + 1), 'a', 'b', false)] + [InlineData('a', 'a', 'z', true)] + [InlineData('m', 'a', 'z', true)] + [InlineData('z', 'a', 'z', true)] + [InlineData((char)('a' - 1), 'a', 'z', false)] + [InlineData((char)('z' + 1), 'a', 'z', false)] + [InlineData('\0', '\0', '\uFFFF', true)] + [InlineData('\u1234', '\0', '\uFFFF', true)] + [InlineData('\uFFFF', '\0', '\uFFFF', true)] + [InlineData('\u1234', '\u0123', '\u2345', true)] + [InlineData('\u1234', '\u2345', '\uFFFF', false)] + [InlineData('\u1234', '\u0123', '\u1233', false)] + [InlineData('\u1234', '\u0123', '\u1234', true)] + [InlineData('\u1234', '\u1235', '\u1231', false)] + [InlineData('b', 'c', 'd', false)] + [InlineData('b', 'd', 'c', true)] + public static void IsBetween_Char(char c, char minInclusive, char maxExclusive, bool expected) + { + Assert.Equal(expected, char.IsBetween(c, minInclusive, maxExclusive)); + } + [Fact] public static void IsLetter_Char() { diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/PemEncoding.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/PemEncoding.cs index c6a55232bc0d35..5af1a376df1215 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/PemEncoding.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/PemEncoding.cs @@ -295,16 +295,14 @@ private static bool TryCountBase64( [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsBase64Character(char ch) { - uint c = (uint)ch; - return c == '+' || c == '/' || - c - '0' < 10 || c - 'A' < 26 || c - 'a' < 26; + return char.IsAsciiLetterOrDigit(ch) || ch is '+' or '/'; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsWhiteSpaceCharacter(char ch) { // Match white space characters from Convert.Base64 - return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'; + return ch is ' ' or '\t' or '\n' or '\r'; } /// diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/FindPal.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/FindPal.cs index 8636df4cb116bc..c5f357fe342993 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/FindPal.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/FindPal.cs @@ -247,7 +247,7 @@ private static BigInteger LaxParseDecimalBigInteger(string decimalString) foreach (char c in decimalString) { - if (c >= '0' && c <= '9') + if (char.IsAsciiDigit(c)) { accum = BigInteger.Multiply(accum, ten); accum = BigInteger.Add(accum, c - '0'); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslX509ChainProcessor.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslX509ChainProcessor.cs index 162eaa91e6ceaa..906f1edb8eb84f 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslX509ChainProcessor.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslX509ChainProcessor.cs @@ -863,9 +863,7 @@ private static ArraySegment Base64UrlEncode(ReadOnlySpan input) { char cur = base64[readIdx]; - if ((cur >= 'A' && cur <= 'Z') || - (cur >= 'a' && cur <= 'z') || - (cur >= '0' && cur <= '9')) + if (char.IsAsciiLetterOrDigit(cur)) { urlEncoded[writeIdx++] = cur; } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X500DistinguishedNameBuilder.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X500DistinguishedNameBuilder.cs index 9c434efab49138..d019c678dbb40f 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X500DistinguishedNameBuilder.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X500DistinguishedNameBuilder.cs @@ -223,7 +223,7 @@ public void AddCountryOrRegion(string twoLetterCode) // those will be prohibited, so "Length" should be fine for checking the length of // the string. // Input must be A-Z per ISO 3166. - if (twoLetterCode.Length != 2 || !IsAlpha(twoLetterCode[0]) || !IsAlpha(twoLetterCode[1])) + if (twoLetterCode.Length != 2 || !char.IsAsciiLetter(twoLetterCode[0]) || !char.IsAsciiLetter(twoLetterCode[1])) { throw new ArgumentException(SR.Argument_X500_InvalidCountryOrRegion, nameof(twoLetterCode)); } @@ -237,12 +237,6 @@ public void AddCountryOrRegion(string twoLetterCode) fixupTwoLetterCode, UniversalTagNumber.PrintableString, nameof(twoLetterCode)); - - static bool IsAlpha(char ch) - { - uint c = (uint)ch; - return c - 'A' < 26 || c - 'a' < 26; - } } /// diff --git a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs index 7547d186bf5c79..a245be688903a5 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs +++ b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs @@ -4315,15 +4315,45 @@ private static string MatchCharacterClass(RegexOptions options, string chExpr, s case RegexCharClass.NotSymbolClass: negate ^= charClass == RegexCharClass.NotSymbolClass; return $"{(negate ? "!" : "")}char.IsSymbol({chExpr})"; + + case RegexCharClass.AsciiLetterClass: + case RegexCharClass.NotAsciiLetterClass: + negate ^= charClass == RegexCharClass.NotAsciiLetterClass; + return $"{(negate ? "!" : "")}char.IsAsciiLetter({chExpr})"; + + case RegexCharClass.AsciiLetterOrDigitClass: + case RegexCharClass.NotAsciiLetterOrDigitClass: + negate ^= charClass == RegexCharClass.NotAsciiLetterOrDigitClass; + return $"{(negate ? "!" : "")}char.IsAsciiLetterOrDigit({chExpr})"; + + case RegexCharClass.HexDigitClass: + case RegexCharClass.NotHexDigitClass: + negate ^= charClass == RegexCharClass.NotHexDigitClass; + return $"{(negate ? "!" : "")}char.IsAsciiHexDigit({chExpr})"; + + case RegexCharClass.HexDigitLowerClass: + case RegexCharClass.NotHexDigitLowerClass: + negate ^= charClass == RegexCharClass.NotHexDigitLowerClass; + return $"{(negate ? "!" : "")}char.IsAsciiHexDigitLower({chExpr})"; + + case RegexCharClass.HexDigitUpperClass: + case RegexCharClass.NotHexDigitUpperClass: + negate ^= charClass == RegexCharClass.NotHexDigitUpperClass; + return $"{(negate ? "!" : "")}char.IsAsciiHexDigitUpper({chExpr})"; } // Next, handle simple sets of one range, e.g. [A-Z], [0-9], etc. This includes some built-in classes, like ECMADigitClass. if (RegexCharClass.TryGetSingleRange(charClass, out char lowInclusive, out char highInclusive)) { negate ^= RegexCharClass.IsNegated(charClass); - return lowInclusive == highInclusive ? - $"({chExpr} {(negate ? "!=" : "==")} {Literal(lowInclusive)})" : - $"(((uint){chExpr}) - {Literal(lowInclusive)} {(negate ? ">" : "<=")} (uint)({Literal(highInclusive)} - {Literal(lowInclusive)}))"; + return (lowInclusive, highInclusive) switch + { + ('0', '9') => $"{(negate ? "!" : "")}char.IsAsciiDigit({chExpr})", + ('a', 'z') => $"{(negate ? "!" : "")}char.IsAsciiLetterLower({chExpr})", + ('A', 'Z') => $"{(negate ? "!" : "")}char.IsAsciiLetterUpper({chExpr})", + _ when lowInclusive == highInclusive => $"({chExpr} {(negate ? "!=" : "==")} {Literal(lowInclusive)})", + _ => $"{(negate ? "!" : "")}char.IsBetween({chExpr}, {Literal(lowInclusive)}, {Literal(highInclusive)})", + }; } // Next, if the character class contains nothing but Unicode categories, we can call char.GetUnicodeCategory and diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs index e3744fc07dc76f..a9e35a5573e593 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs @@ -72,6 +72,16 @@ internal sealed partial class RegexCharClass internal const string NotSeparatorClass = "\0\0\u0005\0\ufff3\ufff2\ufff4\0"; // \P{Z} internal const string SymbolClass = "\0\0\u0006\0\u001b\u001c\u001a\u001d\0"; // \p{S} internal const string NotSymbolClass = "\0\0\u0006\0\uffe5\uffe4\uffe6\uffe3\0"; // \P{S} + internal const string AsciiLetterClass = "\0\u0004\0A[a{"; // [A-Za-z] + internal const string NotAsciiLetterClass = "\u0001\u0004\0A[a{"; // [^A-Za-z] + internal const string AsciiLetterOrDigitClass = "\0\u0006\00:A[a{"; // [A-Za-z0-9] + internal const string NotAsciiLetterOrDigitClass = "\u0001\u0006\00:A[a{"; // [^A-Za-z0-9] + internal const string HexDigitClass = "\0\u0006\00:AGag"; // [A-Fa-f0-9] + internal const string NotHexDigitClass = "\u0001\u0006\00:AGag"; // [^A-Fa-f0-9] + internal const string HexDigitUpperClass = "\0\u0004\00:AG"; // [A-F0-9] + internal const string NotHexDigitUpperClass = "\u0001\u0004\00:AG"; // [A-F0-9] + internal const string HexDigitLowerClass = "\0\u0004\00:ag"; // [a-f0-9] + internal const string NotHexDigitLowerClass = "\u0001\u0004\00:ag"; // [a-f0-9] private const string ECMASpaceRanges = "\u0009\u000E\u0020\u0021"; private const string NotECMASpaceRanges = "\0\u0009\u000E\u0020\u0021"; @@ -984,7 +994,7 @@ public static bool IsAscii(ReadOnlySpan s) // TODO https://github.com/dotn } /// Gets whether the specified character is an ASCII letter. - public static bool IsAsciiLetter(char c) => // TODO https://github.com/dotnet/runtime/issues/28230: Replace once Ascii is available + public static bool IsAsciiLetter(char c) => (uint)((c | 0x20) - 'a') <= 'z' - 'a'; /// Gets whether we can iterate through the set list pairs in order to completely enumerate the set's contents. diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCompiler.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCompiler.cs index 322c371de15c9f..c154d916b101e1 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCompiler.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCompiler.cs @@ -43,6 +43,11 @@ internal abstract class RegexCompiler private static readonly MethodInfo s_charIsWhiteSpaceMethod = typeof(char).GetMethod("IsWhiteSpace", new Type[] { typeof(char) })!; private static readonly MethodInfo s_charIsControlMethod = typeof(char).GetMethod("IsControl", new Type[] { typeof(char) })!; private static readonly MethodInfo s_charIsLetterMethod = typeof(char).GetMethod("IsLetter", new Type[] { typeof(char) })!; + private static readonly MethodInfo s_charIsAsciiLetterMethod = typeof(char).GetMethod("IsAsciiLetter", new Type[] { typeof(char) })!; + private static readonly MethodInfo s_charIsAsciiLetterOrDigitMethod = typeof(char).GetMethod("IsAsciiLetterOrDigit", new Type[] { typeof(char) })!; + private static readonly MethodInfo s_charIsAsciiHexDigitMethod = typeof(char).GetMethod("IsAsciiHexDigit", new Type[] { typeof(char) })!; + private static readonly MethodInfo s_charIsAsciiHexDigitLowerMethod = typeof(char).GetMethod("IsAsciiHexDigitLower", new Type[] { typeof(char) })!; + private static readonly MethodInfo s_charIsAsciiHexDigitUpperMethod = typeof(char).GetMethod("IsAsciiHexDigitUpper", new Type[] { typeof(char) })!; private static readonly MethodInfo s_charIsLetterOrDigitMethod = typeof(char).GetMethod("IsLetterOrDigit", new Type[] { typeof(char) })!; private static readonly MethodInfo s_charIsLowerMethod = typeof(char).GetMethod("IsLower", new Type[] { typeof(char) })!; private static readonly MethodInfo s_charIsUpperMethod = typeof(char).GetMethod("IsUpper", new Type[] { typeof(char) })!; @@ -71,6 +76,9 @@ internal abstract class RegexCompiler private static readonly MethodInfo s_stringGetCharsMethod = typeof(string).GetMethod("get_Chars", new Type[] { typeof(int) })!; private static readonly MethodInfo s_arrayResize = typeof(Array).GetMethod("Resize")!.MakeGenericMethod(typeof(int)); private static readonly MethodInfo s_mathMinIntInt = typeof(Math).GetMethod("Min", new Type[] { typeof(int), typeof(int) })!; + // Note: + // IsAsciiLetterLower, IsAsciiLetterUpper, IsAsciiDigit, and IsBetween aren't used here, as the IL generated for those + // single-range checks is as cheap as the method call, and there's no readability issue as with the source generator. /// The ILGenerator currently in use. protected ILGenerator? _ilg; @@ -5139,6 +5147,41 @@ private void EmitMatchCharacterClass(string charClass) Call(s_charIsSymbolMethod); NegateIf(charClass == RegexCharClass.NotSymbolClass); return; + + case RegexCharClass.AsciiLetterClass: + case RegexCharClass.NotAsciiLetterClass: + // char.IsAsciiLetter(ch) + Call(s_charIsAsciiLetterMethod); + NegateIf(charClass == RegexCharClass.NotAsciiLetterClass); + return; + + case RegexCharClass.AsciiLetterOrDigitClass: + case RegexCharClass.NotAsciiLetterOrDigitClass: + // char.IsAsciiLetterOrDigit(ch) + Call(s_charIsAsciiLetterOrDigitMethod); + NegateIf(charClass == RegexCharClass.NotAsciiLetterOrDigitClass); + return; + + case RegexCharClass.HexDigitClass: + case RegexCharClass.NotHexDigitClass: + // char.IsAsciiHexDigit(ch) + Call(s_charIsAsciiHexDigitMethod); + NegateIf(charClass == RegexCharClass.NotHexDigitClass); + return; + + case RegexCharClass.HexDigitLowerClass: + case RegexCharClass.NotHexDigitLowerClass: + // char.IsAsciiHexDigitLower(ch) + Call(s_charIsAsciiHexDigitLowerMethod); + NegateIf(charClass == RegexCharClass.NotHexDigitLowerClass); + return; + + case RegexCharClass.HexDigitUpperClass: + case RegexCharClass.NotHexDigitUpperClass: + // char.IsAsciiHexDigitUpper(ch) + Call(s_charIsAsciiHexDigitUpperMethod); + NegateIf(charClass == RegexCharClass.NotHexDigitUpperClass); + return; } // Next, handle simple sets of one range, e.g. [A-Z], [0-9], etc. This includes some built-in classes, like ECMADigitClass. @@ -5226,7 +5269,7 @@ private void EmitMatchCharacterClass(string charClass) return; } - // Next, handle simple sets of two ASCII letter ranges that are cased versions of each other, e.g. [A-Za-z]. + // Next, handle simple sets of two ASCII letter ranges that are cased versions of each other, e.g. [B-Db-d]. // This can be implemented as if it were a single range, with an additional bitwise operation. if (RegexCharClass.TryGetDoubleRange(charClass, out (char LowInclusive, char HighInclusive) rangeLower, out (char LowInclusive, char HighInclusive) rangeUpper) && RegexCharClass.IsAsciiLetter(rangeUpper.LowInclusive) && @@ -5254,7 +5297,7 @@ private void EmitMatchCharacterClass(string charClass) // Next, handle sets where the high - low + 1 range is <= 64. In that case, we can emit // a branchless lookup in a ulong that does not rely on loading any objects (e.g. the string-based - // lookup we use later). This nicely handles common sets like [0-9A-Fa-f], [0-9a-f], [A-Za-z], etc. + // lookup we use later). This nicely handles sets made up of a subset of ASCII letters, for example. // We skip this on 32-bit, as otherwise using 64-bit numbers in this manner is a deoptimization // when compared to the subsequent fallbacks. if (IntPtr.Size == 8 && analysis.OnlyRanges && (analysis.UpperBoundExclusiveIfOnlyRanges - analysis.LowerBoundInclusiveIfOnlyRanges) <= 64) diff --git a/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoderUtility.cs b/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoderUtility.cs index 4c3f6fe0f999e0..bf8a8d9bb7f7af 100644 --- a/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoderUtility.cs +++ b/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoderUtility.cs @@ -11,7 +11,7 @@ internal static class HttpEncoderUtility // Set of safe chars, from RFC 1738.4 minus '+' public static bool IsUrlSafeChar(char ch) { - if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')) + if (char.IsAsciiLetterOrDigit(ch)) { return true; } diff --git a/src/libraries/System.Web.HttpUtility/tests/HttpUtility/HttpUtilityTest.cs b/src/libraries/System.Web.HttpUtility/tests/HttpUtility/HttpUtilityTest.cs index 3b7798fe2e238b..4c3f9c9621b27e 100644 --- a/src/libraries/System.Web.HttpUtility/tests/HttpUtility/HttpUtilityTest.cs +++ b/src/libraries/System.Web.HttpUtility/tests/HttpUtility/HttpUtilityTest.cs @@ -563,7 +563,7 @@ public void UrlDecode_ByteArray(string decoded, string encoded) static bool IsUrlSafeChar(char c) { - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) + if (char.IsAsciiLetterOrDigit(c)) { return true; } From 89413a69edd2f540e5fbd514af7b0e67157da71e Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 17 May 2022 20:16:07 -0400 Subject: [PATCH 2/2] Fix hex tests --- .../System.Runtime/tests/System/CharTests.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System/CharTests.cs b/src/libraries/System.Runtime/tests/System/CharTests.cs index e3583d200af190..390038835abf10 100644 --- a/src/libraries/System.Runtime/tests/System/CharTests.cs +++ b/src/libraries/System.Runtime/tests/System/CharTests.cs @@ -307,22 +307,29 @@ public static void IsDigit_String_Int_Invalid() [Fact] public static void IsAsciiHexDigit_Char() => - IsAsciiHexDigit(new HashSet() { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f' }); + IsAsciiHexDigit( + new HashSet() { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f' }, + char.IsAsciiHexDigit); [Fact] public static void IsAsciiHexDigitLower_Char() => - IsAsciiHexDigit(new HashSet() { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }); + IsAsciiHexDigit( + new HashSet() { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }, + char.IsAsciiHexDigitLower); [Fact] public static void IsAsciiHexDigitUpper_Char() => - IsAsciiHexDigit(new HashSet() { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }); + IsAsciiHexDigit( + new HashSet() { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }, + char.IsAsciiHexDigitUpper); - private static void IsAsciiHexDigit(HashSet hexDigits) + private static void IsAsciiHexDigit(HashSet hexDigits, Func predicate) { for (int i = 0; i < 128; i++) { - Assert.Equal(hexDigits.Contains((char)i), char.IsAsciiHexDigit((char)i)); + Assert.Equal(hexDigits.Contains((char)i), predicate((char)i)); } + foreach (char c in hexDigits) { Assert.False(char.IsAsciiHexDigit((char)(c | 0xFF00)));