Skip to content

Commit

Permalink
Add value validation to bitwise functions
Browse files Browse the repository at this point in the history
Large integer values will now be limited to 53-bit if that is what the platform (e.g., MSVC) supports.
Blake-Madden committed Mar 6, 2024
1 parent 482da0b commit 7e041d0
Showing 4 changed files with 137 additions and 61 deletions.
37 changes: 22 additions & 15 deletions docs/manual/functions.qmd
Original file line number Diff line number Diff line change
@@ -10,19 +10,6 @@ The following built-in functions are available:
| ASIN(Number) | Returns the arcsine, or inverse sine function, of *Number*, where -1 <= *Number* <= 1. The arcsine is the angle whose sine is *Number*. The returned angle is given in radians where -pi/2 <= angle <= pi/2. |
| ATAN(x) | Returns the principal value of the arc tangent of *x*, expressed in radians.. |
| ATAN2(y, x) | Returns the principal value of the arc tangent of *y*,*x*, expressed in radians. |
| BITAND(Number1, Number2) | Returns a bitwise 'AND' of two (integral) numbers. (Both numbers must be positive.) |
| BITLROTATE8(Number, RotateAmount) | Returns *Number* left rotated left to the most significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 8-bit integer.<br>\linebreak (Only available if compiled as C++20.) |
| BITLROTATE16(Number, RotateAmount) | Returns *Number* left rotated left to the most significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 16-bit integer.<br>\linebreak (Only available if compiled as C++20.) |
| BITLROTATE32(Number, RotateAmount) | Returns *Number* left rotated left to the most significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 32-bit integer.<br>\linebreak (Only available if compiled as C++20.) |
| BITLROTATE64(Number, RotateAmount) | Returns *Number* left rotated left to the most significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 64-bit integer.<br>\linebreak Note, however, that values beyond the range of `double` should not be used as they will wrap around.<br>\linebreak (Only available if compiled as C++20.) |
| BITLSHIFT(Number, ShiftAmount) | Returns *Number* left shifted by the specified number (*ShiftAmount*) of bits. |
| BITOR(Number1, Number2) | Returns a bitwise 'OR' of two (integral) numbers. (Both numbers must be positive.) |
| BITRROTATE8(Number, RotateAmount) | Returns *Number* right rotated right to the least significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 8-bit integer.<br>\linebreak (Only available if compiled as C++20.) |
| BITRROTATE16(Number, RotateAmount) | Returns *Number* right rotated right to the least significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 16-bit integer.<br>\linebreak (Only available if compiled as C++20.) |
| BITRROTATE32(Number, RotateAmount) | Returns *Number* right rotated right to the least significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 32-bit integer.<br>\linebreak (Only available if compiled as C++20.) |
| BITRROTATE64(Number, RotateAmount) | Returns *Number* right rotated right to the least significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 64-bit integer.<br>\linebreak Note, however, that values beyond the range of `double` should not be used as they will wrap around.<br>\linebreak (Only available if compiled as C++20.) |
| BITRSHIFT(Number, ShiftAmount) | Returns *Number* right shifted by the specified number (*ShiftAmount*) of bits. |
| BITXOR(Number1, Number2) | Returns a bitwise 'XOR' of two (integral) numbers. (Both numbers must be positive.) |
| CEIL(Number) | Smallest integer not less than *Number*.<br>\linebreak `CEIL(-3.2)` = -3<br>\linebreak `CEIL(3.2)` = 4 |
| CLAMP(Number, Start, End) | Constrains *Number* within the range of *Start* and *End*. |
| COMBIN(Number, NumberChosen) | Returns the number of combinations for a given number (*NumberChosen*) of items from *Number* of items. Note that for combinations, order of items is not important. |
@@ -51,15 +38,35 @@ The following built-in functions are available:
| SIN(Number) | Sine of the angle *Number* in radians. |
| SINH(Number) | Hyperbolic sine of *Number*. |
| SQRT(Number) | Square root of *Number*. |
| SUPPORTS32BIT() | Returns true if 32-bit integers are supported. This will affect the supported range of values for bitwise operations. |
| SUPPORTS64BIT() | Returns true if 64-bit integers are supported. This will affect the supported range of values for bitwise operations. |
| TAN(Number) | Tangent of *Number*. |
| TGAMMA(Number) | Returns the gamma function of *Number*. |
| TRUNC(Number) | Discards the fractional part of *Number*.<br>\linebreak `TRUNC(-3.2)` = -3<br>\linebreak `TRUNC(3.2)` = 3 |

