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)
2019-04-03 15:10:02 -07:00
committed by Travis Plunk
parent 4bd41e836c
commit 3f52adb0ae
4 changed files with 772 additions and 269 deletions
@@ -5,17 +5,18 @@ using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.ComponentModel;
using System.Management.Automation.Configuration;
using System.Management.Automation.Internal;
using System.Management.Automation.Language;
using System.Management.Automation.Runspaces;
using System.Management.Automation.Security;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -38,6 +39,229 @@ namespace System.Management.Automation
/// </summary>
internal static class Utils
{
/// <summary>
/// Converts a given double value to BigInteger via Math.Round().
/// </summary>
/// <param name="d">The value to convert.</param>
/// <returns>Returns a BigInteger value equivalent to the input value rounded to nearest integer.</returns>
internal static BigInteger AsBigInt(this double d) => new BigInteger(Math.Round(d));
internal static bool TryCast(BigInteger value, out byte b)
{
if (value < byte.MinValue || byte.MaxValue < value)
{
b = 0;
return false;
}
b = (byte)value;
return true;
}
internal static bool TryCast(BigInteger value, out sbyte sb)
{
if (value < sbyte.MinValue || sbyte.MaxValue < value)
{
sb = 0;
return false;
}
sb = (sbyte)value;
return true;
}
internal static bool TryCast(BigInteger value, out short s)
{
if (value < short.MinValue || short.MaxValue < value)
{
s = 0;
return false;
}
s = (short)value;
return true;
}
internal static bool TryCast(BigInteger value, out ushort us)
{
if (value < ushort.MinValue || ushort.MaxValue < value)
{
us = 0;
return false;
}
us = (ushort)value;
return true;
}
internal static bool TryCast(BigInteger value, out int i)
{
if (value < int.MinValue || int.MaxValue < value)
{
i = 0;
return false;
}
i = (int)value;
return true;
}
internal static bool TryCast(BigInteger value, out uint u)
{
if (value < uint.MinValue || uint.MaxValue < value)
{
u = 0;
return false;
}
u = (uint)value;
return true;
}
internal static bool TryCast(BigInteger value, out long l)
{
if (value < long.MinValue || long.MaxValue < value)
{
l = 0;
return false;
}
l = (long)value;
return true;
}
internal static bool TryCast(BigInteger value, out ulong ul)
{
if (value < ulong.MinValue || ulong.MaxValue < value)
{
ul = 0;
return false;
}
ul = (ulong)value;
return true;
}
internal static bool TryCast(BigInteger value, out decimal dm)
{
if (value < (BigInteger)decimal.MinValue || (BigInteger)decimal.MaxValue < value)
{
dm = 0;
return false;
}
dm = (decimal)value;
return true;
}
internal static bool TryCast(BigInteger value, out double db)
{
if (value < (BigInteger)double.MinValue || (BigInteger)double.MaxValue < value)
{
db = 0;
return false;
}
db = (double)value;
return true;
}
/// <summary>
/// Parses a given string or ReadOnlySpan&lt;char&gt; to calculate its value as a binary number.
/// Assumes input has already been sanitized and only contains zeroes (0) or ones (1).
/// </summary>
/// <param name="digits">Span or string of binary digits. Assumes all digits are either 1 or 0.</param>
/// <param name="unsigned">
/// Whether to treat the number as unsigned. When false, respects established conventions
/// with sign bits for certain input string lengths.
/// </param>
/// <returns>Returns the value of the binary string as a BigInteger.</returns>
internal static BigInteger ParseBinary(ReadOnlySpan<char> digits, bool unsigned)
{
if (!unsigned)
{
if (digits[0] == '0')
{
unsigned = true;
}
else
{
switch (digits.Length)
{
// Only accept sign bits at these lengths:
case 8: // byte
case 16: // short
case 32: // int
case 64: // long
case 96: // decimal
case int n when n >= 128: // BigInteger
break;
default:
// If we do not flag these as unsigned, bigint assumes a sign bit for any (8 * n) string length
unsigned = true;
break;
}
}
}
// Only use heap allocation for very large numbers
const int MaxStackAllocation = 512;
// Calculate number of 8-bit bytes needed to hold the input, rounded up to next whole number.
int outputByteCount = (digits.Length + 7) / 8;
Span<byte> outputBytes = outputByteCount <= MaxStackAllocation ? stackalloc byte[outputByteCount] : new byte[outputByteCount];
int outputByteIndex = outputBytes.Length - 1;
// We need to be prepared for any partial leading bytes, (e.g., 010|00000011|00101100), or cases
// where we only have less than 8 bits to work with from the beginning.
//
// Walk bytes right to left, stepping one whole byte at a time (if there are any whole bytes).
int byteWalker;
for (byteWalker = digits.Length - 1; byteWalker >= 7; byteWalker -= 8)
{
// Use bit shifts and binary-or to sum the values in each byte. These calculations will
// create values higher than a single byte, but the higher bits will be stripped out when cast
// to byte.
//
// The low bits are added in separately to allow us to strip the higher 'noise' bits before we
// sum the values using binary-or.
//
// Simplified representation of logic: (byte)( (7)|(6)|(5)|(4) ) | ( ( (3)|(2)|(1)|(0) ) & 0b1111 )
//
// N.B.: This code has been tested against a straight for loop iterating through the byte, and in no
// circumstance was it faster or more effective than this unrolled version.
outputBytes[outputByteIndex--] =
(byte)(
( (digits[byteWalker - 7] << 7)
| (digits[byteWalker - 6] << 6)
| (digits[byteWalker - 5] << 5)
| (digits[byteWalker - 4] << 4)
)
| (
( (digits[byteWalker - 3] << 3)
| (digits[byteWalker - 2] << 2)
| (digits[byteWalker - 1] << 1)
| (digits[byteWalker])
) & 0b1111
)
);
}
// With complete bytes parsed, byteWalker is either at the partial byte start index, or at -1
if (byteWalker >= 0)
{
int currentByteValue = 0;
for (int i = 0; i <= byteWalker; i++)
{
currentByteValue = (currentByteValue << 1) | (digits[i] - '0');
}
outputBytes[outputByteIndex] = (byte)currentByteValue;
}
return new BigInteger(outputBytes, isUnsigned: unsigned, isBigEndian: true);
}
// From System.Web.Util.HashCodeCombiner
internal static int CombineHashCodes(int h1, int h2)
{
@@ -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();
+123 -21
View File
@@ -678,9 +678,24 @@ foo``u{2195}abc
@{ Script = "0x12"; ExpectedValue = "18"; ExpectedType = [int] }
@{ Script = "-0x12"; ExpectedValue = "-18"; ExpectedType = [int] }
@{ Script = "0x80000000"; ExpectedValue = $([int32]::MinValue); ExpectedType = [int] }
@{ Script = "0xFFFFFFFF"; ExpectedValue = "-1"; ExpectedType = [int] }
@{ Script = "0x7fffffff"; ExpectedValue = $([int32]::MaxValue); ExpectedType = [int] }
@{ Script = "0x100000000"; ExpectedValue = [int64]0x100000000; ExpectedType = [long] }
@{ Script = "0xFF"; ExpectedValue = "255"; ExpectedType = [int] }
@{ Script = "0xFFFF"; ExpectedValue = "65535"; ExpectedType = [int] }
@{ Script = "0xFFFFFF"; ExpectedValue = "16777215"; ExpectedType = [int] }
@{ Script = "0xFFFFFFFF"; ExpectedValue = "-1"; ExpectedType = [int] }
@{ Script = "0xFFFFFFFFFF"; ExpectedValue = "1099511627775"; ExpectedType = [long] }
@{ Script = "0xFFFFFFFFFFFF"; ExpectedValue = "281474976710655"; ExpectedType = [long] }
@{ Script = "0xFFFFFFFFFFFFFF"; ExpectedValue = "72057594037927935"; ExpectedType = [long] }
@{ Script = "0xFFFFFFFFFFFFFFFF"; ExpectedValue = "-1"; ExpectedType = [long] }
#Binary
@{ Script = "0b0"; ExpectedValue = "0"; ExpectedType = [int] }
@{ Script = "0b10"; ExpectedValue = "2"; ExpectedType = [int] }
@{ Script = "-0b10"; ExpectedValue = "-2"; ExpectedType = [int] }
@{ Script = "0b11111111"; ExpectedValue = "-1"; ExpectedType = [int] }
@{ Script = "0b1111111111111111"; ExpectedValue = "-1"; ExpectedType = [int] }
@{ Script = "0b11111111111111111111111111111111"; ExpectedValue = "-1"; ExpectedType = [int] }
@{ Script = "0b1111111111111111111111111111111111111111111111111111111111111111"; ExpectedValue = "-1"; ExpectedType = [long] }
#Multipliers
@{ Script = "1kb"; ExpectedValue = "1024"; ExpectedType = [int] }
@{ Script = "1mb"; ExpectedValue = "1048576"; ExpectedType = [int] }
@@ -729,6 +744,11 @@ foo``u{2195}abc
@{ Script = "0x0y"; ExpectedValue = "0"; ExpectedType = [sbyte] }
@{ Script = "0x41y"; ExpectedValue = "65"; ExpectedType = [sbyte] }
@{ Script = "-0x41y"; ExpectedValue = "-65"; ExpectedType = [sbyte] }
#Binary
@{ Script = "0b0y"; ExpectedValue = "0"; ExpectedType = [sbyte] }
@{ Script = "0b10y"; ExpectedValue = "2"; ExpectedType = [sbyte] }
@{ Script = "-0b10y"; ExpectedValue = "-2"; ExpectedType = [sbyte] }
@{ Script = "0b11111111y"; ExpectedValue = "-1"; ExpectedType = [sbyte] }
#Short Integer notation
#Standard
@@ -750,6 +770,11 @@ foo``u{2195}abc
@{ Script = "0x0s"; ExpectedValue = "0"; ExpectedType = [short] }
@{ Script = "0x41s"; ExpectedValue = "65"; ExpectedType = [short] }
@{ Script = "-0x41s"; ExpectedValue = "-65"; ExpectedType = [short] }
#Binary
@{ Script = "0b0s"; ExpectedValue = "0"; ExpectedType = [short] }
@{ Script = "0b10s"; ExpectedValue = "2"; ExpectedType = [short] }
@{ Script = "-0b10s"; ExpectedValue = "-2"; ExpectedType = [short] }
@{ Script = "0b11111111s"; ExpectedValue = "-1"; ExpectedType = [short] }
#Multipliers
@{ Script = "1skb"; ExpectedValue = "1024"; ExpectedType = [short] }
@@ -765,12 +790,19 @@ foo``u{2195}abc
@{ Script = "-2.5l"; ExpectedValue = "-2"; ExpectedType = [long] }
#Exponential
@{ Script = "0e0l"; ExpectedValue = "0"; ExpectedType = [long] }
@{ Script = "3e0l"; ExpectedValue = "3"; ExpectedType = [long] }
@{ Script = "-3e0l"; ExpectedValue = "-3"; ExpectedType = [long] }
@{ Script = "3e2l"; ExpectedValue = "300"; ExpectedType = [long] }
@{ Script = "-3e2l"; ExpectedValue = "-300"; ExpectedType = [long] }
#Hexadecimal
@{ Script = "0x0l"; ExpectedValue = "0"; ExpectedType = [long] }
@{ Script = "0x41l"; ExpectedValue = "65"; ExpectedType = [long] }
@{ Script = "-0x41l"; ExpectedValue = "-65"; ExpectedType = [long] }
#Binary
@{ Script = "0b0l"; ExpectedValue = "0"; ExpectedType = [long] }
@{ Script = "0b10l"; ExpectedValue = "2"; ExpectedType = [long] }
@{ Script = "-0b10l"; ExpectedValue = "-2"; ExpectedType = [long] }
@{ Script = "0b11111111l"; ExpectedValue = "-1"; ExpectedType = [long] }
#Multipliers
@{ Script = "1lkb"; ExpectedValue = "1024"; ExpectedType = [long] }
@{ Script = "1lmb"; ExpectedValue = "1048576"; ExpectedType = [long] }
@@ -778,6 +810,36 @@ foo``u{2195}abc
@{ Script = "1ltb"; ExpectedValue = "1099511627776"; ExpectedType = [long] }
@{ Script = "1lpb"; ExpectedValue = "1125899906842624"; ExpectedType = [long] }
#BigInteger Integer notation
#Standard
@{ Script = "0n"; ExpectedValue = "0"; ExpectedType = [bigint] }
@{ Script = "10n"; ExpectedValue = "10"; ExpectedType = [bigint] }
@{ Script = "-10n"; ExpectedValue = "-10"; ExpectedType = [bigint] }
@{ Script = "+10n"; ExpectedValue = "10"; ExpectedType = [bigint] }
#Conversion from <Real>
@{ Script = "0.0n"; ExpectedValue = "0"; ExpectedType = [bigint] }
@{ Script = "2.5n"; ExpectedValue = "2"; ExpectedType = [bigint] }
@{ Script = "-2.5n"; ExpectedValue = "-2"; ExpectedType = [bigint] }
#Exponential
@{ Script = "0e0n"; ExpectedValue = "0"; ExpectedType = [bigint] }
@{ Script = "3e2n"; ExpectedValue = "300"; ExpectedType = [bigint] }
@{ Script = "-3e2n"; ExpectedValue = "-300"; ExpectedType = [bigint] }
#Hexadecimal
@{ Script = "0x0n"; ExpectedValue = "0"; ExpectedType = [bigint] }
@{ Script = "0x41n"; ExpectedValue = "65"; ExpectedType = [bigint] }
@{ Script = "-0x41n"; ExpectedValue = "-65"; ExpectedType = [bigint] }
#Binary
@{ Script = "0b0n"; ExpectedValue = "0"; ExpectedType = [bigint] }
@{ Script = "0b10n"; ExpectedValue = "2"; ExpectedType = [bigint] }
@{ Script = "-0b10n"; ExpectedValue = "-2"; ExpectedType = [bigint] }
@{ Script = "0b11111111n"; ExpectedValue = "-1"; ExpectedType = [bigint] }
#Multipliers
@{ Script = "1Nkb"; ExpectedValue = "1024"; ExpectedType = [bigint] }
@{ Script = "1Nmb"; ExpectedValue = "1048576"; ExpectedType = [bigint] }
@{ Script = "1Ngb"; ExpectedValue = "1073741824"; ExpectedType = [bigint] }
@{ Script = "1Ntb"; ExpectedValue = "1099511627776"; ExpectedType = [bigint] }
@{ Script = "1Npb"; ExpectedValue = "1125899906842624"; ExpectedType = [bigint] }
#Unsigned Integer notation
#Standard
@{ Script = "0u"; ExpectedValue = "0"; ExpectedType = [uint] }
@@ -792,6 +854,21 @@ foo``u{2195}abc
#Hexadecimal
@{ Script = "0x0u"; ExpectedValue = "0"; ExpectedType = [uint] }
@{ Script = "0x41u"; ExpectedValue = "65"; ExpectedType = [uint] }
@{ Script = "0xFFu"; ExpectedValue = "255"; ExpectedType = [uint] }
@{ Script = "0xFFFFu"; ExpectedValue = "65535"; ExpectedType = [uint] }
@{ Script = "0xFFFFFFu"; ExpectedValue = "16777215"; ExpectedType = [uint] }
@{ Script = "0xFFFFFFFFu"; ExpectedValue = "$([uint]::MaxValue)"; ExpectedType = [uint] }
@{ Script = "0xFFFFFFFFFFu"; ExpectedValue = "1099511627775"; ExpectedType = [ulong] }
@{ Script = "0xFFFFFFFFFFFFu"; ExpectedValue = "281474976710655"; ExpectedType = [ulong] }
@{ Script = "0xFFFFFFFFFFFFFFu"; ExpectedValue = "72057594037927935"; ExpectedType = [ulong] }
@{ Script = "0xFFFFFFFFFFFFFFFFu"; ExpectedValue = "$([ulong]::MaxValue)"; ExpectedType = [ulong] }
#Binary
@{ Script = "0b0u"; ExpectedValue = "0"; ExpectedType = [uint] }
@{ Script = "0b10u"; ExpectedValue = "2"; ExpectedType = [uint] }
@{ Script = "0b11111111u"; ExpectedValue = "255"; ExpectedType = [uint] }
@{ Script = "0b1111111111111111u"; ExpectedValue = "65535"; ExpectedType = [uint] }
@{ Script = "0b11111111111111111111111111111111u"; ExpectedValue = "4294967295"; ExpectedType = [uint] }
@{ Script = "0b1111111111111111111111111111111111111111111111111111111111111111u"; ExpectedValue = "18446744073709551615"; ExpectedType = [ulong] }
#Multipliers
@{ Script = "1ukb"; ExpectedValue = "1024"; ExpectedType = [uint] }
@{ Script = "1umb"; ExpectedValue = "1048576"; ExpectedType = [uint] }
@@ -814,6 +891,10 @@ foo``u{2195}abc
#Hexadecimal
@{ Script = "0x0uy"; ExpectedValue = "0"; ExpectedType = [byte] }
@{ Script = "0x41uy"; ExpectedValue = "65"; ExpectedType = [byte] }
#Binary
@{ Script = "0b0uy"; ExpectedValue = "0"; ExpectedType = [byte] }
@{ Script = "0b10uy"; ExpectedValue = "2"; ExpectedType = [byte] }
@{ Script = "0b11111111uy"; ExpectedValue = "255"; ExpectedType = [byte] }
#Unsigned-Short Integer Notation
#Standard
@@ -830,6 +911,10 @@ foo``u{2195}abc
#Hexadecimal
@{ Script = "0x0us"; ExpectedValue = "0"; ExpectedType = [ushort] }
@{ Script = "0x41us"; ExpectedValue = "65"; ExpectedType = [ushort] }
#Binary
@{ Script = "0b0us"; ExpectedValue = "0"; ExpectedType = [ushort] }
@{ Script = "0b10us"; ExpectedValue = "2"; ExpectedType = [ushort] }
@{ Script = "0b11111111us"; ExpectedValue = "255"; ExpectedType = [ushort] }
#Multipliers
@{ Script = "1uskb"; ExpectedValue = "1024"; ExpectedType = [ushort] }
@@ -847,6 +932,10 @@ foo``u{2195}abc
#Hexadecimal
@{ Script = "0x0ul"; ExpectedValue = "0"; ExpectedType = [ulong] }
@{ Script = "0x41ul"; ExpectedValue = "65"; ExpectedType = [ulong] }
#Binary
@{ Script = "0b0ul"; ExpectedValue = "0"; ExpectedType = [ulong] }
@{ Script = "0b10ul"; ExpectedValue = "2"; ExpectedType = [ulong] }
@{ Script = "0b11111111ul"; ExpectedValue = "255"; ExpectedType = [ulong] }
#Multipliers
@{ Script = "1ulkb"; ExpectedValue = "1024"; ExpectedType = [ulong] }
@{ Script = "1ulmb"; ExpectedValue = "1048576"; ExpectedType = [ulong] }
@@ -862,28 +951,41 @@ foo``u{2195}abc
}
$testInvalidNumerals = @(
@{ Script = "16p" }
@{ Script = "80x" }
@{ Script = "20ux" }
@{ Script = "18uu" }
@{ Script = "21ss" }
@{ Script = "100ll" }
@{ Script = "150su" }
@{ Script = "160ud" }
@{ Script = "160ld" }
@{ Script = "160sd" }
@{ Script = "160dd" }
@{ Script = "10ds" }
@{ Script = "10ud" }
@{ Script = "16sl" }
@{ Script = "188lu" }
@{ Script = "500sgb" }
@{ Script = "10000usgb" }
@{ Script = "10000.0usgb" }
@{ Script = "16p"; ErrorID = "CommandNotFoundException" }
@{ Script = "1_6"; ErrorID = "CommandNotFoundException" }
@{ Script = "80x"; ErrorID = "CommandNotFoundException" }
@{ Script = "20ux"; ErrorID = "CommandNotFoundException" }
@{ Script = "18uu"; ErrorID = "CommandNotFoundException" }
@{ Script = "21ss"; ErrorID = "CommandNotFoundException" }
@{ Script = "100ll"; ErrorID = "CommandNotFoundException" }
@{ Script = "100Il"; ErrorID = "CommandNotFoundException" }
@{ Script = "100Is"; ErrorID = "CommandNotFoundException" }
@{ Script = "100un"; ErrorID = "CommandNotFoundException" }
@{ Script = "100ln"; ErrorID = "CommandNotFoundException" }
@{ Script = "100sn"; ErrorID = "CommandNotFoundException" }
@{ Script = "100In"; ErrorID = "CommandNotFoundException" }
@{ Script = "100yu"; ErrorID = "CommandNotFoundException" }
@{ Script = "150su"; ErrorID = "CommandNotFoundException" }
@{ Script = "160ud"; ErrorID = "CommandNotFoundException" }
@{ Script = "160ld"; ErrorID = "CommandNotFoundException" }
@{ Script = "160yd"; ErrorID = "CommandNotFoundException" }
@{ Script = "160sd"; ErrorID = "CommandNotFoundException" }
@{ Script = "160dd"; ErrorID = "CommandNotFoundException" }
@{ Script = "10ds"; ErrorID = "CommandNotFoundException" }
@{ Script = "10ud"; ErrorID = "CommandNotFoundException" }
@{ Script = "16sl"; ErrorID = "CommandNotFoundException" }
@{ Script = "188lu"; ErrorID = "CommandNotFoundException" }
@{ Script = "0xFFFFy"; ErrorID = "ParseException" }
@{ Script = "500sgb"; ErrorID = "ParseException" }
@{ Script = "10000usgb"; ErrorID = "ParseException" }
@{ Script = "10000.0usgb"; ErrorID = "ParseException" }
@{ Script = "1uykb"; ErrorID = "ParseException" }
@{ Script = "10_000ul"; ErrorID = "CommandNotFoundException" }
)
It "<Script> should throw an error" -TestCases $testInvalidNumerals {
param($Script)
{[ScriptBlock]::Create($Script).Invoke()} | Should -Throw
param($Script, $ErrorID)
{[ScriptBlock]::Create($Script).Invoke()} | Should -Throw -ErrorId $ErrorID
}
}