mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-03-21 17:34:57 -05:00
Use exprtk lib to evaluate mathematical expressions
This commit is contained in:
parent
fe1ddc3a94
commit
2fdd39fba5
|
|
@ -343,7 +343,8 @@ target_include_directories(
|
|||
${LIB_NAME}
|
||||
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/deps/asio/asio/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deps/websocketpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deps/obs-websocket/lib")
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deps/obs-websocket/lib"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deps/exprtk")
|
||||
|
||||
# --- End of section ---
|
||||
|
||||
|
|
@ -354,7 +355,8 @@ if(OS_WINDOWS)
|
|||
|
||||
target_compile_definitions(${LIB_NAME} PRIVATE UNICODE _UNICODE)
|
||||
if(MSVC)
|
||||
target_compile_options(${LIB_NAME} PUBLIC /MP /d2FH4- /wd4267 /wd4267)
|
||||
target_compile_options(${LIB_NAME} PUBLIC /MP /d2FH4- /wd4267 /wd4267
|
||||
/bigobj)
|
||||
endif()
|
||||
target_sources(${LIB_NAME} PRIVATE src/win/advanced-scene-switcher-win.cpp)
|
||||
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
|
||||
|
|
|
|||
|
|
@ -943,10 +943,7 @@ AdvSceneSwitcher.process.addArgumentDescription="Add new argument:"
|
|||
AdvSceneSwitcher.process.entry="Run{{filePath}}{{advancedSettings}}"
|
||||
AdvSceneSwitcher.process.entry.workingDirectory="Working directory:{{workingDirectory}}"
|
||||
|
||||
AdvSceneSwitcher.math.notANumber="not a valid number"
|
||||
AdvSceneSwitcher.math.expressionFail="failed evaluate expression"
|
||||
AdvSceneSwitcher.math.expressionFailParentheses="failed evaluate expression (missing Parentheses?)"
|
||||
AdvSceneSwitcher.math.notFullyResolved="not all operands were used"
|
||||
AdvSceneSwitcher.math.expressionFail="Failed evaluate expression"
|
||||
|
||||
AdvSceneSwitcher.selectScene="--select scene--"
|
||||
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
|
||||
|
|
|
|||
41032
deps/exprtk/exprtk.hpp
vendored
Normal file
41032
deps/exprtk/exprtk.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -1,10 +1,5 @@
|
|||
#include "math-helpers.hpp"
|
||||
|
||||
#include <string_view>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <stack>
|
||||
#include <cmath>
|
||||
#include <exprtk.hpp>
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
const char *obs_module_text(const char *text)
|
||||
|
|
@ -15,313 +10,21 @@ const char *obs_module_text(const char *text)
|
|||
#include <obs-module.h>
|
||||
#endif
|
||||
|
||||
static std::vector<std::string_view> nonOperatorSeparators = {
|
||||
" ",
|
||||
"\t",
|
||||
};
|
||||
typedef exprtk::expression<double> expression_t;
|
||||
typedef exprtk::parser<double> parser_t;
|
||||
|
||||
static std::vector<std::string_view> brackets = {
|
||||
"(",
|
||||
")",
|
||||
};
|
||||
|
||||
static std::vector<std::string_view> unaryOperators = {
|
||||
"abs(", "sin(", "cos(", "tan(", "sqrt(",
|
||||
};
|
||||
|
||||
static std::vector<std::string_view> binaryOperators = {
|
||||
"+",
|
||||
"-",
|
||||
"*",
|
||||
"/",
|
||||
};
|
||||
|
||||
static bool isBracket(const std::string &token)
|
||||
std::variant<double, std::string> EvalMathExpression(const std::string &expr)
|
||||
{
|
||||
return std::find(brackets.begin(), brackets.end(), token) !=
|
||||
brackets.end();
|
||||
}
|
||||
expression_t expression;
|
||||
|
||||
static bool isUnaryOperator(const std::string &token)
|
||||
{
|
||||
return std::find(unaryOperators.begin(), unaryOperators.end(), token) !=
|
||||
unaryOperators.end();
|
||||
}
|
||||
parser_t parser;
|
||||
|
||||
static bool isBinaryOperator(const std::string &token)
|
||||
{
|
||||
return std::find(binaryOperators.begin(), binaryOperators.end(),
|
||||
token) != binaryOperators.end();
|
||||
}
|
||||
|
||||
static bool isOperator(const std::string &token)
|
||||
{
|
||||
return isUnaryOperator(token) || isBinaryOperator(token);
|
||||
}
|
||||
|
||||
static void addOperatorToken(std::vector<std::string> &tokens,
|
||||
const std::string &separator)
|
||||
{
|
||||
if (separator.back() != '(') {
|
||||
tokens.push_back(separator);
|
||||
return;
|
||||
if (parser.compile(expr, expression)) {
|
||||
return expression.value();
|
||||
}
|
||||
tokens.push_back("(");
|
||||
tokens.push_back(separator);
|
||||
}
|
||||
|
||||
static std::vector<std::string> splitStringIntoTokens(const std::string &input)
|
||||
{
|
||||
auto separators = nonOperatorSeparators;
|
||||
separators.insert(separators.end(), brackets.begin(), brackets.end());
|
||||
separators.insert(separators.end(), unaryOperators.begin(),
|
||||
unaryOperators.end());
|
||||
separators.insert(separators.end(), binaryOperators.begin(),
|
||||
binaryOperators.end());
|
||||
|
||||
std::vector<std::string> tokens;
|
||||
std::string::size_type start = 0;
|
||||
|
||||
while (start < input.length()) {
|
||||
std::string::size_type min_pos = std::string::npos;
|
||||
std::string separator;
|
||||
|
||||
// Find the next occurrence of each separator
|
||||
for (const auto &sep : separators) {
|
||||
std::string::size_type pos = input.find(sep, start);
|
||||
if (pos != std::string::npos && pos < min_pos) {
|
||||
min_pos = pos;
|
||||
separator = sep;
|
||||
}
|
||||
}
|
||||
|
||||
// If a separator was found, add the token before it to the vector
|
||||
if (min_pos != std::string::npos) {
|
||||
const auto token = input.substr(start, min_pos - start);
|
||||
start = min_pos + separator.length();
|
||||
|
||||
if (!std::all_of(token.begin(), token.end(), isspace)) {
|
||||
tokens.push_back(token);
|
||||
}
|
||||
|
||||
// If the separator itself was an operator, add it as a
|
||||
// token to the vector
|
||||
if (isOperator(separator)) {
|
||||
addOperatorToken(tokens, separator);
|
||||
}
|
||||
if (isBracket(separator)) {
|
||||
tokens.push_back(separator);
|
||||
}
|
||||
}
|
||||
// Otherwise, add the remaining string to the vector and exit the
|
||||
// loop
|
||||
else {
|
||||
tokens.push_back(input.substr(start));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
static int precedence(const std::string &op)
|
||||
{
|
||||
if (op == "*" || op == "/") {
|
||||
return 2;
|
||||
} else if (op == "+" || op == "-") {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static std::string getErrorMsg(const std::string &expr, const std::string &msg)
|
||||
{
|
||||
return std::string(obs_module_text(
|
||||
"AdvSceneSwitcher.math.expressionFail")) +
|
||||
" \"" + expr + "\":\n" + msg;
|
||||
}
|
||||
|
||||
static std::string getErrorMsg(const std::string &expr,
|
||||
std::stack<std::string> binaryOperators,
|
||||
std::stack<double> operands,
|
||||
const std::string &msg)
|
||||
{
|
||||
std::string errMsg = getErrorMsg(expr, msg);
|
||||
errMsg += "\n\n ---" +
|
||||
std::string(
|
||||
obs_module_text("AdvSceneSwitcher.math.operators")) +
|
||||
" ---\n";
|
||||
while (!binaryOperators.empty()) {
|
||||
errMsg += binaryOperators.top() + "\n";
|
||||
binaryOperators.pop();
|
||||
}
|
||||
|
||||
errMsg +=
|
||||
"\n\n --- " +
|
||||
std::string(obs_module_text("AdvSceneSwitcher.math.operands")) +
|
||||
" ---\n";
|
||||
while (!operands.empty()) {
|
||||
errMsg += std::to_string(operands.top()) + "\n";
|
||||
operands.pop();
|
||||
}
|
||||
|
||||
return errMsg;
|
||||
}
|
||||
|
||||
static bool evalHelper(std::stack<std::string> &operators,
|
||||
std::stack<double> &operands)
|
||||
{
|
||||
std::string op = operators.top();
|
||||
operators.pop();
|
||||
|
||||
if (isUnaryOperator(op)) {
|
||||
if (operands.size() < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double value = operands.top();
|
||||
operands.pop();
|
||||
double result;
|
||||
if (op == "abs(") {
|
||||
result = abs(value);
|
||||
} else if (op == "sin(") {
|
||||
result = sin(value);
|
||||
} else if (op == "cos(") {
|
||||
result = cos(value);
|
||||
} else if (op == "tan(") {
|
||||
result = cos(value);
|
||||
} else if (op == "sqrt(") {
|
||||
result = sqrt(value);
|
||||
}
|
||||
operands.push(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (operands.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
double op2 = operands.top();
|
||||
operands.pop();
|
||||
double op1 = operands.top();
|
||||
operands.pop();
|
||||
double result;
|
||||
|
||||
if (op == "+") {
|
||||
result = op1 + op2;
|
||||
} else if (op == "-") {
|
||||
result = op1 - op2;
|
||||
} else if (op == "*") {
|
||||
result = op1 * op2;
|
||||
} else if (op == "/") {
|
||||
result = op1 / op2;
|
||||
}
|
||||
operands.push(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::variant<double, std::string>
|
||||
EvalMathExpression(const std::string &expression)
|
||||
{
|
||||
// Create a stack to store operands and operators
|
||||
std::stack<double> operands;
|
||||
std::stack<std::string> operators;
|
||||
|
||||
auto tokens = splitStringIntoTokens(expression);
|
||||
|
||||
// Loop through each token in the expression
|
||||
for (const auto &token : tokens) {
|
||||
// If the token is a number, push it onto the operand stack
|
||||
if (isdigit(token[0])) {
|
||||
auto operand = GetDouble(token);
|
||||
if (!operand.has_value()) {
|
||||
return getErrorMsg(
|
||||
expression,
|
||||
"\"" + token + "\" " +
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.math.notANumber"));
|
||||
}
|
||||
operands.push(*operand);
|
||||
}
|
||||
// If the token is an operator, evaluate higher-precedence
|
||||
// operators first
|
||||
else if (isBinaryOperator(token)) {
|
||||
while (!operators.empty() &&
|
||||
precedence(operators.top()) >=
|
||||
precedence(token)) {
|
||||
if (operators.empty() || operands.empty()) {
|
||||
return getErrorMsg(expression,
|
||||
operators, operands,
|
||||
"");
|
||||
}
|
||||
if (!evalHelper(operators, operands)) {
|
||||
return getErrorMsg(expression,
|
||||
operators, operands,
|
||||
"");
|
||||
}
|
||||
}
|
||||
operators.push(token);
|
||||
}
|
||||
// If the token is an opening bracket, push it onto the
|
||||
// operator stack
|
||||
else if (token == "(") {
|
||||
operators.push(token);
|
||||
}
|
||||
// If the token is a closing bracket, evaluate the expression
|
||||
// inside the brackets
|
||||
else if (token == ")") {
|
||||
if (operators.empty()) {
|
||||
return getErrorMsg(
|
||||
expression, operators, operands,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.math.expressionFailParentheses"));
|
||||
}
|
||||
while (operators.top() != "(") {
|
||||
if (operators.empty() || operands.empty()) {
|
||||
return getErrorMsg(expression,
|
||||
operators, operands,
|
||||
"");
|
||||
}
|
||||
if (!evalHelper(operators, operands) ||
|
||||
operators.empty()) {
|
||||
return getErrorMsg(
|
||||
expression, operators, operands,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.math.expressionFailParentheses"));
|
||||
}
|
||||
}
|
||||
operators.pop(); // Pop the opening bracket
|
||||
|
||||
}
|
||||
|
||||
else if (isUnaryOperator(token)) {
|
||||
operators.push(token);
|
||||
}
|
||||
|
||||
else if (!token.empty()) {
|
||||
return getErrorMsg(
|
||||
expression,
|
||||
std::string(obs_module_text(
|
||||
"AdvSceneSwitcher.math.invalidToken")) +
|
||||
" \"" + token + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate any remaining operators in the stack
|
||||
while (!operators.empty()) {
|
||||
if (!evalHelper(operators, operands) || operands.empty()) {
|
||||
return getErrorMsg(expression, operators, operands, "");
|
||||
}
|
||||
}
|
||||
|
||||
// The result is the top operand on the stack
|
||||
if (operands.size() != 1) {
|
||||
return getErrorMsg(
|
||||
expression, operators, operands,
|
||||
std::string(obs_module_text(
|
||||
"AdvSceneSwitcher.math.notFullyResolved")));
|
||||
}
|
||||
return operands.top();
|
||||
" \"" + expr;
|
||||
}
|
||||
|
||||
bool IsValidNumber(const std::string &str)
|
||||
|
|
|
|||
|
|
@ -11,8 +11,10 @@ target_include_directories(
|
|||
${PROJECT_NAME}
|
||||
PRIVATE "${ADVSS_SOURCE_DIR}/src" "${ADVSS_SOURCE_DIR}/src/legacy"
|
||||
"${ADVSS_SOURCE_DIR}/src/macro-core" "${ADVSS_SOURCE_DIR}/src/utils"
|
||||
"${ADVSS_SOURCE_DIR}/forms")
|
||||
install(TARGETS advanced-scene-switcher-lib
|
||||
DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
"${ADVSS_SOURCE_DIR}/forms" "${ADVSS_SOURCE_DIR}/deps/exprtk")
|
||||
if(MSVC)
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC /MP /d2FH4- /wd4267 /wd4267
|
||||
/bigobj)
|
||||
endif()
|
||||
|
||||
enable_testing()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ TEST_CASE("Expressions are evaluated successfully", "[math-helpers]")
|
|||
REQUIRE(doubleValuePtr != nullptr);
|
||||
REQUIRE(*doubleValuePtr == 1.0);
|
||||
|
||||
expressionResult = EvalMathExpression("-1");
|
||||
doubleValuePtr = std::get_if<double>(&expressionResult);
|
||||
|
||||
REQUIRE(doubleValuePtr != nullptr);
|
||||
REQUIRE(*doubleValuePtr == -1.0);
|
||||
|
||||
expressionResult = EvalMathExpression("1 + 2");
|
||||
doubleValuePtr = std::get_if<double>(&expressionResult);
|
||||
|
||||
|
|
@ -29,8 +35,27 @@ TEST_CASE("Expressions are evaluated successfully", "[math-helpers]")
|
|||
REQUIRE(doubleValuePtr != nullptr);
|
||||
REQUIRE(*doubleValuePtr == 9.0);
|
||||
|
||||
expressionResult = EvalMathExpression("(1 - 2) * 3");
|
||||
doubleValuePtr = std::get_if<double>(&expressionResult);
|
||||
|
||||
REQUIRE(doubleValuePtr != nullptr);
|
||||
REQUIRE(*doubleValuePtr == -3.0);
|
||||
|
||||
expressionResult = EvalMathExpression("(1-2)*3");
|
||||
doubleValuePtr = std::get_if<double>(&expressionResult);
|
||||
|
||||
REQUIRE(doubleValuePtr != nullptr);
|
||||
REQUIRE(*doubleValuePtr == -3.0);
|
||||
|
||||
expressionResult =
|
||||
EvalMathExpression("cos(abs(1 - sqrt(10 - 2 * 3)) -1)");
|
||||
EvalMathExpression("cos(abs(1 - sqrt(10 - 2 * 3)) - 1)");
|
||||
doubleValuePtr = std::get_if<double>(&expressionResult);
|
||||
|
||||
REQUIRE(doubleValuePtr != nullptr);
|
||||
REQUIRE(*doubleValuePtr == 1.0);
|
||||
|
||||
expressionResult =
|
||||
EvalMathExpression("cos(abs(1 - sqrt(10 -2 * 3)) -1)");
|
||||
doubleValuePtr = std::get_if<double>(&expressionResult);
|
||||
|
||||
REQUIRE(doubleValuePtr != nullptr);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user