Table: Math Functions\index{functions!math}
:::

::: {.minipage data-latex="{\textwidth}"}
| Function | Description |
| :-- | :-- |
| BITAND(Number1, Number2) | Returns a bitwise 'AND' of two (integral) numbers. (Both numbers must be positive and cannot exceed `(2^48)-1`.) |
| BITLROTATE8(Number, RotateAmount) | Returns *Number* left rotated left to the most significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 8-bit integer.<br>\linebreak (Only available if compiled as C++20.) |
| BITLROTATE16(Number, RotateAmount) | Returns *Number* left rotated left to the most significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 16-bit integer.<br>\linebreak (Only available if compiled as C++20.) |
| BITLROTATE32(Number, RotateAmount) | Returns *Number* left rotated left to the most significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 32-bit integer.<br>\linebreak (Only available if compiled as C++20.) |
| BITLROTATE(Number, RotateAmount) | Returns *Number* left rotated left to the most significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 53-bit (or 64-bit) integer.<br>\linebreak Note, however, that values beyond the range of `double` should not be used as they will wrap around.<br>\linebreak (Only available if compiled as C++20.) |
| BITLSHIFT(Number, ShiftAmount) | Returns *Number* left shifted by the specified number (*ShiftAmount*) of bits.<br>\linebreak *Number* cannot exceed `(2^48)-1` and *ShiftAmount* cannot exceed `53` (or `64`, if supported). |
| BITOR(Number1, Number2) | Returns a bitwise 'OR' of two (integral) numbers. (Both numbers must be positive and cannot exceed `(2^48)-1`.) |
| BITRROTATE8(Number, RotateAmount) | Returns *Number* right rotated right to the least significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 8-bit integer.<br>\linebreak (Only available if compiled as C++20.) |
| BITRROTATE16(Number, RotateAmount) | Returns *Number* right rotated right to the least significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 16-bit integer.<br>\linebreak (Only available if compiled as C++20.) |
| BITRROTATE32(Number, RotateAmount) | Returns *Number* right rotated right to the least significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 32-bit integer.<br>\linebreak (Only available if compiled as C++20.) |
| BITRROTATE(Number, RotateAmount) | Returns *Number* right rotated right to the least significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 53-bit (or 64-bit) integer.<br>\linebreak Note, however, that values beyond the range of `double` should not be used as they will wrap around.<br>\linebreak (Only available if compiled as C++20.) |
| BITRSHIFT(Number, ShiftAmount) | Returns *Number* right shifted by the specified number (*ShiftAmount*) of bits.<br>\linebreak *Number* cannot exceed `(2^48)-1` and *ShiftAmount* cannot exceed `53` (or `64`, if supported). |
| BITXOR(Number1, Number2) | Returns a bitwise 'XOR' of two (integral) numbers. (Both numbers must be positive and cannot exceed `(2^48)-1`.) |
| SUPPORTS32BIT() | Returns true if 32-bit integers are supported. This will affect the supported range of values for bitwise operations. |
| SUPPORTS64BIT() | Returns true if 64-bit integers are supported. This will affect the supported range of values for bitwise operations. |

Table: Bitwise Functions\index{functions!bitwise}
:::

