#include "Formula.h" #include #include "tinyexpr.h" namespace { // tinyexpr has no built-in min/max; expose them so config formulas can // clamp (e.g. a floored overclaim penalty "max(0.5, 1 - 0.1*x)"). double formulaMin(double a, double b) { return a < b ? a : b; } double formulaMax(double a, double b) { return a > b ? a : b; } } Formula::Formula(Formula&& other) noexcept : m_source(std::move(other.m_source)) , m_x(std::move(other.m_x)) , m_expr(other.m_expr) { other.m_expr = nullptr; } Formula& Formula::operator=(Formula&& other) noexcept { if (this != &other) { te_free(m_expr); m_source = std::move(other.m_source); m_x = std::move(other.m_x); m_expr = other.m_expr; other.m_expr = nullptr; } return *this; } Formula::~Formula() { te_free(m_expr); } Formula Formula::compile(const std::string& source) { Formula result; result.m_source = source; result.m_x = std::make_unique(0.0); const te_variable variables[] = { { "x", result.m_x.get(), TE_VARIABLE, nullptr }, { "min", reinterpret_cast(&formulaMin), TE_FUNCTION2 | TE_FLAG_PURE, nullptr }, { "max", reinterpret_cast(&formulaMax), TE_FUNCTION2 | TE_FLAG_PURE, nullptr }, }; int errorPos = 0; const int variableCount = static_cast(sizeof(variables) / sizeof(variables[0])); result.m_expr = te_compile(result.m_source.c_str(), variables, variableCount, &errorPos); if (result.m_expr == nullptr) { throw std::runtime_error( "Formula parse error at position " + std::to_string(errorPos) + " in \"" + source + "\""); } return result; } double Formula::evaluate(double x) const { if (m_expr == nullptr) { throw std::runtime_error("Formula::evaluate called on uninitialized formula"); } // The variable at *m_x is what the compiled tree dereferences during // te_eval. Mutating it here does not affect logical const-ness of the // formula — the formula itself is unchanged. *m_x = x; return te_eval(m_expr); }