// 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-2021 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. */ /* COMPILE TIME OPTIONS */ /* Exponentiation associativity: For a^b^c = (a^b)^c and -a^b = (-a)^b do nothing. For a^b^c = a^(b^c) and -a^b = -(a^b) uncomment the next line.*/ /* #define TE_POW_FROM_RIGHT */ /* Logarithms For log = base 10 log do nothing For log = natural log uncomment the next line. */ /* #define TE_NAT_LOG */ #include "tinyexpr.h" // builtin functions [[nodiscard]] constexpr static double _equal(double a, double b) noexcept { return (a == b) ? 1 : 0; } [[nodiscard]] constexpr static double _not_equal(double a, double b) noexcept { return (a != b) ? 1 : 0; } [[nodiscard]] constexpr static double _less_than(double a, double b) noexcept { return (a < b) ? 1 : 0; } [[nodiscard]] constexpr static double _less_than_equal_to(double a, double b) noexcept { return (a <= b) ? 1 : 0; } [[nodiscard]] constexpr static double _greater_than(double a, double b) noexcept { return (a > b) ? 1 : 0; } [[nodiscard]] constexpr static double _greater_than_equal_to(double a, double b) noexcept { return (a >= b) ? 1 : 0; } [[nodiscard]] constexpr static double _and(double a, double b) noexcept { return (a && b) ? 1 : 0; } [[nodiscard]] constexpr static double _or(double a, double b) noexcept { return (a || b) ? 1 : 0; } [[nodiscard]] constexpr static double _not(double a) noexcept { return !a; } [[nodiscard]] constexpr static double pi() noexcept { return 3.14159265358979323846; } [[nodiscard]] constexpr static double e() noexcept { return 2.71828182845904523536; } [[nodiscard]] static double fac(double a) noexcept {/* simplest version of factorial */ if (a < 0.0 || std::isnan(a)) return std::numeric_limits::quiet_NaN(); if (a > std::numeric_limits::max()) return std::numeric_limits::infinity(); const auto ua = static_cast(a); unsigned long int result{ 1 }, i{ 1 }; for (i = 1; i <= ua; i++) { if (i > std::numeric_limits::max() / result) return std::numeric_limits::infinity(); result *= i; } return static_cast(result); } [[nodiscard]] static double random() { std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution<> distr(0.0, 1.0); return distr(gen); } [[nodiscard]] constexpr static double divide(double a, double b) { if (b == 0) { throw std::exception("Division by zero."); } return a / b; } [[nodiscard]] static double modulus(double a, double b) { if (b == 0) { throw std::exception("Modulus by zero."); } return std::fmod(a,b); } [[nodiscard]] static double sum(double v1, double v2, double v3, double v4, double v5, double v6, double v7) noexcept { return (std::isnan(v1) ? 0 : v1) + (std::isnan(v2) ? 0 : v2) + (std::isnan(v3) ? 0 : v3) + (std::isnan(v4) ? 0 : v4) + (std::isnan(v5) ? 0 : v5) + (std::isnan(v6) ? 0 : v6) + (std::isnan(v7) ? 0 : v7); } [[nodiscard]] static double average(double v1, double v2, double v3, double v4, double v5, double v6, double v7) noexcept { const auto validN = (std::isnan(v1) ? 0 : 1) + (std::isnan(v2) ? 0 : 1) + (std::isnan(v3) ? 0 : 1) + (std::isnan(v4) ? 0 : 1) + (std::isnan(v5) ? 0 : 1) + (std::isnan(v6) ? 0 : 1) + (std::isnan(v7) ? 0 : 1); const auto total = sum(v1, v2, v3, v4, v5, v6, v7); return divide(total, validN); } [[nodiscard]] static double _round(double val, double decimal_places) noexcept { const size_t decimalPlaces{ std::isnan(decimal_places) ? 0 : static_cast(decimal_places) }; const size_t decimalPostition = (decimalPlaces == 0) ? 0 : (decimalPlaces == 1) ? 10 : (decimalPlaces == 2) ? 100 : (decimalPlaces == 3) ? 1000 : (decimalPlaces == 4) ? 10000 : (decimalPlaces == 5) ? 100000 : (decimalPlaces >= 6) ? 1000000 : 10; if (val < 0) { return (decimalPostition == 0) ? std::ceil(val -0.5f) : std::ceil((val * decimalPostition) - 0.5f)/decimalPostition; } else { return (decimalPostition == 0) ? std::floor(val +0.5f) : std::floor((val * decimalPostition) + 0.5f)/decimalPostition; } } // Combinations (without repetition) [[nodiscard]] static double ncr(double n, double r) noexcept { if (n < 0.0 || r < 0.0 || n < r || std::isnan(n) || std::isnan(r)) return std::numeric_limits::quiet_NaN(); if (n > std::numeric_limits::max() || r > std::numeric_limits::max()) return std::numeric_limits::infinity(); const unsigned long int un = static_cast(n); unsigned long int ur = static_cast(r); unsigned long int result = 1; if (ur > un / 2) ur = un - ur; for (decltype(ur) i = 1; i <= ur; i++) { if (result > std::numeric_limits::max() / (un - ur + i)) return std::numeric_limits::infinity(); result *= un - ur + i; result /= i; } return static_cast(result); } // Permutations (without repetition) [[nodiscard]] static double npr(double n, double r) noexcept { return ncr(n, r) * fac(r); } [[nodiscard]] constexpr static double add(double a, double b) noexcept { return a + b; } [[nodiscard]] constexpr static double sub(double a, double b) noexcept { return a - b; } [[nodiscard]] constexpr static double mul(double a, double b) noexcept { return a * b; } [[nodiscard]] constexpr static double _sqr(double a) noexcept { return a*a; } [[nodiscard]] static double _max_maybe_nan(double v1, double v2_maybe_nan) noexcept { return std::max(v1, std::isnan(v2_maybe_nan) ? v1 : v2_maybe_nan); } [[nodiscard]] static double _max(double v1, double v2, double v3, double v4, double v5, double v6, double v7) noexcept { // assumes that at least v1 is a number, rest can be NaN auto maxVal = _max_maybe_nan(v1, v2); maxVal = _max_maybe_nan(maxVal, v3); maxVal = _max_maybe_nan(maxVal, v4); maxVal = _max_maybe_nan(maxVal, v5); maxVal = _max_maybe_nan(maxVal, v6); return _max_maybe_nan(maxVal, v7); } [[nodiscard]] static double _min_maybe_nan(double v1, double v2_maybe_nan) noexcept { return std::min(v1, std::isnan(v2_maybe_nan) ? v1 : v2_maybe_nan); } [[nodiscard]] static double _min(double v1, double v2, double v3, double v4, double v5, double v6, double v7) noexcept { // assumes that at least v1 is legit, rest can be NaN auto minVal = _min_maybe_nan(v1, v2); minVal = _min_maybe_nan(minVal, v3); minVal = _min_maybe_nan(minVal, v4); minVal = _min_maybe_nan(minVal, v5); minVal = _min_maybe_nan(minVal, v6); return _min_maybe_nan(minVal, v7); } [[nodiscard]] static double _and_maybe_nan(double v1, double v2_maybe_nan) noexcept { return std::isnan(v2_maybe_nan) ? v1 : (v1 && v2_maybe_nan); } [[nodiscard]] static double _and_variadic(double v1, double v2, double v3, double v4, double v5, double v6, double v7) noexcept { // assumes that at least v1 is legit, rest can be NaN auto andVal = _and_maybe_nan(v1, v2); andVal = _and_maybe_nan(andVal, v3); andVal = _and_maybe_nan(andVal, v4); andVal = _and_maybe_nan(andVal, v5); andVal = _and_maybe_nan(andVal, v6); return _and_maybe_nan(andVal, v7); } [[nodiscard]] static double _or_maybe_nan(double v1, double v2_maybe_nan) noexcept { return std::isnan(v2_maybe_nan) ? v1 : (v1 || v2_maybe_nan); } [[nodiscard]] static double _or_variadic(double v1, double v2, double v3, double v4, double v5, double v6, double v7) noexcept { // assumes that at least v1 is legit, rest can be NaN auto orVal = _or_maybe_nan(v1, v2); orVal = _or_maybe_nan(orVal, v3); orVal = _or_maybe_nan(orVal, v4); orVal = _or_maybe_nan(orVal, v5); orVal = _or_maybe_nan(orVal, v6); return _or_maybe_nan(orVal, v7); } [[nodiscard]] constexpr static double _if(double a, double b, double c) noexcept { return (a != 0.0) ? b : c; } // cotangent [[nodiscard]] static double cot(double a) noexcept { if (a == 0.0) { return std::numeric_limits::quiet_NaN(); } return 1 / std::tan(a); } [[nodiscard]] constexpr static double sign(double a) noexcept { return (a < 0.0) ? -1 : (a > 0.0) ? 1 : 0; } [[nodiscard]] constexpr static double negate(double a) noexcept { return -a; } [[nodiscard]] constexpr static double comma([[maybe_unused]] double a, double b) noexcept { return b; } te_expr* te_parser::new_expr(const variable_flags type, const variant_type& value, const std::initializer_list parameters) { const auto arity = get_arity(value); te_expr* ret = new te_expr; ret->m_parameters.resize( std::max( std::max(parameters.size(), arity) + (is_closure(value) ? 1 : 0), 1) ); if (parameters.size()) { std::copy(parameters.begin(), parameters.end(), ret->m_parameters.begin()); } ret->m_type = type; ret->m_value = 0; return ret; } void te_parser::te_free_parameters(te_expr *n) { if (!n) return; if (is_closure(n->m_value)) { // last param is the context object, we don't manage that here for (auto param = n->m_parameters.begin(); param != n->m_parameters.end() - 1; ++param) { te_free(*param); *param = nullptr; } } else if (is_function(n->m_value)) { for (auto param = n->m_parameters.begin(); param != n->m_parameters.end(); ++param) { te_free(*param); *param = nullptr; } } } void te_parser::te_free(te_expr *n) { if (!n) return; te_free_parameters(n); delete n; } const std::vector te_parser::m_functions = { /* must be in alphabetical order */ {"abs", static_cast(std::fabs), TE_PURE}, {"acos", static_cast(std::acos), TE_PURE}, {"and", static_cast(_and_variadic), static_cast(TE_PURE|TE_VARIADIC)}, // variadic, accepts 1-7 arguments {"asin", static_cast(std::asin), TE_PURE}, {"atan", static_cast(std::atan), TE_PURE}, {"atan2", static_cast(std::atan2), TE_PURE}, {"average", static_cast(average), static_cast(TE_PURE|TE_VARIADIC)}, {"ceil", static_cast(std::ceil), TE_PURE}, {"clamp", static_cast( [](const double num, const double start, const double end) { if (start > end) { throw std::exception("Error in CLAMP: start of range cannot be larger than end of range."); } return std::clamp(num, start, end); }), TE_PURE}, {"combin", static_cast(ncr), TE_PURE}, {"cos", static_cast(std::cos), TE_PURE}, {"cosh", static_cast(std::cosh), TE_PURE}, {"cot", static_cast(cot), TE_PURE}, {"e", static_cast(e), TE_PURE}, {"exp", static_cast(std::exp), TE_PURE}, {"fac", static_cast(fac), TE_PURE}, {"fact", static_cast(fac), TE_PURE}, {"floor", static_cast(std::floor), TE_PURE}, {"if", static_cast(_if), TE_PURE}, {"ln", static_cast(std::log), TE_PURE}, #ifdef TE_NAT_LOG {"log", static_cast(std::log), TE_PURE}, #else {"log", static_cast(std::log10), TE_PURE}, #endif {"log10", static_cast(std::log10), TE_PURE}, {"max", static_cast(_max), static_cast(TE_PURE|TE_VARIADIC)}, {"min", static_cast(_min), static_cast(TE_PURE|TE_VARIADIC)}, {"mod", static_cast(modulus), TE_PURE}, {"ncr", static_cast(ncr), TE_PURE}, {"not", static_cast(_not), TE_PURE}, {"npr", static_cast(npr), TE_PURE}, {"or", static_cast(_or_variadic), static_cast(TE_PURE|TE_VARIADIC)}, {"permut", static_cast(npr), TE_PURE}, {"pi", static_cast(pi), TE_PURE}, {"pow", static_cast(std::pow), TE_PURE}, {"power",/* Excel alias*/ static_cast(std::pow), TE_PURE}, {"rand", static_cast(random), TE_PURE}, {"round", static_cast(_round), static_cast(TE_PURE|TE_VARIADIC)}, {"sign", static_cast(sign), TE_PURE}, {"sin", static_cast(std::sin), TE_PURE}, {"sinh", static_cast(std::sinh), TE_PURE}, {"sqr", static_cast(_sqr), TE_PURE}, {"sqrt", static_cast(std::sqrt), TE_PURE}, {"sum", static_cast(sum), static_cast(TE_PURE|TE_VARIADIC)}, {"tan", static_cast(std::tan), TE_PURE}, {"tanh", static_cast(std::tanh), TE_PURE}, {"trunc", static_cast(std::trunc), TE_PURE} }; void te_parser::next_token(te_parser::state *s) { assert(s); if (!s) { return; } s->m_type = te_parser::state::token_type::TOK_NULL; do { if (!*s->m_next) { s->m_type = te_parser::state::token_type::TOK_END; return; } /* Try reading a number. */ if ((s->m_next[0] >= '0' && s->m_next[0] <= '9') || s->m_next[0] == get_decimal_separator()) { char* nEnd{ nullptr }; s->m_value = std::strtod(s->m_next, &nEnd); s->m_next = nEnd; s->m_type = te_parser::state::token_type::TOK_NUMBER; } else { /* Look for a variable or builtin function call. */ if (is_letter(s->m_next[0])) { const char* start = s->m_next; while (is_letter(s->m_next[0]) || (s->m_next[0] >= '0' && s->m_next[0] <= '9') || (s->m_next[0] == '_')) s->m_next++; m_varFound = false; m_currentVar = find_lookup(s, start, s->m_next - start); if (m_currentVar != s->m_lookup.cend()) { m_varFound = true; } else { m_currentVar = find_builtin(start, s->m_next - start); if (m_currentVar != m_functions.cend()) { m_varFound = true; } } if (!m_varFound) { s->m_type = te_parser::state::token_type::TOK_ERROR; } else { // keep track of what's been used in the formula if (is_function(m_currentVar->m_value) || is_closure(m_currentVar->m_value)) { m_usedFunctions.insert(m_currentVar->m_name); } else { m_usedVars.insert(m_currentVar->m_name); } if (is_constant(m_currentVar->m_value)) { s->m_type = te_parser::state::token_type::TOK_NUMBER; s->m_value = m_currentVar->m_value; } else if (is_variable(m_currentVar->m_value)) { s->m_type = te_parser::state::token_type::TOK_VARIABLE; s->m_value = m_currentVar->m_value; } else if (is_closure(m_currentVar->m_value)) { s->context = m_currentVar->m_context; s->m_type = te_parser::state::token_type::TOK_FUNCTION; s->m_varType = m_currentVar->m_type; s->m_value = m_currentVar->m_value; } else if (is_function(m_currentVar->m_value)) { s->m_type = te_parser::state::token_type::TOK_FUNCTION; s->m_varType = m_currentVar->m_type; s->m_value = m_currentVar->m_value; } } } else { /* Look for an operator or special character. */ const auto tok = s->m_next++[0]; if (tok == '+') { s->m_type = te_parser::state::token_type::TOK_INFIX; s->m_value = add; } else if (tok == '-') { s->m_type = te_parser::state::token_type::TOK_INFIX; s->m_value = sub; } else if (tok == '*') { s->m_type = te_parser::state::token_type::TOK_INFIX; s->m_value = mul; } else if (tok == '/') { s->m_type = te_parser::state::token_type::TOK_INFIX; s->m_value = divide; } else if (tok == '^') { s->m_type = te_parser::state::token_type::TOK_INFIX; s->m_value = static_cast(std::pow); } else if (tok == '%') { s->m_type = te_parser::state::token_type::TOK_INFIX; s->m_value = modulus; } else if (tok == '(') { s->m_type = te_parser::state::token_type::TOK_OPEN; } else if (tok == ')') { s->m_type = te_parser::state::token_type::TOK_CLOSE; } else if (tok == get_list_separator()) { s->m_type = te_parser::state::token_type::TOK_SEP; } // logical operators else if (tok == '=') { s->m_type = te_parser::state::token_type::TOK_INFIX; s->m_value = static_cast(_equal); } else if (tok == '<' && s->m_next[0] == '>') { s->m_type = te_parser::state::token_type::TOK_INFIX; s->m_value = static_cast(_not_equal); ++s->m_next; } else if (tok == '<' && s->m_next[0] == '=') { s->m_type = te_parser::state::token_type::TOK_INFIX; s->m_value = static_cast(_less_than_equal_to); ++s->m_next; } else if (tok == '<') { s->m_type = te_parser::state::token_type::TOK_INFIX; s->m_value = static_cast(_less_than); } else if (tok == '>' && s->m_next[0] == '=') { s->m_type = te_parser::state::token_type::TOK_INFIX; s->m_value = static_cast(_greater_than_equal_to); ++s->m_next; } else if (tok == '>') { s->m_type = te_parser::state::token_type::TOK_INFIX; s->m_value = static_cast(_greater_than); } else if (tok == '&') { s->m_type = te_parser::state::token_type::TOK_INFIX; s->m_value = static_cast(_and); } else if (tok == '|') { s->m_type = te_parser::state::token_type::TOK_INFIX; s->m_value = static_cast(_or); } else if (tok == ' ' || tok == '\t' || tok == '\n' || tok == '\r') { /*noop*/ } else { s->m_type = te_parser::state::token_type::TOK_ERROR; } } } } while (s->m_type == te_parser::state::token_type::TOK_NULL); } te_expr* te_parser::base(te_parser::state *s) { /* = | | {"(" ")"} | | "(" {"," } ")" | "(" ")" */ te_expr* ret{ nullptr }; int arity{ 0 }; if (s->m_type == te_parser::state::token_type::TOK_OPEN) { next_token(s); ret = list(s); if (s->m_type != te_parser::state::token_type::TOK_CLOSE) { s->m_type = te_parser::state::token_type::TOK_ERROR; } else { next_token(s); } } else if (s->m_type == te_parser::state::token_type::TOK_NUMBER) { ret = new_expr(TE_DEFAULT, s->m_value, {}); ret->m_value = s->m_value; next_token(s); } else if (s->m_type == te_parser::state::token_type::TOK_VARIABLE) { ret = new_expr(TE_DEFAULT, s->m_value, {}); ret->m_value = s->m_value; next_token(s); } else if (s->m_type == te_parser::state::token_type::TOK_NULL || s->m_type == te_parser::state::token_type::TOK_ERROR || s->m_type == te_parser::state::token_type::TOK_END || s->m_type == te_parser::state::token_type::TOK_SEP || s->m_type == te_parser::state::token_type::TOK_CLOSE || s->m_type == te_parser::state::token_type::TOK_INFIX) { ret = new_expr(TE_DEFAULT, variant_type(static_cast(0.0)), {}); s->m_type = te_parser::state::token_type::TOK_ERROR; ret->m_value = std::numeric_limits::quiet_NaN(); } else if (is_function0(s->m_value) || is_closure0(s->m_value)) { ret = new_expr(s->m_varType, s->m_value, {}); ret->m_value = s->m_value; if (is_closure(s->m_value)) ret->m_parameters[0] = s->context; next_token(s); if (s->m_type == te_parser::state::token_type::TOK_OPEN) { next_token(s); if (s->m_type != te_parser::state::token_type::TOK_CLOSE) { s->m_type = te_parser::state::token_type::TOK_ERROR; } else { next_token(s); } } } else if (is_function1(s->m_value) || is_closure1(s->m_value)) { ret = new_expr(s->m_varType, s->m_value, { nullptr }); ret->m_value = s->m_value; if (is_closure(s->m_value)) ret->m_parameters[1] = s->context; next_token(s); ret->m_parameters[0] = power(s); } else if (is_function2(s->m_value) || is_closure2(s->m_value) || is_function3(s->m_value) || is_closure3(s->m_value) || is_function4(s->m_value) || is_closure4(s->m_value) || is_function5(s->m_value) || is_closure5(s->m_value) || is_function6(s->m_value) || is_closure6(s->m_value) || is_function7(s->m_value) || is_closure7(s->m_value)) { arity = get_arity(s->m_value); ret = new_expr(s->m_varType, s->m_value, {}); ret->m_value = s->m_value; if (is_closure(s->m_value)) ret->m_parameters[arity] = s->context; next_token(s); if (s->m_type != te_parser::state::token_type::TOK_OPEN) { s->m_type = te_parser::state::token_type::TOK_ERROR; } else { int i{ 0 }; // If there are vars or other functions in the parameters, keep track of the original // opening function; that is what we will do our variadic check on. const auto varValid{ m_varFound }; const auto openingVar = m_currentVar; // load any parameters for(i = 0; i < arity; i++) { next_token(s); ret->m_parameters[i] = expr(s); if(s->m_type != te_parser::state::token_type::TOK_SEP) { break; } } if (s->m_type == te_parser::state::token_type::TOK_CLOSE && i != arity - 1 && varValid && is_variadic(openingVar->m_type)) { next_token(s); } else if(s->m_type != te_parser::state::token_type::TOK_CLOSE || i != arity - 1) { s->m_type = te_parser::state::token_type::TOK_ERROR; } else { next_token(s); } } } return ret; } te_expr* te_parser::power(te_parser::state *s) { /* = {("-" | "+" | "&" | "|")} */ int Sign{ 1 }; while (s->m_type == te_parser::state::token_type::TOK_INFIX && is_function2(s->m_value) && (get_function2(s->m_value) == add || get_function2(s->m_value) == sub || get_function2(s->m_value) == _and || get_function2(s->m_value) == _or || get_function2(s->m_value) == _equal || get_function2(s->m_value) == _not_equal || get_function2(s->m_value) == _less_than || get_function2(s->m_value) == _less_than_equal_to || get_function2(s->m_value) == _greater_than || get_function2(s->m_value) == _greater_than_equal_to)) { if (get_function2(s->m_value) == sub) Sign = -Sign; next_token(s); } te_expr* ret{ nullptr }; if (Sign == 1) { ret = base(s); } else { ret = new_expr(TE_PURE, variant_type(negate), { base(s) }); ret->m_value = negate; } return ret; } #ifdef TE_POW_FROM_RIGHT te_expr *factor(te_parser::state *s) { /* = {"^" } */ te_expr* ret = power(s); int neg = 0; if (ret->m_type == (TE_PURE) && is_function1(ret->m_value) && get_function1(ret->m_value) == negate) { te_expr *se = ret->m_parameters[0]; free(ret); ret = se; neg = 1; } te_expr *insertion = 0; while (s->m_type == te_parser::state::token_type::TOK_INFIX && is_function2(ret->m_value) && (get_function2(ret->m_value) == static_cast(std::pow))) { const te_fun2 t = get_function2(s->m_value); next_token(s); if (insertion) { /* Make exponentiation go right-to-left. */ te_expr* insert = new_expr(TE_PURE, t, { insertion->m_parameters[1], power(s) }); insert->m_value = t; insertion->m_parameters[1] = insert; insertion = insert; } else { ret = new_expr(TE_PURE, t, { ret, power(s) }); ret->m_value = t; insertion = ret; } } if (neg) { ret = new_expr(TE_PURE, variant_type(negate), { ret }); ret->m_value = negate; } return ret; } #else te_expr* te_parser::factor(te_parser::state *s) { /* = {"^" } */ te_expr* ret = power(s); while (s->m_type == te_parser::state::token_type::TOK_INFIX && is_function2(s->m_value) && (get_function2(s->m_value) == static_cast(std::pow))) { const te_fun2 t = get_function2(s->m_value); next_token(s); ret = new_expr(TE_PURE, t, { ret, power(s) }); ret->m_value = t; } return ret; } #endif te_expr* te_parser::term(te_parser::state *s) { /* = {("*" | "/" | "%") } */ te_expr* ret = factor(s); while (s->m_type == te_parser::state::token_type::TOK_INFIX && is_function2(s->m_value) && (get_function2(s->m_value) == mul || get_function2(s->m_value) == divide || get_function2(s->m_value) == modulus)) { const te_fun2 t = get_function2(s->m_value); next_token(s); ret = new_expr(TE_PURE, t, { ret, factor(s) }); ret->m_value = t; } return ret; } te_expr* te_parser::expr(te_parser::state *s) { /* = {("+" | "-" | "&" | "|") } */ te_expr* ret = term(s); while (s->m_type == te_parser::state::token_type::TOK_INFIX && is_function2(s->m_value) && (get_function2(s->m_value) == add || get_function2(s->m_value) == _and || get_function2(s->m_value) == _or || get_function2(s->m_value) == _equal || get_function2(s->m_value) == _not_equal || get_function2(s->m_value) == _less_than || get_function2(s->m_value) == _less_than_equal_to || get_function2(s->m_value) == _greater_than || get_function2(s->m_value) == _greater_than_equal_to || get_function2(s->m_value) == sub)) { const te_fun2 t = get_function2(s->m_value); next_token(s); ret = new_expr(TE_PURE, t, { ret, term(s) }); ret->m_value = t; } return ret; } te_expr* te_parser::list(te_parser::state *s) { /* = {"," } */ te_expr* ret = expr(s); while (s->m_type == te_parser::state::token_type::TOK_SEP) { next_token(s); ret = new_expr(TE_PURE, variant_type(comma), { ret, expr(s) }); ret->m_value = comma; } return ret; } double te_parser::te_eval(const te_expr *n) { if (!n) return std::numeric_limits::quiet_NaN(); const auto M = [&n = std::as_const(n)](const size_t e) constexpr noexcept { return (e < n->m_parameters.size()) ? te_eval(n->m_parameters[e]) : std::numeric_limits::quiet_NaN(); }; if (is_constant(n->m_value)) { return get_constant(n->m_value); } else if (is_variable(n->m_value)) { return *(get_variable(n->m_value)); } else if (is_function0(n->m_value)) { return get_function0(n->m_value)(); } else if (is_function1(n->m_value)) { return get_function1(n->m_value)( M(0) ); } else if (is_function2(n->m_value)) { return get_function2(n->m_value)(M(0), M(1)); } else if (is_function3(n->m_value)) { return get_function3(n->m_value)(M(0), M(1), M(2)); } else if (is_function4(n->m_value)) { return get_function4(n->m_value)(M(0), M(1), M(2), M(3)); } else if (is_function5(n->m_value)) { return get_function5(n->m_value)(M(0), M(1), M(2), M(3), M(4)); } else if (is_function6(n->m_value)) { return get_function6(n->m_value)(M(0), M(1), M(2), M(3), M(4), M(5)); } else if (is_function7(n->m_value)) { return get_function7(n->m_value)(M(0), M(1), M(2), M(3), M(4), M(5), M(6)); } else if (is_closure0(n->m_value)) { return get_closure0(n->m_value)(n->m_parameters[0]); } else if (is_closure1(n->m_value)) { return get_closure1(n->m_value)(n->m_parameters[1], M(0)); } else if (is_closure2(n->m_value)) { return get_closure2(n->m_value)(n->m_parameters[2], M(0), M(1)); } else if (is_closure3(n->m_value)) { return get_closure3(n->m_value)(n->m_parameters[3], M(0), M(1), M(2)); } else if (is_closure4(n->m_value)) { return get_closure4(n->m_value)(n->m_parameters[4], M(0), M(1), M(2), M(3)); } else if (is_closure5(n->m_value)) { return get_closure5(n->m_value)(n->m_parameters[5], M(0), M(1), M(2), M(3), M(4)); } else if (is_closure6(n->m_value)) { return get_closure6(n->m_value)(n->m_parameters[6], M(0), M(1), M(2), M(3), M(4), M(5)); } else if (is_closure7(n->m_value)) { return get_closure7(n->m_value)(n->m_parameters[7], M(0), M(1), M(2), M(3), M(4), M(5), M(6)); } else { return std::numeric_limits::quiet_NaN(); } } void te_parser::optimize(te_expr *n) { if (!n) return; /* Evaluates as much as possible. */ if (is_constant(n->m_value) || is_variable(n->m_value)) return; /* Only optimize out functions flagged as pure. */ if (is_pure(n->m_type)) { const int arity = get_arity(n->m_value); bool known{ true }; for (int i = 0; i < arity; ++i) { if (!n->m_parameters[i]) { break; } optimize(n->m_parameters[i]); if (!is_constant(n->m_parameters[i]->m_value)) { known = false; } } if (known) { const double value = te_eval(n); te_free_parameters(n); n->m_type = TE_DEFAULT; n->m_value = value; } } } te_expr* te_parser::te_compile(const char* expression, TE_RELEASE_CONST std::vector& variables) { state s(expression, TE_DEFAULT, variables); next_token(&s); te_expr* root = list(&s); if (s.m_type != te_parser::state::token_type::TOK_END) { te_free(root); m_errorPos = (s.m_next - s.m_start); if (m_errorPos > 0) --m_errorPos; return nullptr; } else { optimize(root); m_errorPos = -1; return root; } } bool te_parser::compile(const char* expression) { assert(expression && "compile() should not be called with null!"); // reset everything from previous call m_errorPos = -1; m_result = std::numeric_limits::quiet_NaN(); m_parseSuccess = false; te_free(m_compiledExpression); m_compiledExpression = nullptr; m_currentVar = m_functions.cend(); m_varFound = false; m_usedFunctions.clear(); m_usedVars.clear(); if (!expression) { m_expression.clear(); return std::numeric_limits::quiet_NaN(); } m_expression.assign(expression); m_compiledExpression = te_compile(expression, get_vars()); m_parseSuccess = (m_compiledExpression) ? true : false; return m_parseSuccess; } double te_parser::evaluate() { m_result = (m_compiledExpression) ? te_eval(m_compiledExpression) : std::numeric_limits::quiet_NaN(); return m_result; } double te_parser::evaluate(const char* expression) { if (compile(expression)) { return evaluate(); } else { return std::numeric_limits::quiet_NaN(); } } #ifndef NDEBUG void te_parser::te_print(const te_expr *n, int depth) { printf("%*s", depth, ""); if (is_function(n->m_value) || is_closure(n->m_value)) { int arity = get_arity(n->m_value); printf("f%d", arity); for (int i = 0; i < arity; i++) { printf(" %p", n->m_parameters[i]); } printf("\n"); for (int i = 0; i < arity; i++) { te_print(n->m_parameters[i], depth + 1); } } else if (is_constant(n->m_value)) { printf("%f\n", get_constant(n->m_value)); } else if (is_variable(n->m_value)) { printf("bound %p\n", get_variable(n->m_value)); } } #endif