::: {.notesection data-latex=""}
Defining `TE_FLOAT` will disable all bitwise functions and operators.
:::
55 changes: 31 additions & 24 deletions tests/tetests.cpp
Original file line number Diff line number Diff line change
@@ -2179,6 +2179,9 @@ TEST_CASE("Bitwise operators", "[bitwise]")
CHECK(tep.evaluate("8000 | 4294967295") ==
(8000 | 4294967295));
#endif
CHECK(tep.evaluate("=BITOR((2^48)-1, 1)") == 281474976710655);
CHECK(tep.evaluate("=BITOR((2^48)-1, (2^48)-1)") == 281474976710655);
CHECK(std::isnan(tep.evaluate("=BITOR((2^48)-1, (2^48))")));
CHECK(tep.evaluate("BITOR(23, 10)") == 31);
CHECK(tep.evaluate("BITOR(23, 0)") == (23 | 0));
CHECK(tep.evaluate("BITOR(0, 10)") == (0 | 10));
@@ -2210,6 +2213,10 @@ TEST_CASE("Bitwise operators", "[bitwise]")
CHECK(tep.evaluate("8000 ^ 4294967295") ==
(8000 ^ 4294967295));
#endif
CHECK(tep.evaluate("=BITXOR((2^48)-1, 1)") == 281474976710654);
CHECK(tep.evaluate("=BITXOR((2^48)-1, (2^48)-1)") == 0);
CHECK(tep.evaluate("=BITXOR((2^48)-1, 1587)") == 281474976709068);
CHECK(std::isnan(tep.evaluate("=BITXOR((2^48)-1, (2^48))")));
CHECK(tep.evaluate("BITXOR(5,3)") == 6);
CHECK(tep.evaluate("BITXOR(5,9)") == 12);
CHECK(tep.evaluate("BITXOR(23, 0)") == (23 ^ 0));
@@ -2233,6 +2240,10 @@ TEST_CASE("Bitwise operators", "[bitwise]")
CHECK(tep.evaluate("23 & 0") == (23 & 0));
CHECK(tep.evaluate("0 & 10") == (0 & 10));
#endif
CHECK(tep.evaluate("=BITAND((2^48)-1, 1)") == 1);
CHECK(tep.evaluate("=BITAND((2^48)-1, (2^48)-1)") == 281474976710655);
CHECK(tep.evaluate("=BITAND((2^48)-1, 1587)") == 1587);
CHECK(std::isnan(tep.evaluate("=BITAND((2^48)-1, (2^48))")));
CHECK(tep.evaluate("BITAND(1, 5)") == 1);
CHECK(tep.evaluate("BITAND(13, 25)") == 9);
CHECK(tep.evaluate("BITAND(23, 0)") == (23 & 0));
@@ -2262,7 +2273,7 @@ TEST_CASE("Rotate operators", "[rotate]")
CHECK(tep.evaluate("BITLROTATE8(255, 0)") == std::rotl(i, 0));
CHECK(tep.evaluate("BITLROTATE8(255, 1)") == std::rotl(i, 1));
CHECK(tep.evaluate("BITLROTATE8(255, 4)") == std::rotl(i, 4));
CHECK(tep.evaluate("BITLROTATE8(255, 9)") == std::rotl(i, 9));
CHECK(std::isnan(tep.evaluate("BITLROTATE8(255, 9)")));
CHECK(tep.evaluate("BITLROTATE8(255, -1)") == std::rotl(i, -1));
}
SECTION("BITLROTATE16")
@@ -2285,7 +2296,7 @@ TEST_CASE("Rotate operators", "[rotate]")
CHECK(tep.evaluate("BITLROTATE32(4294967295, 9)") == std::rotl(i, 9));
CHECK(tep.evaluate("BITLROTATE32(4294967295, -1)") == std::rotl(i, -1));
}
SECTION("BITLROTATE64")
SECTION("BITLROTATE")
{
// malformed
CHECK(std::isnan(tep.evaluate("5 <")));
@@ -2300,12 +2311,12 @@ TEST_CASE("Rotate operators", "[rotate]")
CHECK(tep.evaluate("4294967295 <<< 9") == std::rotl(i, 9));
CHECK(tep.evaluate("4294967295 <<< -1") == std::rotl(i, -1));

CHECK(tep.evaluate("BITLROTATE64(0, 0)") == std::rotl((uint64_t)0, 0));
CHECK(tep.evaluate("BITLROTATE64(4294967295, 0)") == std::rotl(i, 0));
CHECK(tep.evaluate("BITLROTATE64(4294967295, 1)") == std::rotl(i, 1));
CHECK(tep.evaluate("BITLROTATE64(4294967295, 4)") == std::rotl(i, 4));
CHECK(tep.evaluate("BITLROTATE64(4294967295, 9)") == std::rotl(i, 9));
CHECK(tep.evaluate("BITLROTATE64(4294967295, -1)") == std::rotl(i, -1));
CHECK(tep.evaluate("BITLROTATE(0, 0)") == std::rotl((uint64_t)0, 0));
CHECK(tep.evaluate("BITLROTATE(4294967295, 0)") == std::rotl(i, 0));
CHECK(tep.evaluate("BITLROTATE(4294967295, 1)") == std::rotl(i, 1));
CHECK(tep.evaluate("BITLROTATE(4294967295, 4)") == std::rotl(i, 4));
CHECK(tep.evaluate("BITLROTATE(4294967295, 9)") == std::rotl(i, 9));
CHECK(tep.evaluate("BITLROTATE(4294967295, -1)") == std::rotl(i, -1));
}

