Compare commits

...

4 Commits

Author SHA1 Message Date
WarmUpTill
2fdd39fba5 Use exprtk lib to evaluate mathematical expressions 2023-04-01 05:37:40 -07:00
WarmUpTill
fe1ddc3a94 Fix connection selection being lost after renaming connection 2023-04-01 05:37:40 -07:00
WarmUpTill
cff2d805db Fix variable selection being lost after renaming variable 2023-04-01 05:37:40 -07:00
WarmUpTill
34425d3e47 CI: minor improvements
* Print checksums in log
* Log if tests were skipped
* Switch to gh-release action version 1
2023-03-27 12:37:08 -07:00
19 changed files with 41179 additions and 362 deletions

View File

@@ -16,6 +16,7 @@ runs:
shell: zsh {0}
run: |
if [[ '${{ inputs.target }}' != 'x86_64' ]]; then
echo tests skipped!
exit 0
fi
${{ inputs.workingDirectory }}/build_x86_64/tests/advanced-scene-switcher-tests

View File

@@ -425,10 +425,11 @@ jobs:
for file in ${{ github.workspace }}/**/@(*.pkg|*.exe|*.deb|*.zip); do
echo " ${file##*/}: $(sha256sum "${file}" | cut -d " " -f 1)" >> ${{ github.workspace }}/CHECKSUMS.txt
done
cat ${{ github.workspace }}/CHECKSUMS.txt
- name: Create Release
id: create_release
uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5
uses: softprops/action-gh-release@v1
with:
draft: true
prerelease: true

View File

@@ -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_)

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -538,8 +538,8 @@ void SwitcherData::loadSettings(obs_data_t *obj)
// selections to be available.
loadSceneGroups(obj);
loadVariables(obj);
loadMacros(obj);
loadConnections(obj);
loadMacros(obj);
loadWindowTitleSwitches(obj);
loadScreenRegionSwitches(obj);
loadPauseSwitches(obj);

View File

@@ -119,7 +119,7 @@ void MacroActionVariable::HandleMathExpression(Variable *var)
bool MacroActionVariable::PerformAction()
{
auto var = GetVariableByName(_variableName);
auto var = _variable.lock();
if (!var) {
return true;
}
@@ -132,7 +132,7 @@ bool MacroActionVariable::PerformAction()
apppend(*var, _strValue);
break;
case Type::APPEND_VAR: {
auto var2 = GetVariableByName(_variable2Name);
auto var2 = _variable2.lock();
if (!var2) {
return true;
}
@@ -171,18 +171,18 @@ bool MacroActionVariable::PerformAction()
}
case Type::SUBSTRING: {
if (_regex.Enabled()) {
HandleRegexSubString(var);
HandleRegexSubString(var.get());
return true;
}
HandleIndexSubString(var);
HandleIndexSubString(var.get());
return true;
}
case Type::FIND_AND_REPLACE: {
HandleFindAndReplace(var);
HandleFindAndReplace(var.get());
return true;
}
case Type::MATH_EXPRESSION: {
HandleMathExpression(var);
HandleMathExpression(var.get());
return true;
}
}
@@ -193,8 +193,10 @@ bool MacroActionVariable::PerformAction()
bool MacroActionVariable::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
obs_data_set_string(obj, "variableName", _variableName.c_str());
obs_data_set_string(obj, "variable2Name", _variable2Name.c_str());
obs_data_set_string(obj, "variableName",
GetWeakVariableName(_variable).c_str());
obs_data_set_string(obj, "variable2Name",
GetWeakVariableName(_variable2).c_str());
obs_data_set_string(obj, "strValue", _strValue.c_str());
obs_data_set_double(obj, "numValue", _numValue);
obs_data_set_int(obj, "condition", static_cast<int>(_type));
@@ -213,8 +215,10 @@ bool MacroActionVariable::Save(obs_data_t *obj) const
bool MacroActionVariable::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_variableName = obs_data_get_string(obj, "variableName");
_variable2Name = obs_data_get_string(obj, "variable2Name");
_variable =
GetWeakVariableByName(obs_data_get_string(obj, "variableName"));
_variable2 = GetWeakVariableByName(
obs_data_get_string(obj, "variable2Name"));
_strValue = obs_data_get_string(obj, "strValue");
_numValue = obs_data_get_double(obj, "numValue");
_type = static_cast<Type>(obs_data_get_int(obj, "condition"));
@@ -238,7 +242,7 @@ bool MacroActionVariable::PostLoad()
std::string MacroActionVariable::GetShortDesc() const
{
return _variableName;
return GetWeakVariableName(_variable);
}
void MacroActionVariable::SetSegmentIndexValue(int value)
@@ -463,8 +467,8 @@ void MacroActionVariableEdit::UpdateEntryData()
return;
}
_variables->SetVariable(_entryData->_variableName);
_variables2->SetVariable(_entryData->_variable2Name);
_variables->SetVariable(_entryData->_variable);
_variables2->SetVariable(_entryData->_variable2);
_actions->setCurrentIndex(static_cast<int>(_entryData->_type));
_strValue->setPlainText(QString::fromStdString(_entryData->_strValue));
_numValue->setValue(_entryData->_numValue);
@@ -489,7 +493,7 @@ void MacroActionVariableEdit::VariableChanged(const QString &text)
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_variableName = text.toStdString();
_entryData->_variable = GetWeakVariableByQString(text);
}
void MacroActionVariableEdit::Variable2Changed(const QString &text)
@@ -499,7 +503,7 @@ void MacroActionVariableEdit::Variable2Changed(const QString &text)
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_variable2Name = text.toStdString();
_entryData->_variable2 = GetWeakVariableByQString(text);
}
void MacroActionVariableEdit::ActionChanged(int value)

