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:
Joel Sallow (/u/ta11ow) authored and Travis Plunk committed 2019-04-03 15:10:02 -07:00
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();