SECTION("BITRROTATE8")
@@ -2315,7 +2326,7 @@ TEST_CASE("Rotate operators", "[rotate]")
CHECK(tep.evaluate("BITRROTATE8(255, 0)") == std::rotr(i, 0));
CHECK(tep.evaluate("BITRROTATE8(255, 1)") == std::rotr(i, 1));
CHECK(tep.evaluate("BITRROTATE8(255, 4)") == std::rotr(i, 4));
CHECK(tep.evaluate("BITRROTATE8(255, 9)") == std::rotr(i, 9));
CHECK(std::isnan(tep.evaluate("BITRROTATE8(255, 9)")));
CHECK(tep.evaluate("BITRROTATE8(255, -1)") == std::rotr(i, -1));
}
SECTION("BITRROTATE16")
@@ -2338,7 +2349,7 @@ TEST_CASE("Rotate operators", "[rotate]")
CHECK(tep.evaluate("BITRROTATE32(4294967295, 9)") == std::rotr(i, 9));
CHECK(tep.evaluate("BITRROTATE32(4294967295, -1)") == std::rotr(i, -1));
}
SECTION("BITRROTATE64")
SECTION("BITRROTATE")
{
// malformed
CHECK(std::isnan(tep.evaluate("5 >")));
@@ -2355,12 +2366,12 @@ TEST_CASE("Rotate operators", "[rotate]")
CHECK(tep.evaluate("4294967295 >>> 9") == std::rotr(i, 9));
CHECK(tep.evaluate("4294967295 >>> -1") == std::rotr(i, -1));

CHECK(tep.evaluate("BITRROTATE64(0, 0)") == std::rotr((uint64_t)0, 0));
CHECK(tep.evaluate("BITRROTATE64(4294967295, 0)") == std::rotr(i, 0));
CHECK(tep.evaluate("BITRROTATE64(4294967295, 1)") == std::rotr(i, 1));
CHECK(tep.evaluate("BITRROTATE64(4294967295, 4)") == std::rotr(i, 4));
CHECK(tep.evaluate("BITRROTATE64(4294967295, 9)") == std::rotr(i, 9));
CHECK(tep.evaluate("BITRROTATE64(4294967295, -1)") == std::rotr(i, -1));
CHECK(tep.evaluate("BITRROTATE(0, 0)") == std::rotr((uint64_t)0, 0));
CHECK(tep.evaluate("BITRROTATE(4294967295, 0)") == std::rotr(i, 0));
CHECK(tep.evaluate("BITRROTATE(4294967295, 1)") == std::rotr(i, 1));
CHECK(tep.evaluate("BITRROTATE(4294967295, 4)") == std::rotr(i, 4));
CHECK(tep.evaluate("BITRROTATE(4294967295, 9)") == std::rotr(i, 9));
CHECK(tep.evaluate("BITRROTATE(4294967295, -1)") == std::rotr(i, -1));
}
}
#endif
@@ -2370,12 +2381,12 @@ TEST_CASE("Shift operators", "[shift]")
{
te_parser tep;

for (uint64_t i = 0; i < 63; ++i)
for (uint64_t i = 0; i < te_parser::get_max_integer_bitness()+1; ++i)
{
CHECK(tep.evaluate((std::string("1 << ") + std::to_string(i)).c_str()) == ((uint64_t)1 << i));
CHECK(tep.evaluate((std::string("1 >> ") + std::to_string(i)).c_str()) == ((uint64_t)1 >> i));
}
for (uint64_t i = 0; i < 62; ++i)
for (uint64_t i = 0; i < te_parser::get_max_integer_bitness()+1; ++i)
{
CHECK(tep.evaluate((std::string("2 << ") + std::to_string(i)).c_str()) == ((uint64_t)2 << i));
CHECK(tep.evaluate((std::string("2 >> ") + std::to_string(i)).c_str()) == ((uint64_t)2 >> i));
@@ -2401,17 +2412,14 @@ TEST_CASE("Shift operators", "[shift]")
}
SECTION("Left")
{
CHECK_FALSE(tep.compile("1 << 64"));
CHECK_FALSE(tep.compile("1 << 54"));
CHECK(std::isnan(tep.evaluate()));
CHECK(tep.get_last_error_message() == "Additive expression of left shift (<<) operation must be between 0-63.");
CHECK(tep.evaluate("0 << 4") == ((uint64_t)0 << 4));
CHECK(std::isnan(tep.evaluate("1 << 64")));
CHECK(std::isnan(tep.evaluate("1 << -5")));
CHECK(tep.get_last_error_message() == "Additive expression of left shift (<<) operation must be between 0-63.");
CHECK(tep.evaluate("31 << 59") == ((uint64_t)31 << 59));
CHECK(tep.evaluate("31 << 53") == ((uint64_t)31 << 53));
// overflow
CHECK(std::isnan(tep.evaluate("32 << 59")));
CHECK(tep.get_last_error_message() == "Overflow in left shift (<<) operation; base number is too large.");
CHECK(std::isnan(tep.evaluate("2 << 63")));
CHECK(std::isnan(tep.evaluate("-1 << 2")));
CHECK(tep.evaluate("1.0 << 4.0") == ((uint64_t)1 << 4));
@@ -2436,7 +2444,6 @@ TEST_CASE("Shift operators", "[shift]")
CHECK(std::isnan(tep.evaluate("1 >> 64")));
CHECK(std::isnan(tep.get_result()));
CHECK(std::isnan(tep.evaluate("1 >> -5")));
CHECK(tep.get_last_error_message() == "Additive expression of right shift (>>) operation must be between 0-63.");
CHECK(std::isnan(tep.get_result()));
CHECK(tep.evaluate("32 >> 4") == ((uint64_t)32 >> 4));
CHECK(tep.evaluate("32 >> 5") == ((uint64_t)32 >> 5));
96 changes: 74 additions & 22 deletions tinyexpr.cpp
Original file line number Diff line number Diff line change
@@ -446,6 +446,10 @@ namespace te_builtins
{
throw std::runtime_error("Bitwise RIGHT ROTATE value must be positive.");
}
else if (val2 > 8)
{
throw std::runtime_error("Rotation operation must be between 0-8.");
}

return static_cast<te_type>(std::rotr(static_cast<uint8_t>(val1), static_cast<int>(val2)));
}
@@ -462,6 +466,10 @@ namespace te_builtins
{
throw std::runtime_error("Bitwise LEFT ROTATE value must be positive.");
}
else if (val2 > 8)
{
throw std::runtime_error("Rotation operation must be between 0-8.");
}

return static_cast<te_type>(std::rotl(static_cast<uint8_t>(val1), static_cast<int>(val2)));
}
@@ -478,6 +486,10 @@ namespace te_builtins
{
throw std::runtime_error("Bitwise RIGHT ROTATE value must be positive.");
}
else if (val2 > 16)
{
throw std::runtime_error("Rotation operation must be between 0-16.");
}

