mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-17 12:45:47 +00:00
Add Binary Parsing Support & Refactor TryGetNumberValue & ScanNumberHelper (#7993)
Fixes #7557 * Adds support for binary parsing in format echoing hex: `0b11010110` * Works with all existing type suffixes and multipliers. * Supports arbitrary length parsing with `n` suffix using BigInteger; details below. * Adds `NumberFormat` enum to specify hex/binary/base 10 for the tokenizer, replacing old `bool hex`. * Adds `n` suffix for all numeric literals to support returning value as a `BigInteger` if requested. This bypasses the issue of large literals losing accuracy when they cast through `double`. * Adds tests for all new behaviours. --- ### Binary / Hex Parsing Implementation * Mimics old sign bit behaviour for int and long types. Sign bits accepted for 8 or 16-bit Hex parsing, and 8, 16, 32, 64 for binary. * i.e., `0xFFFFFFFF -eq ([int]-1)` and `0xFFFFFFFFFFFFFFFF -eq ([long]-1)`, but suffixing `u` creates `int.MaxValue` and `long.MaxValue`, respectively, instead. * Sign bits higher than this are accepted for bigint-suffixed numerals: * Hex: Bigint-suffixed hex treats the high bit of any literal with a length multiple of 8 as the sign bit * Binary: Bigint-suffixed binary accepts sign bits at 96 and 128 chars, and from there on every 8 characters. * Prefixing the literal with a 0 will bypass this and be treated as unsigned, e.g. `0b011111111` * Specifying an `u`nsigned suffix (or combination suffix that includes `u`) ignores sign bits, similar to how parsing a hex string using `[Convert]::ToUint32()` would do so. * Supports negating literals using `-` prefix. This can result in positive numbers due to sign bits being permitted, just like hex literals. --- ### Refactored numeric tokenizer parsing **New flow:** 1. Check for `real` (`.01`, `0.0`, or `0e0` syntaxes) 1. If the decimal suffix is present, TryParse directly into decimal. If the TryParse fails, TryGetNumberValue returns `false`. 2. TryParse as `Double`, and apply multiplier to value. If the TryParse fails, TryGetNumberValue returns `false`. 1. Check type suffixes and attempt to cast into appropriate type. This will return `false` if the value exceeds the specified type's bounds. 2. Default to parsing as `double` where no suffix has been applied. 2. Check number format. * If binary, manually parse into BigInteger using optimized helper function to directly construct the BigInteger bytes from the string. * If hex, TryParse into `BigInteger` using some special casing to retain original behaviours in int/long ranges. * If neither binary nor hex, TryParse normally as a `BigInteger`. 3. Apply multiplier value before attempting any casts to ensure type bounds can be appropriately checked without overflows. 4. Check type suffixes. * If a specific type suffix is used, check type bounds and attempt to parse into that type. * If the value exceeds the type's available values, the parse fails. Otherwise, a straight cast is performed. 5. If no suffix is used, the following types are bounds-checked, in order, resulting in the first successful test determining the type of the number. * `int` * `long` * `decimal` (base-10 literals only) * `double` (base-10 literals only) * ~~`BigInteger` for binary or hex literals.~~ If the value is outside `long` range (for hex and binary) or `double` range (for base 10), the parse will fail; higher values must be explicitly requested using the `n`/`N` BigInteger suffix. --- *This is a breaking change* as binary literals are now read as numbers instead of generic tokens which could potentially have been used as function / cmdlet names or file names. Notes: * Binary literal support was approved by the committee in #7557 * ~~The same issue is still under further discussion for underscore support in numeric literals and whether BigInteger parsing ought to be exposed to the user at all.~~ * ~~Supporting underscore literals is a further breaking change causing some generic tokens like `1_000_000` to be read as numerals instead.~~ Per @SteveL-MSFT's [comment](https://github.com/PowerShell/PowerShell/pull/7993#issuecomment-442651543) this proposal was rejected. * ~~Removing underscore support or preventing standard parsing from accepting BigInteger ranges is a relatively trivial matter. It is my personal opinion that there is no particular reason *not* to hand the user a BigInteger when they enter a sufficiently large literal, but I will defer to the PowerShell Committee's judgement on this.~~
This commit is contained in:
1 parent
4bd41e836c
commit
3f52adb0ae
4 files changed
+772
-269
No files matched your search
@@ -27,39 +27,64 @@ namespace System.Management.Automation.Language
|
||||
[Flags]
|
||||
internal enum CharTraits
|
||||
{
|
||||
/// <summary>
|
||||
/// No specific character traits.
|
||||
/// </summary>
|
||||
None = 0x0000,
|
||||
|
||||
// For identifiers, is the character a letter?
|
||||
/// <summary>
|
||||
/// For identifiers, the first character must be a letter or underscore.
|
||||
/// </summary>
|
||||
IdentifierStart = 0x0002,
|
||||
|
||||
// The character is a valid first character of a multiplier
|
||||
/// <summary>
|
||||
/// The character is a valid first character of a multiplier.
|
||||
/// </summary>
|
||||
MultiplierStart = 0x0004,
|
||||
|
||||
// The character is a valid type suffix for numeric literals
|
||||
/// <summary>
|
||||
/// The character is a valid type suffix for numeric literals.
|
||||
/// </summary>
|
||||
TypeSuffix = 0x0008,
|
||||
|
||||
// The character is a whitespace character
|
||||
/// <summary>
|
||||
/// The character is a whitespace character.
|
||||
/// </summary>
|
||||
Whitespace = 0x0010,
|
||||
|
||||
// The character terminates a line.
|
||||
/// <summary>
|
||||
/// The character terminates a line.
|
||||
/// </summary>
|
||||
Newline = 0x0020,
|
||||
|
||||
// The character is a hexadecimal digit.
|
||||
/// <summary>
|
||||
/// The character is a hexadecimal digit.
|
||||
/// </summary>
|
||||
HexDigit = 0x0040,
|
||||
|
||||
// The character is a decimal digit.
|
||||
/// <summary>
|
||||
/// The character is a decimal digit.
|
||||
/// </summary>
|
||||
Digit = 0x0080,
|
||||
|
||||
// The character is allowed as the first character in an unbraced variable name.
|
||||
/// <summary>
|
||||
/// The character is allowed as the first character in an unbraced variable name.
|
||||
/// </summary>
|
||||
VarNameFirst = 0x0100,
|
||||
|
||||
// The character is not part of the token being scanned.
|
||||
/// <summary>
|
||||
/// The character is not part of the token being scanned.
|
||||
/// </summary>
|
||||
ForceStartNewToken = 0x0200,
|
||||
|
||||
// The character is not part of the token being scanned, when the token is known to be part of an assembly name.
|
||||
/// <summary>
|
||||
/// The character is not part of the token being scanned, when the token is known to be part of an assembly name.
|
||||
/// </summary>
|
||||
ForceStartNewAssemblyNameSpecToken = 0x0400,
|
||||
|
||||
// The character is the first character of some operator (and hence is not part of a token that starts a number)
|
||||
/// <summary>
|
||||
/// The character is the first character of some operator (and hence is not part of a token that starts a number).
|
||||
/// </summary>
|
||||
ForceStartNewTokenAfterNumber = 0x0800,
|
||||
}
|
||||
|
||||
@@ -150,7 +175,7 @@ namespace System.Management.Automation.Language
|
||||
/* K */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.MultiplierStart,
|
||||
/* L */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.TypeSuffix,
|
||||
/* M */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.MultiplierStart,
|
||||
/* N */ CharTraits.IdentifierStart | CharTraits.VarNameFirst,
|
||||
/* N */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.TypeSuffix,
|
||||
/* O */ CharTraits.IdentifierStart | CharTraits.VarNameFirst,
|
||||
/* P */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.MultiplierStart,
|
||||
/* Q */ CharTraits.IdentifierStart | CharTraits.VarNameFirst,
|
||||
@@ -182,7 +207,7 @@ namespace System.Management.Automation.Language
|
||||
/* k */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.MultiplierStart,
|
||||
/* l */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.TypeSuffix,
|
||||
/* m */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.MultiplierStart,
|
||||
/* n */ CharTraits.IdentifierStart | CharTraits.VarNameFirst,
|
||||
/* n */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.TypeSuffix,
|
||||
/* o */ CharTraits.IdentifierStart | CharTraits.VarNameFirst,
|
||||
/* p */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.MultiplierStart,
|
||||
/* q */ CharTraits.IdentifierStart | CharTraits.VarNameFirst,
|
||||
@@ -298,18 +323,17 @@ namespace System.Management.Automation.Language
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return true if the character is a decimal digit.
|
||||
internal static bool IsDecimalDigit(this char c)
|
||||
{
|
||||
if (c < 128)
|
||||
{
|
||||
return (s_traits[c] & CharTraits.Digit) != 0;
|
||||
}
|
||||
// Returns true if the character is a decimal digit.
|
||||
internal static bool IsDecimalDigit(this char c) => (uint)(c - '0') <= 9;
|
||||
|
||||
return false;
|
||||
}
|
||||
// These decimal/binary checking methods are more performant than the alternatives due to requiring
|
||||
// less overall operations than a more readable check such as {(this char c) => c == 0 | c == 1},
|
||||
// especially in the case of IsDecimalDigit().
|
||||
|
||||
// Return true if the character is a type suffix character.
|
||||
// Returns true if the character is a binary digit.
|
||||
internal static bool IsBinaryDigit(this char c) => (uint)(c - '0') <= 1;
|
||||
|
||||
// Returns true if the character is a type suffix character.
|
||||
internal static bool IsTypeSuffix(this char c)
|
||||
{
|
||||
if (c < 128)
|
||||
|
||||
@@ -540,7 +540,33 @@ namespace System.Management.Automation.Language
|
||||
/// <summary>
|
||||
/// Indicates 'd' suffix for decimal (128-bit) real numbers.
|
||||
/// </summary>
|
||||
Decimal = 0x10
|
||||
Decimal = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates 'I' suffix for BigInteger (arbitrarily large integer) numerals.
|
||||
/// </summary>
|
||||
BigInteger = 0x20
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the format of a numeric literal.
|
||||
/// </summary>
|
||||
internal enum NumberFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates standard decimal literal, no necessary prefix.
|
||||
/// </summary>
|
||||
Decimal = 0x0,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates hexadecimal literal, with '0x' prefix.
|
||||
/// </summary>
|
||||
Hex = 0x1,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates binary literal, with '0b' prefix.
|
||||
/// </summary>
|
||||
Binary = 0x2
|
||||
}
|
||||
|
||||
//
|
||||
@@ -3352,6 +3378,17 @@ namespace System.Management.Automation.Language
|
||||
return countDigits;
|
||||
}
|
||||
|
||||
private void ScanBinaryDigits(StringBuilder sb)
|
||||
{
|
||||
char c = PeekChar();
|
||||
while (c.IsBinaryDigit())
|
||||
{
|
||||
SkipChar();
|
||||
sb.Append(c);
|
||||
c = PeekChar();
|
||||
}
|
||||
}
|
||||
|
||||
private void ScanExponent(StringBuilder sb, ref int signIndex, ref bool notNumber)
|
||||
{
|
||||
char c = PeekChar();
|
||||
@@ -3387,7 +3424,13 @@ namespace System.Management.Automation.Language
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetNumberValue(string strNum, bool hex, bool real, NumberSuffixFlags suffix, long multiplier, out object result)
|
||||
private static bool TryGetNumberValue(
|
||||
ReadOnlySpan<char> strNum,
|
||||
NumberFormat format,
|
||||
NumberSuffixFlags suffix,
|
||||
bool real,
|
||||
long multiplier,
|
||||
out object result)
|
||||
{
|
||||
checked
|
||||
{
|
||||
@@ -3396,22 +3439,23 @@ namespace System.Management.Automation.Language
|
||||
NumberStyles style = NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint |
|
||||
NumberStyles.AllowExponent;
|
||||
|
||||
// Decimal parser does not accept hex literals, and 'd' is a valid hex character, so will never be read as Decimal literal
|
||||
// e.g., 0x1d == 29
|
||||
if (suffix == NumberSuffixFlags.Decimal)
|
||||
{
|
||||
if (decimal.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out decimal d))
|
||||
{
|
||||
result = d * multiplier;
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (real)
|
||||
{
|
||||
// Decimal parser does not accept hex literals, and 'd' is a valid hex character, so will
|
||||
// never be read as Decimal literal
|
||||
// e.g., 0x1d == 29
|
||||
if (suffix == NumberSuffixFlags.Decimal)
|
||||
{
|
||||
if (decimal.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out decimal d))
|
||||
{
|
||||
result = d * multiplier;
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (double.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out double doubleValue))
|
||||
{
|
||||
// TryParse incorrectly return +0 when the result should be -0, so check for that case
|
||||
@@ -3420,204 +3464,272 @@ namespace System.Management.Automation.Language
|
||||
doubleValue = -0.0;
|
||||
}
|
||||
|
||||
doubleValue *= multiplier;
|
||||
switch (suffix)
|
||||
{
|
||||
case NumberSuffixFlags.None:
|
||||
result = doubleValue * multiplier;
|
||||
break;
|
||||
case NumberSuffixFlags.Long:
|
||||
result = (long)Convert.ChangeType(doubleValue, typeof(long), CultureInfo.InvariantCulture) * multiplier;
|
||||
break;
|
||||
case NumberSuffixFlags.Short:
|
||||
result = (short)((short)Convert.ChangeType(doubleValue, typeof(short), CultureInfo.InvariantCulture) * multiplier);
|
||||
break;
|
||||
result = doubleValue;
|
||||
return true;
|
||||
case NumberSuffixFlags.SignedByte:
|
||||
result = (sbyte)((sbyte)Convert.ChangeType(doubleValue, typeof(sbyte), CultureInfo.InvariantCulture) * multiplier);
|
||||
break;
|
||||
case NumberSuffixFlags.UnsignedLong:
|
||||
result = (ulong)Convert.ChangeType(doubleValue, typeof(ulong), CultureInfo.InvariantCulture) * (ulong)multiplier;
|
||||
break;
|
||||
case NumberSuffixFlags.UnsignedShort:
|
||||
result = (ushort)((ushort)Convert.ChangeType(doubleValue, typeof(ushort), CultureInfo.InvariantCulture) * multiplier);
|
||||
if (Utils.TryCast(doubleValue.AsBigInt(), out sbyte sb))
|
||||
{
|
||||
result = sb;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case NumberSuffixFlags.UnsignedByte:
|
||||
result = (byte)((byte)Convert.ChangeType(doubleValue, typeof(byte), CultureInfo.InvariantCulture) * multiplier);
|
||||
if (Utils.TryCast(doubleValue.AsBigInt(), out byte b))
|
||||
{
|
||||
result = b;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case NumberSuffixFlags.Short:
|
||||
if (Utils.TryCast(doubleValue.AsBigInt(), out short s))
|
||||
{
|
||||
result = s;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case NumberSuffixFlags.Long:
|
||||
if (Utils.TryCast(doubleValue.AsBigInt(), out long l))
|
||||
{
|
||||
result = l;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case NumberSuffixFlags.UnsignedShort:
|
||||
if (Utils.TryCast(doubleValue.AsBigInt(), out ushort us))
|
||||
{
|
||||
result = us;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case NumberSuffixFlags.Unsigned:
|
||||
ulong testresult = (ulong)Convert.ChangeType(doubleValue, typeof(ulong), CultureInfo.InvariantCulture) * (ulong)multiplier;
|
||||
if (testresult < uint.MaxValue)
|
||||
BigInteger testValue = doubleValue.AsBigInt();
|
||||
if (Utils.TryCast(testValue, out uint u))
|
||||
{
|
||||
result = (uint)testresult;
|
||||
result = u;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
else if (Utils.TryCast(testValue, out ulong ul))
|
||||
{
|
||||
result = testresult;
|
||||
result = ul;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
result = null;
|
||||
return false;
|
||||
case NumberSuffixFlags.UnsignedLong:
|
||||
if (Utils.TryCast(doubleValue.AsBigInt(), out ulong ulValue))
|
||||
{
|
||||
result = ulValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case NumberSuffixFlags.BigInteger:
|
||||
result = doubleValue.AsBigInt();
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
// Invalid NumberSuffixFlags combination, or outside bounds of specified type.
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// TryParse on (real) number fails.
|
||||
// TryParse for real numeric literal failed
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hex && !strNum[0].IsHexDigit())
|
||||
{
|
||||
if (strNum[0] == '-')
|
||||
{
|
||||
multiplier = -multiplier;
|
||||
}
|
||||
BigInteger bigValue;
|
||||
|
||||
strNum = strNum.Substring(1);
|
||||
switch (format)
|
||||
{
|
||||
case NumberFormat.Hex:
|
||||
if (!strNum[0].IsHexDigit())
|
||||
{
|
||||
if (strNum[0] == '-')
|
||||
{
|
||||
multiplier = -multiplier;
|
||||
}
|
||||
|
||||
// Remove leading char (expected: - or +)
|
||||
strNum = strNum.Slice(1);
|
||||
}
|
||||
|
||||
// If we're expecting a sign bit, remove the leading 0 added in ScanNumberHelper
|
||||
if (!suffix.HasFlag(NumberSuffixFlags.Unsigned) && ((strNum.Length - 1) & 7) == 0)
|
||||
{
|
||||
strNum = strNum.Slice(1);
|
||||
}
|
||||
|
||||
style = NumberStyles.AllowHexSpecifier;
|
||||
if (!BigInteger.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out bigValue))
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we have a hex literal denoting (u)int64, treat it as such, even if the value is low
|
||||
if (strNum.Length == 16 && (suffix == NumberSuffixFlags.None || suffix == NumberSuffixFlags.Unsigned))
|
||||
{
|
||||
suffix |= NumberSuffixFlags.Long;
|
||||
}
|
||||
|
||||
break;
|
||||
case NumberFormat.Binary:
|
||||
if (!strNum[0].IsBinaryDigit())
|
||||
{
|
||||
if (strNum[0] == '-')
|
||||
{
|
||||
multiplier = -multiplier;
|
||||
}
|
||||
|
||||
// Remove leading char (expected: - or +)
|
||||
strNum = strNum.Slice(1);
|
||||
}
|
||||
|
||||
bigValue = Utils.ParseBinary(strNum, suffix.HasFlag(NumberSuffixFlags.Unsigned));
|
||||
|
||||
// If we have a binary literal denoting (u)int64, treat it as such
|
||||
if (strNum.Length == 64 && (suffix == NumberSuffixFlags.None || suffix == NumberSuffixFlags.Unsigned))
|
||||
{
|
||||
suffix |= NumberSuffixFlags.Long;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
style = NumberStyles.AllowLeadingSign;
|
||||
if (!BigInteger.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out bigValue))
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
style = hex ? NumberStyles.AllowHexSpecifier : NumberStyles.AllowLeadingSign;
|
||||
// Apply multiplier before attempting casting to prevent overflow
|
||||
bigValue *= multiplier;
|
||||
|
||||
switch (suffix)
|
||||
{
|
||||
case NumberSuffixFlags.None:
|
||||
break;
|
||||
case NumberSuffixFlags.Long:
|
||||
if (long.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out long l))
|
||||
{
|
||||
result = l * multiplier;
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
case NumberSuffixFlags.Short:
|
||||
if (short.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out short s))
|
||||
{
|
||||
result = (short)(s * multiplier);
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
case NumberSuffixFlags.SignedByte:
|
||||
// Multiplier for hex-parsed values can be negative to permit - prefix for hex values
|
||||
if (Math.Abs(multiplier) == 1 && sbyte.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out sbyte sb))
|
||||
if (Utils.TryCast(bigValue, out sbyte sb))
|
||||
{
|
||||
result = (sbyte)(sb * multiplier);
|
||||
result = sb;
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
case NumberSuffixFlags.Unsigned:
|
||||
if (ulong.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out ulong u))
|
||||
{
|
||||
u *= (ulong)multiplier;
|
||||
|
||||
if (u <= uint.MaxValue)
|
||||
{
|
||||
result = (uint)u;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = u;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
case NumberSuffixFlags.UnsignedLong:
|
||||
if (ulong.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out ulong ul))
|
||||
{
|
||||
result = (ulong)(ul * (ulong)multiplier);
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
case NumberSuffixFlags.UnsignedShort:
|
||||
if (ushort.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out ushort us))
|
||||
{
|
||||
result = (ushort)(us * (ushort)multiplier);
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
break;
|
||||
case NumberSuffixFlags.UnsignedByte:
|
||||
// If multiplier is negative or greater than 1, we can assume it will fail since the
|
||||
// minimum multiplier is 1024 (already exceeds byte.MaxValue), and byte is unsigned
|
||||
if (multiplier == 1 && byte.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out byte b))
|
||||
if (Utils.TryCast(bigValue, out byte b))
|
||||
{
|
||||
result = b;
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
default:
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// From here on - the user hasn't specified the type, so we need to figure it out.
|
||||
BigInteger bigValue;
|
||||
TypeCode whichTryParseWorked;
|
||||
if (int.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out int intValue))
|
||||
{
|
||||
whichTryParseWorked = TypeCode.Int32;
|
||||
bigValue = intValue;
|
||||
}
|
||||
else if (long.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out long longValue))
|
||||
{
|
||||
whichTryParseWorked = TypeCode.Int64;
|
||||
bigValue = longValue;
|
||||
}
|
||||
else if (decimal.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out decimal decimalValue))
|
||||
{
|
||||
whichTryParseWorked = TypeCode.Decimal;
|
||||
bigValue = (BigInteger)decimalValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The result must be double if we get here.
|
||||
if (!hex)
|
||||
{
|
||||
if (double.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out double dbl))
|
||||
break;
|
||||
case NumberSuffixFlags.Short:
|
||||
if (Utils.TryCast(bigValue, out short s))
|
||||
{
|
||||
result = dbl * multiplier;
|
||||
result = s;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
break;
|
||||
case NumberSuffixFlags.Long:
|
||||
if (Utils.TryCast(bigValue, out long l))
|
||||
{
|
||||
result = l;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case NumberSuffixFlags.UnsignedShort:
|
||||
if (Utils.TryCast(bigValue, out ushort us))
|
||||
{
|
||||
result = us;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case NumberSuffixFlags.Unsigned:
|
||||
if (Utils.TryCast(bigValue, out uint u))
|
||||
{
|
||||
result = u;
|
||||
return true;
|
||||
}
|
||||
else if (Utils.TryCast(bigValue, out ulong ul))
|
||||
{
|
||||
result = ul;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case NumberSuffixFlags.UnsignedLong:
|
||||
if (Utils.TryCast(bigValue, out ulong ulValue))
|
||||
{
|
||||
result = ulValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case NumberSuffixFlags.Decimal:
|
||||
if (Utils.TryCast(bigValue, out decimal dm))
|
||||
{
|
||||
result = dm;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case NumberSuffixFlags.BigInteger:
|
||||
result = bigValue;
|
||||
return true;
|
||||
case NumberSuffixFlags.None:
|
||||
// Type not specified; fit value into narrowest signed type available, int32 minimum
|
||||
if (Utils.TryCast(bigValue, out int i))
|
||||
{
|
||||
result = i;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Utils.TryCast(bigValue, out long lValue))
|
||||
{
|
||||
result = lValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Result is too big for anything else; fallback to decimal or double
|
||||
if (format == NumberFormat.Decimal)
|
||||
{
|
||||
if (Utils.TryCast(bigValue, out decimal dmValue))
|
||||
{
|
||||
result = dmValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Utils.TryCast(bigValue, out double d))
|
||||
{
|
||||
result = d;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Hex or Binary value, too big for generic non-suffixed parsing
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bigValue *= multiplier;
|
||||
if (bigValue >= int.MinValue && bigValue <= int.MaxValue && whichTryParseWorked <= TypeCode.Int32)
|
||||
{
|
||||
result = (int)bigValue;
|
||||
}
|
||||
else if (bigValue >= long.MinValue && bigValue <= long.MaxValue && whichTryParseWorked <= TypeCode.Int64)
|
||||
{
|
||||
result = (long)bigValue;
|
||||
}
|
||||
else if (bigValue >= (BigInteger)decimal.MinValue && bigValue <= (BigInteger)decimal.MaxValue && whichTryParseWorked <= TypeCode.Decimal)
|
||||
{
|
||||
result = (decimal)bigValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = (double)bigValue;
|
||||
}
|
||||
|
||||
return true;
|
||||
// Value cannot be contained in type specified by suffix, or invalid suffix flags.
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -3630,14 +3742,12 @@ namespace System.Management.Automation.Language
|
||||
|
||||
private Token ScanNumber(char firstChar)
|
||||
{
|
||||
Diagnostics.Assert(firstChar == '.' || (firstChar >= '0' && firstChar <= '9')
|
||||
Diagnostics.Assert(
|
||||
firstChar == '.' || (firstChar >= '0' && firstChar <= '9')
|
||||
|| (AllowSignedNumbers && (firstChar == '+' || firstChar.IsDash())), "Number must start with '.', '-', or digit.");
|
||||
|
||||
bool hex, real;
|
||||
NumberSuffixFlags suffix;
|
||||
long multiplier;
|
||||
ReadOnlySpan<char> strNum = ScanNumberHelper(firstChar, out NumberFormat format, out NumberSuffixFlags suffix, out bool real, out long multiplier);
|
||||
|
||||
string strNum = ScanNumberHelper(firstChar, out hex, out real, out suffix, out multiplier);
|
||||
// the token is not a number. i.e. 77z.exe
|
||||
if (strNum == null)
|
||||
{
|
||||
@@ -3647,7 +3757,7 @@ namespace System.Management.Automation.Language
|
||||
}
|
||||
|
||||
object value;
|
||||
if (!TryGetNumberValue(strNum, hex, real, suffix, multiplier, out value))
|
||||
if (!TryGetNumberValue(strNum, format, suffix, real, multiplier, out value))
|
||||
{
|
||||
if (!InExpressionMode())
|
||||
{
|
||||
@@ -3665,21 +3775,24 @@ namespace System.Management.Automation.Language
|
||||
return NewNumberToken(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scans a numeric string to determine its characteristics.
|
||||
/// </summary>
|
||||
/// <param name="firstChar">The first character.</param>
|
||||
/// <param name="hex">Indicate if it's a hex number.</param>
|
||||
/// <param name="real">Indicate if it's a real number.</param>
|
||||
/// <param name="suffix">Indicate the type suffix.</param>
|
||||
/// <param name="format">Indicate if it's a hex, binary, or decimal number.</param>
|
||||
/// <param name="suffix">Indicate the format suffix.</param>
|
||||
/// <param name="real">Indicate if the number is real (non-integer).</param>
|
||||
/// <param name="multiplier">Indicate the specified multiplier.</param>
|
||||
/// <returns>
|
||||
/// return null if the token is not a number
|
||||
/// Return null if the token is not a number
|
||||
/// OR
|
||||
/// return the string format of the number
|
||||
/// Return the string format of the number.
|
||||
/// </returns>
|
||||
private string ScanNumberHelper(char firstChar, out bool hex, out bool real, out NumberSuffixFlags suffix, out long multiplier)
|
||||
private ReadOnlySpan<char> ScanNumberHelper(char firstChar, out NumberFormat format, out NumberSuffixFlags suffix, out bool real, out long multiplier)
|
||||
{
|
||||
hex = false;
|
||||
real = false;
|
||||
format = NumberFormat.Decimal;
|
||||
suffix = NumberSuffixFlags.None;
|
||||
real = false;
|
||||
multiplier = 1;
|
||||
|
||||
bool notNumber = false;
|
||||
@@ -3702,16 +3815,36 @@ namespace System.Management.Automation.Language
|
||||
else
|
||||
{
|
||||
c = PeekChar();
|
||||
if (firstChar == '0' && (c == 'x' || c == 'X'))
|
||||
bool isHexOrBinary = firstChar == '0' && (c == 'x' || c == 'X' || c == 'b' || c == 'B');
|
||||
|
||||
if (isHexOrBinary)
|
||||
{
|
||||
SkipChar();
|
||||
ScanHexDigits(sb);
|
||||
if (sb.Length == 0)
|
||||
{
|
||||
notNumber = true;
|
||||
}
|
||||
|
||||
hex = true;
|
||||
switch (c)
|
||||
{
|
||||
case 'x':
|
||||
case 'X':
|
||||
sb.Append('0'); // Prepend a 0 to the number before any numeric digits are added
|
||||
ScanHexDigits(sb);
|
||||
if (sb.Length == 0)
|
||||
{
|
||||
notNumber = true;
|
||||
}
|
||||
|
||||
format = NumberFormat.Hex;
|
||||
break;
|
||||
case 'b':
|
||||
case 'B':
|
||||
ScanBinaryDigits(sb);
|
||||
if (sb.Length == 0)
|
||||
{
|
||||
notNumber = true;
|
||||
}
|
||||
|
||||
format = NumberFormat.Binary;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3773,6 +3906,10 @@ namespace System.Management.Automation.Language
|
||||
case 'Y':
|
||||
suffix |= NumberSuffixFlags.SignedByte;
|
||||
break;
|
||||
case 'n':
|
||||
case 'N':
|
||||
suffix |= NumberSuffixFlags.BigInteger;
|
||||
break;
|
||||
default:
|
||||
notNumber = true;
|
||||
break;
|
||||
@@ -3783,30 +3920,32 @@ namespace System.Management.Automation.Language
|
||||
if (c.IsTypeSuffix())
|
||||
{
|
||||
SkipChar();
|
||||
if (suffix == NumberSuffixFlags.Unsigned)
|
||||
switch (suffix)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case 'l':
|
||||
case 'L':
|
||||
suffix |= NumberSuffixFlags.Long;
|
||||
break;
|
||||
case 's':
|
||||
case 'S':
|
||||
suffix |= NumberSuffixFlags.Short;
|
||||
break;
|
||||
case 'y':
|
||||
case 'Y':
|
||||
suffix |= NumberSuffixFlags.SignedByte;
|
||||
break;
|
||||
default:
|
||||
notNumber = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
notNumber = true;
|
||||
case NumberSuffixFlags.Unsigned:
|
||||
switch (c)
|
||||
{
|
||||
case 'l':
|
||||
case 'L':
|
||||
suffix |= NumberSuffixFlags.Long;
|
||||
break;
|
||||
case 's':
|
||||
case 'S':
|
||||
suffix |= NumberSuffixFlags.Short;
|
||||
break;
|
||||
case 'y':
|
||||
case 'Y':
|
||||
suffix |= NumberSuffixFlags.SignedByte;
|
||||
break;
|
||||
default:
|
||||
notNumber = true;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
notNumber = true;
|
||||
break;
|
||||
}
|
||||
|
||||
c = PeekChar();
|
||||
@@ -3817,11 +3956,29 @@ namespace System.Management.Automation.Language
|
||||
{
|
||||
SkipChar();
|
||||
|
||||
if (c == 'k' || c == 'K') { multiplier = 1024; }
|
||||
else if (c == 'm' || c == 'M') { multiplier = 1024 * 1024; }
|
||||
else if (c == 'g' || c == 'G') { multiplier = 1024 * 1024 * 1024; }
|
||||
else if (c == 't' || c == 'T') { multiplier = 1024L * 1024 * 1024 * 1024; }
|
||||
else if (c == 'p' || c == 'P') { multiplier = 1024L * 1024 * 1024 * 1024 * 1024; }
|
||||
switch (c)
|
||||
{
|
||||
case 'k':
|
||||
case 'K':
|
||||
multiplier = 1024;
|
||||
break;
|
||||
case 'm':
|
||||
case 'M':
|
||||
multiplier = 1024 * 1024;
|
||||
break;
|
||||
case 'g':
|
||||
case 'G':
|
||||
multiplier = 1024 * 1024 * 1024;
|
||||
break;
|
||||
case 't':
|
||||
case 'T':
|
||||
multiplier = 1024L * 1024 * 1024 * 1024;
|
||||
break;
|
||||
case 'p':
|
||||
case 'P':
|
||||
multiplier = 1024L * 1024 * 1024 * 1024 * 1024;
|
||||
break;
|
||||
}
|
||||
|
||||
char c1 = PeekChar();
|
||||
if (c1 == 'b' || c1 == 'B')
|
||||
@@ -3860,7 +4017,7 @@ namespace System.Management.Automation.Language
|
||||
sb[0] = '-';
|
||||
}
|
||||
|
||||
return GetStringAndRelease(sb);
|
||||
return GetStringAndRelease(sb).AsSpan();
|
||||
}
|
||||
|
||||
#endregion Numbers
|
||||
@@ -4677,12 +4834,8 @@ namespace System.Management.Automation.Language
|
||||
|
||||
if (InExpressionMode() && (char.IsDigit(c1) || c1 == '.'))
|
||||
{
|
||||
bool hex, real;
|
||||
NumberSuffixFlags suffix;
|
||||
long multiplier;
|
||||
|
||||
// check if the next token is actually a number
|
||||
string strNum = ScanNumberHelper(c, out hex, out real, out suffix, out multiplier);
|
||||
ReadOnlySpan<char> strNum = ScanNumberHelper(c, out NumberFormat format, out NumberSuffixFlags suffix, out bool real, out long multiplier);
|
||||
// rescan characters after the check
|
||||
_currentIndex = _tokenStart;
|
||||
c = GetChar();
|
||||
|
||||
Reference in new issue
Block a user