View File

@@ -36,8 +36,8 @@ public:
};
Type _type = Type::SET_FIXED_VALUE;
std::string _variableName = "";
std::string _variable2Name = "";
std::weak_ptr<Variable> _variable;
std::weak_ptr<Variable> _variable2;
std::string _strValue = "";
double _numValue = 0;
int _subStringStart = 0;

View File

@@ -18,7 +18,7 @@ static std::map<MacroActionWebsocket::Type, std::string> actionTypes = {
void MacroActionWebsocket::SendRequest()
{
auto connection = GetConnectionByName(_connection);
auto connection = _connection.lock();
if (!connection) {
return;
}
@@ -46,7 +46,7 @@ void MacroActionWebsocket::LogAction() const
switch (_type) {
case MacroActionWebsocket::Type::REQUEST:
vblog(LOG_INFO, "sent msg \"%s\" via \"%s\"", _message.c_str(),
_connection.c_str());
GetWeakConnectionName(_connection).c_str());
break;
case MacroActionWebsocket::Type::EVENT:
vblog(LOG_INFO, "sent event \"%s\" to connected clients",
@@ -62,7 +62,8 @@ bool MacroActionWebsocket::Save(obs_data_t *obj) const
MacroAction::Save(obj);
obs_data_set_int(obj, "type", static_cast<int>(_type));
_message.Save(obj, "message");
obs_data_set_string(obj, "connection", _connection.c_str());
obs_data_set_string(obj, "connection",
GetWeakConnectionName(_connection).c_str());
return true;
}
@@ -71,14 +72,15 @@ bool MacroActionWebsocket::Load(obs_data_t *obj)
MacroAction::Load(obj);
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
_message.Load(obj, "message");
_connection = obs_data_get_string(obj, "connection");
_connection =
GetWeakConnectionByName(obs_data_get_string(obj, "connection"));
return true;
}
std::string MacroActionWebsocket::GetShortDesc() const
{
if (_type == Type::REQUEST) {
return _connection;
return GetWeakConnectionName(_connection);
}
return "";
}
@@ -206,6 +208,6 @@ void MacroActionWebsocketEdit::ConnectionSelectionChanged(
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_connection = connection.toStdString();
_entryData->_connection = GetWeakConnectionByQString(connection);
emit(HeaderInfoChanged(connection));
}

View File

@@ -29,7 +29,7 @@ public:
Type _type = Type::REQUEST;
StringVariable _message = obs_module_text("AdvSceneSwitcher.enterText");
std::string _connection;
std::weak_ptr<Connection> _connection;
private:
void SendRequest();

View File

@@ -76,8 +76,8 @@ bool MacroConditionVariable::ValueChanged(const Variable &var)
bool MacroConditionVariable::CompareVariables()
{
auto var1 = GetVariableByName(_variableName);
auto var2 = GetVariableByName(_variable2Name);
auto var1 = _variable.lock();
auto var2 = _variable2.lock();
if (!var1 || !var2) {
return false;
}
@@ -105,7 +105,7 @@ bool MacroConditionVariable::CompareVariables()
bool MacroConditionVariable::CheckCondition()
{
auto var = GetVariableByName(_variableName);
auto var = _variable.lock();
if (!var) {
return false;
}
@@ -137,8 +137,10 @@ bool MacroConditionVariable::CheckCondition()
bool MacroConditionVariable::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
obs_data_set_string(obj, "variableName", _variableName.c_str());
obs_data_set_string(obj, "variable2Name", _variable2Name.c_str());
obs_data_set_string(obj, "variableName",
GetWeakVariableName(_variable).c_str());
obs_data_set_string(obj, "variable2Name",
GetWeakVariableName(_variable2).c_str());
obs_data_set_string(obj, "strValue", _strValue.c_str());
obs_data_set_double(obj, "numValue", _numValue);
obs_data_set_int(obj, "condition", static_cast<int>(_type));
@@ -149,8 +151,10 @@ bool MacroConditionVariable::Save(obs_data_t *obj) const
bool MacroConditionVariable::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_variableName = obs_data_get_string(obj, "variableName");
_variable2Name = obs_data_get_string(obj, "variable2Name");
_variable =
GetWeakVariableByName(obs_data_get_string(obj, "variableName"));
_variable2 = GetWeakVariableByName(
obs_data_get_string(obj, "variable2Name"));
_strValue = obs_data_get_string(obj, "strValue");
_numValue = obs_data_get_double(obj, "numValue");
_type = static_cast<Type>(obs_data_get_int(obj, "condition"));
@@ -165,7 +169,7 @@ bool MacroConditionVariable::Load(obs_data_t *obj)
std::string MacroConditionVariable::GetShortDesc() const
{
return _variableName;
return GetWeakVariableName(_variable);
}
static inline void populateConditionSelection(QComboBox *list)
@@ -236,8 +240,8 @@ void MacroConditionVariableEdit::UpdateEntryData()
return;
}
_variables->SetVariable(_entryData->_variableName);
_variables2->SetVariable(_entryData->_variable2Name);
_variables->SetVariable(_entryData->_variable);
_variables2->SetVariable(_entryData->_variable2);
_conditions->setCurrentIndex(static_cast<int>(_entryData->_type));
_strValue->setPlainText(QString::fromStdString(_entryData->_strValue));
_numValue->setValue(_entryData->_numValue);
@@ -252,7 +256,7 @@ void MacroConditionVariableEdit::VariableChanged(const QString &text)
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_variableName = text.toStdString();
_entryData->_variable = GetWeakVariableByQString(text);
}
void MacroConditionVariableEdit::Variable2Changed(const QString &text)
@@ -263,7 +267,7 @@ void MacroConditionVariableEdit::Variable2Changed(const QString &text)
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_variable2Name = text.toStdString();
_entryData->_variable2 = GetWeakVariableByQString(text);
}
void MacroConditionVariableEdit::ConditionChanged(int value)