return static_cast<te_type>(std::rotr(static_cast<uint16_t>(val1), static_cast<int>(val2)));
}
@@ -494,6 +506,10 @@ namespace te_builtins
{
throw std::runtime_error("Bitwise LEFT ROTATE value must be positive.");
}
else if (val2 > 16)
{
throw std::runtime_error("Rotation operation must be between 0-16.");
}

return static_cast<te_type>(std::rotl(static_cast<uint16_t>(val1), static_cast<int>(val2)));
}
@@ -510,6 +526,10 @@ namespace te_builtins
{
throw std::runtime_error("Bitwise RIGHT ROTATE value must be positive.");
}
else if (val2 > 32)
{
throw std::runtime_error("Rotation operation must be between 0-32.");
}

return static_cast<te_type>(std::rotr(static_cast<uint32_t>(val1), static_cast<int>(val2)));
}
@@ -526,13 +546,17 @@ namespace te_builtins
{
throw std::runtime_error("Bitwise LEFT ROTATE value must be positive.");
}
else if (val2 > 32)
{
throw std::runtime_error("Rotation operation must be between 0-32.");
}

return static_cast<te_type>(std::rotl(static_cast<uint32_t>(val1), static_cast<int>(val2)));
}

//--------------------------------------------------
[[nodiscard]]
static te_type te_right_rotate64(te_type val1, te_type val2)
static te_type te_right_rotate(te_type val1, te_type val2)
{
if (std::floor(val1) != val1 || std::floor(val2) != val2)
{
@@ -542,13 +566,18 @@ namespace te_builtins
{
throw std::runtime_error("Bitwise RIGHT ROTATE value must be positive.");
}
else if (val2 > te_parser::get_max_integer_bitness())
{
throw std::runtime_error("Rotation operation must be between 0-" +
std::to_string(te_parser::get_max_integer_bitness()));
}

return static_cast<te_type>(std::rotr(static_cast<uint64_t>(val1), static_cast<int>(val2)));
}

//--------------------------------------------------
[[nodiscard]]
static te_type te_left_rotate64(te_type val1, te_type val2)
static te_type te_left_rotate(te_type val1, te_type val2)
{
if (std::floor(val1) != val1 || std::floor(val2) != val2)
{
@@ -558,6 +587,11 @@ namespace te_builtins
{
throw std::runtime_error("Bitwise LEFT ROTATE value must be positive.");
}
else if (val2 > te_parser::get_max_integer_bitness())
{
throw std::runtime_error("Rotation operation must be between 0-" +
std::to_string(te_parser::get_max_integer_bitness()));
}

return static_cast<te_type>(std::rotl(static_cast<uint64_t>(val1), static_cast<int>(val2)));
}
@@ -577,6 +611,10 @@ namespace te_builtins
{
throw std::runtime_error("Bitwise OR operation must use positive integers.");
}
else if (val1 > te_parser::MAX_BITOPS_VAL || val2 > te_parser::MAX_BITOPS_VAL)
{
throw std::runtime_error("Value is too large for bitwise operation.");
}
return static_cast<te_type>(static_cast<uint64_t>(val1) | static_cast<uint64_t>(val2));
}

@@ -594,6 +632,10 @@ namespace te_builtins
{
throw std::runtime_error("Bitwise XOR operation must use positive integers.");
}
else if (val1 > te_parser::MAX_BITOPS_VAL || val2 > te_parser::MAX_BITOPS_VAL)
{
throw std::runtime_error("Value is too large for bitwise operation.");
}
return static_cast<te_type>(static_cast<uint64_t>(val1) ^ static_cast<uint64_t>(val2));
}

