// SPDX-License-Identifier: Zlib /* * TINYEXPR - Tiny recursive descent parser and evaluation engine in C * * Copyright (c) 2015-2020 Lewis Van Winkle * * http://CodePlea.com * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgement in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ /* * TINYEXPR++ - Tiny recursive descent parser and evaluation engine in C++ * * Copyright (c) 2020-2026 Blake Madden * * C++ version of the TinyExpr library. * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgement in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ #ifndef __TINYEXPR_PLUS_PLUS_H__ #define __TINYEXPR_PLUS_PLUS_H__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if __has_include() #include #endif // type_traits to get n_args. #include #include constexpr int TINYEXPR_CPP_MAJOR_VERSION = 1; constexpr int TINYEXPR_CPP_MINOR_VERSION = 1; constexpr int TINYEXPR_CPP_PATCH_VERSION = 0; constexpr int TINYEXPR_CPP_TWEAK_VERSION = 0; #define TINYEXPR_CPP_COPYRIGHT \ "TinyExpr: Copyright (c) 2015-2020 Lewis Van Winkle\n" \ "TinyExpr++: Copyright (c) 2020-2026 Blake Madden" class te_parser; #if defined(TE_RAND_SEED) && defined(TE_RAND_SEED_TIME) #error TE_RAND_SEED and TE_RAND_SEED_TIME compile options cannot be combined. Only one random number generator seeding method can be specified. #endif #if defined(TE_FLOAT) && defined(TE_LONG_DOUBLE) #error TE_FLOAT and TE_LONG_DOUBLE compile options cannot be combined. Only one data type can be specified. #endif /// @brief Define this to use @c float instead of @c double for the parser's data type. #ifdef TE_FLOAT /// @brief The parameter and return type for parser and its functions. using te_type = float; #elif defined(TE_LONG_DOUBLE) using te_type = long double; #else /// @brief The parameter and return type for parser and its functions. using te_type = double; #endif #if defined(TE_FLOAT) && defined(TE_BITWISE_OPERATORS) #error TE_FLOAT and TE_BITWISE_OPERATORS compile options cannot be combined. TE_FLOAT will not support bitwise operations. #endif class te_expr; // clang-format off // regular functions using te_fun0 = te_type (*)(); using te_fun1 = te_type (*)(te_type); using te_fun2 = te_type (*)(te_type, te_type); using te_fun3 = te_type (*)(te_type, te_type, te_type); using te_fun4 = te_type (*)(te_type, te_type, te_type, te_type); using te_fun5 = te_type (*)(te_type, te_type, te_type, te_type, te_type); using te_fun6 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type); using te_fun7 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_fun8 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_fun9 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_fun10 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_fun11 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_fun12 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_fun13 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_fun14 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_fun15 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_fun16 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_fun17 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_fun18 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_fun19 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_fun20 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_fun21 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_fun22 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_fun23 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_fun24 = te_type (*)(te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); // context functions (where te_variable passes a client's te_expr as the first argument) using te_confun0 = te_type (*)(const te_expr*); using te_confun1 = te_type (*)(const te_expr*, te_type); using te_confun2 = te_type (*)(const te_expr*, te_type, te_type); using te_confun3 = te_type (*)(const te_expr*, te_type, te_type, te_type); using te_confun4 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type); using te_confun5 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type); using te_confun6 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun7 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun8 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun9 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun10 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun11 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun12 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun13 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun14 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun15 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun16 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun17 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun18 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun19 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun20 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun21 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun22 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun23 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); using te_confun24 = te_type (*)(const te_expr*, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type, te_type); // clang-format on template struct te_fun_traits; // Specialization for function pointers template struct te_fun_traits { using arg_tuple = std::tuple; constexpr static size_t arity = std::tuple_size::value; }; // Specialization for function types template struct te_fun_traits { using arg_tuple = std::tuple; constexpr static size_t arity = std::tuple_size::value; }; // Helper variable template for easier access template constexpr size_t te_function_arity = te_fun_traits::arity; template constexpr bool te_is_constant_v = std::is_same_v; template constexpr bool te_is_variable_v = std::is_same_v; template struct te_is_closure { constexpr static bool value{ false }; }; // Specialization for function pointers template struct te_is_closure { constexpr static bool value{ true }; }; // Specialization for function types template struct te_is_closure { constexpr static bool value{ true }; }; template constexpr bool te_is_closure_v = te_is_closure::value; template constexpr bool te_is_function_v = !te_is_constant_v && !te_is_variable_v && !te_is_closure_v; // functions for unknown symbol resolution using te_usr_noop = std::function; using te_usr_fun0 = std::function; using te_usr_fun1 = std::function; using te_usr_variant_type = std::variant; // do not change the ordering of these, the indices are used to determine // the value type of te_variable using te_variant_type = std::variant; /// @brief A variable's flags, effecting how it is evaluated. /// @note This is a bitmask, so flags (TE_PURE and TE_VARIADIC) can be OR'ed. /// @internal Note that because this is a bitmask, don't declare it as an enum class, /// just a C-style enum. enum te_variable_flags { /// @brief Don't do anything special when evaluating. TE_DEFAULT = 0, /// @brief Don't update when simple evaluation is run /// (i.e., only updated when expression is compiled). TE_PURE = (1 << 0), /// @brief Function that can take 1-24 argument (unused arguments are set to NaN). TE_VARIADIC = (1 << 1) }; /// @private class te_string_less { public: [[nodiscard]] bool operator()(const std::string& lhv, const std::string& rhv) const { const auto minStrLen = std::min(lhv.length(), rhv.length()); for (size_t i = 0; i < minStrLen; ++i) { const auto lhCh = tolower(lhv[i]); const auto rhCh = tolower(rhv[i]); if (lhCh == rhCh) { continue; } return (lhCh < rhCh); } return (lhv.length() < rhv.length()); } // We can assume that we are only dealing with a-z, A-Z, 0-9, ., or _, // so use a branchless tolower. [[nodiscard]] constexpr static char tolower(const char chr) noexcept { return chr + (32 * (chr >= 'A' && chr <= 'Z')); } }; /// @brief A compiled expression. /// @details Can also be an additional object that can be passed to /// te_confun0-te_confun24 functions via a te_variable. class te_expr { public: te_expr(const te_variable_flags type, const te_variant_type& value) noexcept : m_type(type), m_value(value) { } explicit te_expr(const te_variable_flags type) noexcept : m_type(type) {} /// @private te_expr() noexcept = default; /// @private te_expr(const te_expr&) = delete; /// @private te_expr& operator=(const te_expr&) = delete; /// @private virtual ~te_expr() = default; /// @brief The type that m_value represents. te_variable_flags m_type{ TE_DEFAULT }; /// @brief The te_type constant, te_type pointer, or function to bind to. te_variant_type m_value{ static_cast(0.0) }; /// @brief Additional parameters. std::vector m_parameters{ nullptr }; }; /// @brief Custom variable or function that can be added to a te_parser. class te_variable { public: /// @private using name_type = std::string; /// @private [[nodiscard]] bool operator<(const te_variable& that) const { return te_string_less{}(m_name, that.m_name); } /// @brief The name as it would appear in a formula. name_type m_name; /// @brief The te_type constant, te_type pointer, or function to bind the name to. te_variant_type m_value; /// @brief The type that m_value represents. te_variable_flags m_type{ TE_DEFAULT }; /// If @c m_value is a function pointer of type `te_confun0`-`te_confun24`, then /// this is passed to that function when called. This is useful for passing /// an object which manages additional data to your functions. te_expr* m_context{ nullptr }; }; /// @brief Math formula parser. class te_parser { public: /// @private te_parser() = default; /// @private // cppcheck-suppress uninitMemberVar te_parser(const te_parser& that) : m_customFuncsAndVars(that.m_customFuncsAndVars), m_unknownSymbolResolve(that.m_unknownSymbolResolve), m_keepResolvedVariables(that.m_keepResolvedVariables), m_decimalSeparator(that.m_decimalSeparator), m_listSeparator(that.m_listSeparator), m_expression(that.m_expression) { try { if (!m_expression.empty()) { [[maybe_unused]] const auto retVal = evaluate(m_expression); } } catch (const std::exception& expt) { m_parseSuccess = false; m_result = te_nan; m_lastErrorMessage = expt.what(); } } /// @private te_parser& operator=(const te_parser& that) { m_customFuncsAndVars = that.m_customFuncsAndVars; m_unknownSymbolResolve = that.m_unknownSymbolResolve; m_keepResolvedVariables = that.m_keepResolvedVariables; m_decimalSeparator = that.m_decimalSeparator; m_listSeparator = that.m_listSeparator; m_expression = that.m_expression; // re-run the expression that was copied over reset_state(); try { if (!m_expression.empty()) { [[maybe_unused]] const auto retVal = evaluate(m_expression); } } catch (const std::exception& expt) { m_parseSuccess = false; m_result = te_nan; m_lastErrorMessage = expt.what(); } return *this; } /// @private ~te_parser() { te_free(m_compiledExpression); } /// @brief NaN (not-a-number) constant to indicate an invalid value. constexpr static auto te_nan = std::numeric_limits::quiet_NaN(); /// @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 { return std::numeric_limits::digits >= std::numeric_limits::digits; } /// @returns @c true if the parser's internal type can hold `uint64_t` without truncation. [[nodiscard]] constexpr static bool supports_64bit() noexcept { return std::numeric_limits::digits >= std::numeric_limits::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::digits; } /// @returns The largest integer value that the parser can handle without truncation. [[nodiscard]] static te_type get_max_integer() { #ifdef TE_FLOAT const te_type maxBit = std::ldexp(1, FLT_MANT_DIG - 1); #elif defined(TE_LONG_DOUBLE) const te_type maxBit = std::ldexp(1, LDBL_MANT_DIG - 1); #else const te_type maxBit = std::ldexp(1, DBL_MANT_DIG - 1); #endif return maxBit + (maxBit - 1); } /** @brief Parses the input @c expression. @param expression The formula to compile. @returns Whether the expression compiled or not. (This can be checked by calling success() afterward as well.) @sa success(). @note Returns NaN if division or modulus by zero occurs. @throws std::runtime_error Throws an exception in the case of arithmetic overflows (e.g., `1 << 64` would cause an overflow).*/ bool compile(std::string_view expression); /** @brief Evaluates expression passed to compile() previously and returns its result. @returns The result, or NaN on error. @throws std::runtime_error Throws an exception in the case of arithmetic overflows (e.g., `1 << 64` would cause an overflow).*/ [[nodiscard]] te_type evaluate(); /** @brief Compiles and evaluates an expression and returns its result. @param expression The formula to compile and evaluate. @returns The result, or NaN on error. @note Returns NaN if division or modulus by zero occurs. @throws std::runtime_error Throws an exception in the case of arithmetic overflows (e.g., `1 << 64` would cause an overflow).*/ [[nodiscard]] te_type evaluate(std::string_view expression); /// @returns The last call to evaluate()'s result (which will be NaN on error). [[nodiscard]] te_type get_result() const noexcept { return m_result; } /// @private [[nodiscard]] te_type get_result() const volatile noexcept { return m_result; } /// @returns Whether the last call to compile() was successful. /// @sa get_last_error_position(). [[nodiscard]] bool success() const noexcept { return m_parseSuccess; } [[nodiscard]] bool success() const volatile noexcept { return m_parseSuccess; } /// @returns The zero-based index into the last parsed expression where the parse failed, /// or te_parser::npos if no error occurred. /// @note Call success() to see if the last parse succeeded or not. [[nodiscard]] int64_t get_last_error_position() const noexcept { return m_errorPos; } /// @private [[nodiscard]] int64_t get_last_error_position() const volatile noexcept { return m_errorPos; } /// @returns Any error message from the last parse. [[nodiscard]] const std::string& get_last_error_message() const noexcept { return m_lastErrorMessage; } /// @brief Sets the list of custom variables and functions. /// @param vars The list of variables and functions. /// @note Valid variable and function names must begin with a letter from a-z (A-Z), /// followed by additional English letters, numbers, periods, or underscores. /// @throws std::runtime_error Throws an exception if an illegal character is found /// in any variable name. void set_variables_and_functions(std::set vars) { for (const auto& var : vars) { validate_name(var); } m_customFuncsAndVars = std::move(vars); } /// @brief Adds a custom variable or function. /// @param var The variable/function to add. /// @note Prefer using set_variables_and_functions() as it will be more optimal /// (fewer sorts will need to be performed). /// @throws std::runtime_error Throws an exception if an illegal character is found /// in the variable name. void add_variable_or_function(te_variable var) { validate_name(var); m_customFuncsAndVars.insert(std::move(var)); } /// @brief Removes a custom variable or function. /// @param var The variable/function to remove (by name). void remove_variable_or_function(te_variable::name_type var) { const auto foundVar = m_customFuncsAndVars.find( te_variable{ std::move(var), static_cast(0.0), TE_DEFAULT, nullptr }); if (foundVar != m_customFuncsAndVars.cend()) { m_customFuncsAndVars.erase(foundVar); } } #ifndef TE_NO_BOOKKEEPING /// @brief Removes any custom variables and functions that weren't used in the last compilation. /// @details This can be useful if the parser is preloaded with a large number of /// variables and functions that needs to be pruned after the first expression is parsed. /// @warning After calling this, any custom variables and functions that weren't found /// in the previously parsed expression will no longer be available. void remove_unused_variables_and_functions() { for (auto funcIter = m_customFuncsAndVars.cbegin(); funcIter != m_customFuncsAndVars.cend(); /* in loop*/) { funcIter = (is_function_used(funcIter->m_name) || is_variable_used(funcIter->m_name)) ? ++funcIter : m_customFuncsAndVars.erase(funcIter); } } #endif /** @brief Sets a custom function to resolve unknown symbols in an expression. @param usr The function to use to resolve unknown symbols. @param keepResolvedVariables @c true to cache any resolved variables into the parser. This means that they will not need to be resolved again on subsequent calls to evaluate().\n Pass @c false to this if you wish to re-resolve any previously resolved variables on later evaluations. This can be useful for when a resolved variable's value is volatile and needs to be re-resolved on every use.*/ void set_unknown_symbol_resolver(te_usr_variant_type usr, const bool keepResolvedVariables = true) { m_unknownSymbolResolve = std::move(usr); m_keepResolvedVariables = keepResolvedVariables; } /// @private [[nodiscard]] const std::set& get_variables_and_functions() const noexcept { return m_customFuncsAndVars; } /// @returns The list of custom variables and functions. [[nodiscard]] std::set& get_variables_and_functions() noexcept { return m_customFuncsAndVars; } /// @returns The decimal separator used for numbers. [[nodiscard]] char get_decimal_separator() const noexcept { return m_decimalSeparator; } /// @private [[nodiscard]] char get_decimal_separator() const volatile noexcept { return m_decimalSeparator; } /// @brief Sets the decimal separator used for numbers. /// @param sep The decimal separator. /// @throws std::runtime_error Throws an exception if an illegal character is used. void set_decimal_separator(const char sep) { if (sep != ',' && sep != '.') { throw std::runtime_error("Decimal separator must be either a '.' or ','."); } m_decimalSeparator = sep; } /// @private void set_decimal_separator(const char sep) volatile { if (sep != ',' && sep != '.') { throw std::runtime_error("Decimal separator must be either a '.' or ','."); } m_decimalSeparator = sep; } /// @brief Sets a constant variable's value. /// @param name The name of the (constant) variable. /// @param value The new value to set the constant to. /// @note If the constant variable hasn't been added yet (via set_variables_and_functions()), /// then this will add it.\n /// If a variable with the provided name is found but is not a constant, /// then this will be ignored. void set_constant(const std::string_view name, const te_type value) { auto cvar = find_variable_or_function(name); if (cvar == get_variables_and_functions().end()) { add_variable_or_function({ te_variable::name_type{ name }, value }); } else if (is_constant(cvar->m_value)) { auto nh = get_variables_and_functions().extract(cvar); nh.value().m_value = value; get_variables_and_functions().insert(std::move(nh)); // if previously compiled, then re-compile since this // constant would have been optimized if (!m_expression.empty()) { compile(m_expression); } } } /// @brief Retrieves a constant variable's value. /// @param name The name of the (constant) variable. /// @returns The value of the constant variable if found, NaN otherwise. [[nodiscard]] te_type get_constant(const std::string_view name) const { auto cvar = find_variable_or_function(name); if (cvar == get_variables_and_functions().cend() || !is_constant(cvar->m_value)) { return te_nan; } if (const auto* const val = std::get_if(&cvar->m_value); val != nullptr) { return *val; } return te_nan; } /// @returns The separator used between function arguments. [[nodiscard]] char get_list_separator() const noexcept { return m_listSeparator; } /// @private [[nodiscard]] char get_list_separator() const volatile noexcept { return m_listSeparator; } /// @brief Sets the separator used between function arguments. /// @param sep The list separator. /// @throws std::runtime_error Throws an exception if an illegal character is used. void set_list_separator(const char sep) { if (sep != ',' && sep != ';') { throw std::runtime_error("List separator must be either a ',' or ';'."); } m_listSeparator = sep; } /// @private void set_list_separator(const char sep) volatile { if (sep != ',' && sep != ';') { throw std::runtime_error("List separator must be either a ',' or ';'."); } m_listSeparator = sep; } #ifndef TE_NO_BOOKKEEPING /// @returns @c true if @c name is a function that had been used in the last parsed formula. /// @param name The name of the function. /// @sa compile() and evaluate(). [[nodiscard]] bool is_function_used(const std::string_view name) const { return m_usedFunctions.contains(te_variable::name_type{ name }); } /// @returns @c true if @c name is a variable that had been used in the last parsed formula. /// @param name The name of the variable. /// @sa compile() and evaluate(). [[nodiscard]] bool is_variable_used(const std::string_view name) const { return m_usedVars.contains(te_variable::name_type{ name }); } #endif /// @returns A report of all available functions and variables. [[nodiscard]] std::string list_available_functions_and_variables(); /// @returns The last formula passed to the parser. /// @note Comments will be stripped from the original expression. [[nodiscard]] const std::string& get_expression() const noexcept { return m_expression; } /// @brief Helper function to convert a number into boolean. /// @details This takes NaN into account, returning @c for NaN. /// @param val The value to examine. /// @returns @c true if the value is non-zero and also valid /// (not NaN or infinite). /// @private [[nodiscard]] static bool number_to_bool(te_type val) { return std::isfinite(val) ? static_cast(val) : false; } /// @returns Information about how the parser is configured, its capabilities, etc. [[nodiscard]] static std::string info(); private: /// Reset everything from previous call. void reset_state() { m_errorPos = te_parser::npos; m_lastErrorMessage.clear(); m_result = te_nan; m_parseSuccess = false; te_free(m_compiledExpression); m_compiledExpression = nullptr; m_currentVar = m_functions.cend(); m_varFound = false; #ifndef TE_NO_BOOKKEEPING m_usedFunctions.clear(); m_usedVars.clear(); #endif m_resolvedVariables.clear(); } /// @brief Resets any resolved variables from USR if not being cached. void reset_usr_resolved_if_necessary() { if (!m_keepResolvedVariables && !m_resolvedVariables.empty()) { for (const auto& resolvedVar : m_resolvedVariables) { remove_variable_or_function(resolvedVar); } m_resolvedVariables.clear(); } } /// @brief Gets the compiled expression, which will be the optimized version /// of the original expression. /// @returns The compiled expression. [[nodiscard]] const te_expr* get_compiled_expression() const noexcept { return m_compiledExpression; } /// @private [[nodiscard]] const te_expr* get_compiled_expression() const volatile noexcept { return m_compiledExpression; } /// @brief Validates that a variable only contains legal characters /// (and has a valid length). /// @param var The variable to validate. /// @throws std::runtime_error Throws an exception if an illegal character is found. static void validate_name(const te_variable& var) { if (var.m_name.empty()) { throw std::runtime_error("Variable name is empty."); } if (!is_letter(var.m_name[0]) && var.m_name[0] != '_') { throw std::runtime_error( std::string("Variable name must begin with a letter from a-z or _: ") + var.m_name); } const auto varCharPos = std::find_if(var.m_name.cbegin(), var.m_name.cend(), [](const auto chr) noexcept { return !is_name_char_valid(chr); }); if (varCharPos != var.m_name.cend()) { throw std::runtime_error(std::string("Invalid character in variable name: ") + var.m_name); } } /// @returns @c true if character is valid for a function or variable name. /// @param chr The character to review. [[nodiscard]] constexpr static bool is_name_char_valid(const char chr) noexcept { return (is_letter(chr) || (chr >= '0' && chr <= '9') || (chr == '_') || (chr == '.')); } /// @returns An iterator to the custom variable or function with the given @c name, /// or end of get_variables_and_functions() if not found. /// @param name The name of the function or variable to search for. [[nodiscard]] std::set::iterator find_variable_or_function(const std::string_view name) { if (name.empty()) { return m_customFuncsAndVars.end(); } return m_customFuncsAndVars.find(te_variable{ te_variable::name_type{ name }, static_cast(0.0), TE_DEFAULT, nullptr }); } /// @returns An iterator to the custom variable or function with the given @c name, /// or end of get_variables_and_functions() if not found. /// @param name The name of the function or variable to search for. [[nodiscard]] std::set::const_iterator find_variable_or_function(const std::string_view name) const { if (name.empty()) { return m_customFuncsAndVars.cend(); } return m_customFuncsAndVars.find(te_variable{ te_variable::name_type{ name }, static_cast(0.0), TE_DEFAULT, nullptr }); } [[nodiscard]] constexpr static auto is_pure(const te_variable_flags type) { return (((type)&TE_PURE) != 0); } [[nodiscard]] constexpr static auto is_variadic(const te_variable_flags type) { return (((type)&TE_VARIADIC) != 0); } /// @returns Number of parameters that a function/variable takes. [[nodiscard]] static auto get_arity(const te_variant_type& var) { return std::visit( [](const auto& var0) -> size_t { using T = std::decay_t; if constexpr (te_is_constant_v || te_is_variable_v) { return 0; } else if constexpr (te_is_closure_v) { return te_function_arity - 1; // minus the first ctx variable. } else { // te_is_function_v return te_function_arity; } }, var); } [[nodiscard]] constexpr static bool is_constant(const te_variant_type& var) noexcept { return var.index() == 0; } [[nodiscard]] constexpr static te_type get_constant(const te_variant_type& var) { assert(std::holds_alternative(var)); return std::get<0>(var); } [[nodiscard]] constexpr static bool is_variable(const te_variant_type& var) noexcept { return var.index() == 1; } [[nodiscard]] constexpr static const te_type* get_variable(const te_variant_type& var) { assert(std::holds_alternative(var)); return std::get<1>(var); } [[nodiscard]] constexpr static bool is_function(const te_variant_type& var) { return std::visit( [](const auto& var0) -> bool { using T = std::decay_t; return te_is_function_v; }, var); } #define TE_DEF_FUNCTION(n) \ [[nodiscard]] \ constexpr static bool is_function##n(const te_variant_type& var) noexcept \ { \ return std::holds_alternative(var); \ } \ [[nodiscard]] \ constexpr static te_fun##n get_function##n(const te_variant_type& var) \ { \ assert(std::holds_alternative(var)); \ return std::get(var); \ } TE_DEF_FUNCTION(0); TE_DEF_FUNCTION(1); TE_DEF_FUNCTION(2); #undef TE_DEF_FUNCTION [[nodiscard]] constexpr static bool is_closure(const te_variant_type& var) { return std::visit( [](const auto& var0) -> bool { using T = std::decay_t; return te_is_closure::value; }, var); } #define TE_DEF_CLOSURE(n) \ [[nodiscard]] \ constexpr static bool is_closure##n(const te_variant_type& var) noexcept \ { \ return std::holds_alternative(var); \ } \ [[nodiscard]] \ constexpr static te_confun##n get_closure##n(const te_variant_type& var) \ { \ assert(std::holds_alternative(var)); \ return std::get(var); \ } TE_DEF_CLOSURE(0); TE_DEF_CLOSURE(1); TE_DEF_CLOSURE(2); #undef TE_DEF_CLOSURE struct state { enum class token_type { TOK_NULL, TOK_ERROR, TOK_END, TOK_SEP, TOK_OPEN, TOK_CLOSE, TOK_NUMBER, TOK_VARIABLE, TOK_FUNCTION, TOK_INFIX }; state(const char* expression, te_variable_flags varType, std::set& vars) : m_start(expression), m_next(expression), m_varType(varType), m_lookup(vars) { } const char* m_start{ nullptr }; const char* m_next{ nullptr }; token_type m_type{ token_type::TOK_NULL }; te_variable_flags m_varType{ TE_DEFAULT }; te_variant_type m_value; te_expr* context{ nullptr }; std::set& m_lookup; }; [[nodiscard]] static te_expr* new_expr(const te_variable_flags type, te_variant_type value, const std::initializer_list& parameters) { auto* ret = new te_expr{ type, value }; ret->m_parameters.resize( std::max(std::max(parameters.size(), get_arity(ret->m_value)) + (is_closure(ret->m_value) ? 1 : 0), 0)); if (parameters.size() != 0U) { std::ranges::copy(parameters, ret->m_parameters.begin()); } return ret; } [[nodiscard]] static te_expr* new_expr(const te_variable_flags type, te_variant_type value) { auto* ret = new te_expr{ type, value }; ret->m_parameters.resize(static_cast(get_arity(ret->m_value)) + (is_closure(ret->m_value) ? 1 : 0)); return ret; } [[nodiscard]] constexpr static bool is_letter(const char chr) noexcept { return (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z'); } /** @brief Parses the input expression and binds variables. @param expression The formula to parse. @param variables The collection of custom functions and variables to add to the parser. @returns null on error.*/ [[nodiscard]] te_expr* te_compile(std::string_view expression, std::set& variables); /* Evaluates the expression. */ [[nodiscard]] static te_type te_eval(const te_expr* texp); /* Frees the expression. */ /* This is safe to call on null pointers. */ static void te_free(te_expr* texp) { if (texp == nullptr) { return; } te_free_parameters(texp); delete texp; } static void te_free_parameters(te_expr* texp); static void optimize(te_expr* texp); [[nodiscard]] static auto find_builtin(const std::string_view name) { return m_functions.find(te_variable{ te_variable::name_type{ name }, static_cast(0.0), TE_DEFAULT, nullptr }); } [[nodiscard]] static auto find_lookup(state* ste, const std::string_view name) { return ste->m_lookup.find(te_variable{ te_variable::name_type{ name }, static_cast(0.0), TE_DEFAULT, nullptr }); } void next_token(state* theState); [[nodiscard]] te_expr* base(state* theState); [[nodiscard]] te_expr* power(state* theState); [[nodiscard]] te_expr* factor(state* theState); [[nodiscard]] te_expr* term(state* theState); [[nodiscard]] te_expr* expr_level1(state* theState); [[nodiscard]] te_expr* expr_level2(state* theState); [[nodiscard]] te_expr* expr_level3(state* theState); [[nodiscard]] te_expr* expr_level4(state* theState); [[nodiscard]] te_expr* expr_level5(state* theState); [[nodiscard]] te_expr* expr_level6(state* theState); [[nodiscard]] te_expr* expr_level7(state* theState); [[nodiscard]] te_expr* expr_level8(state* theState); [[nodiscard]] te_expr* expr_level9(state* theState); [[nodiscard]] te_expr* list(state* theState); // built-in functions static const std::set m_functions; // customizable settings std::set m_customFuncsAndVars; te_usr_variant_type m_unknownSymbolResolve{ te_usr_noop{} }; bool m_keepResolvedVariables{ true }; char m_decimalSeparator{ '.' }; char m_listSeparator{ ',' }; // state information std::string m_expression; te_expr* m_compiledExpression{ nullptr }; bool m_parseSuccess{ false }; int64_t m_errorPos{ 0 }; std::string m_lastErrorMessage; te_type m_result{ te_nan }; std::set m_resolvedVariables; std::set::const_iterator m_currentVar; bool m_varFound{ false }; #ifndef TE_NO_BOOKKEEPING std::set m_usedFunctions; std::set m_usedVars; #endif }; #endif // __TINYEXPR_PLUS_PLUS_H__