View File

@@ -35,8 +35,8 @@ public:
};
Type _type = Type::EQUALS;
std::string _variableName = "";
std::string _variable2Name = "";
std::weak_ptr<Variable> _variable;
std::weak_ptr<Variable> _variable2;
std::string _strValue = "";
double _numValue = 0;
RegexConfig _regex;

View File

@@ -38,7 +38,7 @@ bool MacroConditionWebsocket::CheckCondition()
messages = &switcher->websocketMessages;
break;
case MacroConditionWebsocket::Type::EVENT: {
auto connection = GetConnectionByName(_connection);
auto connection = _connection.lock();
if (!connection) {
return false;
}
@@ -76,7 +76,8 @@ bool MacroConditionWebsocket::Save(obs_data_t *obj) const
obs_data_set_int(obj, "type", static_cast<int>(_type));
_message.Save(obj, "message");
_regex.Save(obj);
obs_data_set_string(obj, "connection", _connection.c_str());
obs_data_set_string(obj, "connection",
GetWeakConnectionName(_connection).c_str());
return true;
}
@@ -91,7 +92,8 @@ bool MacroConditionWebsocket::Load(obs_data_t *obj)
_regex.CreateBackwardsCompatibleRegex(
obs_data_get_bool(obj, "useRegex"), false);
}
_connection = obs_data_get_string(obj, "connection");
_connection =
GetWeakConnectionByName(obs_data_get_string(obj, "connection"));
return true;
}
@@ -100,7 +102,7 @@ std::string MacroConditionWebsocket::GetShortDesc() const
if (_type == Type::REQUEST) {
return "";
}
return _connection;
return GetWeakConnectionName(_connection);
}
static inline void populateConditionSelection(QComboBox *list)
@@ -237,7 +239,7 @@ void MacroConditionWebsocketEdit::ConnectionSelectionChanged(
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_connection = connection.toStdString();
_entryData->_connection = GetWeakConnectionByQString(connection);
emit(HeaderInfoChanged(connection));
}