@@ -611,6 +653,10 @@ namespace te_builtins
{
throw std::runtime_error("Bitwise AND operation must use positive integers.");
}
else if (val1 > te_parser::MAX_BITOPS_VAL || val2 > te_parser::MAX_BITOPS_VAL)
{
throw std::runtime_error("Value is too large for bitwise operation.");
}
return static_cast<te_type>(static_cast<uint64_t>(val1) & static_cast<uint64_t>(val2));
}

@@ -619,27 +665,30 @@ namespace te_builtins
[[nodiscard]]
static te_type te_left_shift(te_type val1, te_type val2)
{
constexpr int BITNESS_64BIT{ 64 }; // NOLINT

if (std::floor(val1) != val1)
{
throw std::runtime_error("Left side of left shift (<<) operation must be an integer.");
}
if (std::floor(val2) != val2)
else if (std::floor(val2) != val2)
{
throw std::runtime_error(
"Additive expression of left shift (<<) operation must be an integer.");
}
if (val1 < 0)
else if (val1 < 0)
{
throw std::runtime_error("Left side of left shift (<<) operation cannot be negative.");
}
// bitness is limited to 64-bit, so ensure shift doesn't go beyond that
else if (val1 > te_parser::MAX_BITOPS_VAL)
{
throw std::runtime_error("Value is too large for bitwise operation.");
}
// bitness is limited to 53-bit, so ensure shift doesn't go beyond that
// and cause undefined behavior
if (val2 < 0 || val2 >= BITNESS_64BIT)
else if (val2 < 0 || val2 > te_parser::get_max_integer_bitness())
{
throw std::runtime_error(
"Additive expression of left shift (<<) operation must be between 0-63.");
"Additive expression of left shift (<<) operation must be between 0-" +
std::to_string(te_parser::get_max_integer_bitness()));
}

