mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +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:
committed by
Travis Plunk
parent
4bd41e836c
commit
3f52adb0ae
@@ -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<char> 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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user