View File

@@ -27,7 +27,7 @@ public:
Type _type = Type::REQUEST;
StringVariable _message = obs_module_text("AdvSceneSwitcher.enterText");
RegexConfig _regex;
std::string _connection;
std::weak_ptr<Connection> _connection;
private:
static bool _registered;

View File

@@ -133,6 +133,32 @@ Connection *GetConnectionByName(const std::string &name)
return nullptr;
}
std::weak_ptr<Connection> GetWeakConnectionByName(const std::string &name)
{
for (const auto &c : switcher->connections) {
if (c->Name() == name) {
std::weak_ptr<Connection> wp =
std::dynamic_pointer_cast<Connection>(c);
return wp;
}
}
return std::weak_ptr<Connection>();
}
std::weak_ptr<Connection> GetWeakConnectionByQString(const QString &name)
{
return GetWeakConnectionByName(name.toStdString());
}
std::string GetWeakConnectionName(std::weak_ptr<Connection> connection)
{
auto con = connection.lock();
if (!con) {
return "invalid connection selection";
}
return con->Name();
}
bool ConnectionNameAvailable(const QString &name)
{
return !GetConnectionByName(name);
@@ -187,6 +213,18 @@ void ConnectionSelection::SetConnection(const std::string &con)
}
}
void ConnectionSelection::SetConnection(
const std::weak_ptr<Connection> &connection_)
{
const QSignalBlocker blocker(_selection);
auto connection = connection_.lock();
if (connection) {
SetConnection(connection->Name());
} else {
_selection->setCurrentIndex(0);
}
}
ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
const Connection &settings)
: ItemSettingsDialog(settings, switcher->connections,

View File

@@ -56,6 +56,9 @@ private:
Connection *GetConnectionByName(const QString &);
Connection *GetConnectionByName(const std::string &);
std::weak_ptr<Connection> GetWeakConnectionByName(const std::string &name);
std::weak_ptr<Connection> GetWeakConnectionByQString(const QString &name);
std::string GetWeakConnectionName(std::weak_ptr<Connection>);
class ConnectionSettingsDialog : public ItemSettingsDialog {
Q_OBJECT
@@ -92,4 +95,5 @@ class ConnectionSelection : public ItemSelection {
public:
ConnectionSelection(QWidget *parent = 0);
void SetConnection(const std::string &);
void SetConnection(const std::weak_ptr<Connection> &);
};

View File

@@ -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)

View File

@@ -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()

View File

@@ -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);