const auto multipler = (static_cast<uint64_t>(1) << static_cast<uint64_t>(val2));
@@ -656,25 +705,28 @@ namespace te_builtins
[[nodiscard]]
static te_type te_right_shift(te_type val1, te_type val2)
{
constexpr int BITNESS_64BIT{ 64 }; // NOLINT

if (std::floor(val1) != val1)
{
throw std::runtime_error("Left side of right shift (>>) operation must be an integer.");
}
if (std::floor(val2) != val2)
else if (std::floor(val2) != val2)
{
throw std::runtime_error(
"Additive expression of right shift (>>)operation must be an integer.");
"Additive expression of right shift (>>) operation must be an integer.");
}
if (val1 < 0)
else if (val1 < 0)
{
throw std::runtime_error("Left side of right shift (<<) operation cannot be negative.");
}
if (val2 < 0 || val2 >= BITNESS_64BIT)
else if (val1 > te_parser::MAX_BITOPS_VAL)
{
throw std::runtime_error("Value is too large for bitwise operation.");
}
else if (val2 < 0 || val2 > te_parser::get_max_integer_bitness())
{
throw std::runtime_error(
"Additive expression of right shift (>>) operation must be between 0-63.");
"Additive expression of right shift (>>) operation must be between 0-" +
std::to_string(te_parser::get_max_integer_bitness()));
}

return static_cast<te_type>(static_cast<uint64_t>(val1) >> static_cast<uint64_t>(val2));
@@ -934,8 +986,8 @@ const std::set<te_variable> te_parser::m_functions = { // NOLINT
{ "bitrrotate16", static_cast<te_fun2>(te_builtins::te_right_rotate16), TE_PURE },
{ "bitlrotate32", static_cast<te_fun2>(te_builtins::te_left_rotate32), TE_PURE },
{ "bitrrotate32", static_cast<te_fun2>(te_builtins::te_right_rotate32), TE_PURE },
{ "bitlrotate64", static_cast<te_fun2>(te_builtins::te_left_rotate64), TE_PURE },
{ "bitrrotate64", static_cast<te_fun2>(te_builtins::te_right_rotate64), TE_PURE },
{ "bitlrotate", static_cast<te_fun2>(te_builtins::te_left_rotate), TE_PURE },
{ "bitrrotate", static_cast<te_fun2>(te_builtins::te_right_rotate), TE_PURE },
#endif
{ "bitlshift", static_cast<te_fun2>(te_builtins::te_left_shift_or_right), TE_PURE },
{ "bitrshift", static_cast<te_fun2>(te_builtins::te_right_shift_or_left), TE_PURE },
@@ -1234,14 +1286,14 @@ void te_parser::next_token(te_parser::state* theState)
(*std::next(theState->m_next) == '<'))
{
theState->m_type = te_parser::state::token_type::TOK_INFIX;
theState->m_value = static_cast<te_fun2>(te_builtins::te_left_rotate64);
theState->m_value = static_cast<te_fun2>(te_builtins::te_left_rotate);
std::advance(theState->m_next, 2);
}
else if (tok == '>' && (*theState->m_next == '>') &&
(*std::next(theState->m_next) == '>'))
{
theState->m_type = te_parser::state::token_type::TOK_INFIX;
theState->m_value = static_cast<te_fun2>(te_builtins::te_right_rotate64);
theState->m_value = static_cast<te_fun2>(te_builtins::te_right_rotate);
std::advance(theState->m_next, 2);
}
#else
@@ -1665,8 +1717,8 @@ te_expr* te_parser::expr_level8(te_parser::state* theState)
get_function2(theState->m_value) == te_builtins::te_right_shift
#if __cplusplus >= 202002L && !defined(TE_FLOAT)
||
get_function2(theState->m_value) == te_builtins::te_left_rotate64 ||
get_function2(theState->m_value) == te_builtins::te_right_rotate64 ||
get_function2(theState->m_value) == te_builtins::te_left_rotate ||
get_function2(theState->m_value) == te_builtins::te_right_rotate ||
get_function2(theState->m_value) == te_builtins::te_left_rotate32 ||
get_function2(theState->m_value) == te_builtins::te_right_rotate32 ||
get_function2(theState->m_value) == te_builtins::te_left_rotate16 ||
10 changes: 10 additions & 0 deletions tinyexpr.h
Original file line number Diff line number Diff line change
@@ -257,6 +257,9 @@ class te_parser
/// @brief No position, which is what get_last_error_position() returns
/// when there was no parsing error.
constexpr static int64_t npos = -1;
/// @private
// (2^48)-1
constexpr static double MAX_BITOPS_VAL{ 281474976710655 }; // NOLINT
/// @returns @c true if the parser's internal type can hold `uint32_t` without truncation.
[[nodiscard]]
constexpr static bool supports_32bit() noexcept
@@ -269,6 +272,13 @@ class te_parser
{
return std::numeric_limits<te_type>::digits >= std::numeric_limits<uint64_t>::digits;
}
/// @returns The bits available in the internal data type.\n
/// This will affect the largest integer size that can be used in bitwise operations.
[[nodiscard]]
constexpr static int get_max_integer_bitness() noexcept
{
return std::numeric_limits<te_type>::digits;
}
/// @returns The largest integer value that the parser can handle without truncation.
[[nodiscard]]
static te_type get_max_integer()

0 comments on commit 7e041d0

Please sign in to comment.