feat: Support nested conditionals in shuntingYard

This commit is contained in:
wingphil
2025-03-07 16:53:41 +00:00
parent 45eca462e7
commit 9874ecde82
2 changed files with 65 additions and 31 deletions

View File

@@ -626,6 +626,27 @@ std::string calculate(const std::string & expr) {
return result; return result;
} }
void skipBrackets(const std::string & expr, size_t & pos, const int direction) {
int i = pos;
char open = '(', close = ')';
if (direction == -1) {
open = ')'; close = '(';
}
size_t depth = 1;
while ((i >= 0) && (i < expr.size()) && depth > 0) {
if (expr[i] == open) depth++;
if (expr[i] == close) depth--;
i += direction;
}
if (depth > 0) {
// We didn't find the close bracket so return the end of the string
pos = direction == 1 ? expr.size() : 0;
} else {
pos = i - direction * 2;
}
}
// check for multiple instances of <cond> ? <expr1> : <expr2> // check for multiple instances of <cond> ? <expr1> : <expr2>
std::string compute(const std::string & expr) { std::string compute(const std::string & expr) {
auto expr_new = emsesp::Helpers::toLower(expr); auto expr_new = emsesp::Helpers::toLower(expr);
@@ -681,31 +702,37 @@ std::string compute(const std::string & expr) {
f = expr_new.find_first_of('{', e); f = expr_new.find_first_of('{', e);
} }
// positions: q-questionmark, c-colon // <cond> ? <expr1> : <expr2>
auto q = expr_new.find_first_of('?'); // We don't allow nested conditionals in <cond> but we do in <expr1> or <expr2>
while (q != std::string::npos) { // Therefore if you nest a conditional, it's ? is always to the right of the parent one
// find corresponding colon // Therefore the rightmost ? never has nested conditionals
auto c1 = expr_new.find_first_of(':', q + 1); // We will also insist that a nested conditional is surrounded by brackets
auto q1 = expr_new.find_first_of('?', q + 1); // positions: s-start, q-questionmark, c-colon, e:end
while (q1 < c1 && q1 != std::string::npos && c1 != std::string::npos) { size_t q;
q1 = expr_new.find_first_of('?', q1 + 1); while ((q = expr_new.find_last_of('?')) != std::string::npos) {
c1 = expr_new.find_first_of(':', c1 + 1); // find corresponding colon, remember we know there's no nested ones to worry about
} size_t c = expr_new.find_first_of(':', q);
if (c1 == std::string::npos) { if (c == std::string::npos) {
return ""; // error: missing colon return ""; // error: missing colon
} }
std::string cond = calculate(expr_new.substr(0, q)); // Find the start of <cond>
size_t s = q - 1;
skipBrackets(expr, s, -1);
// Find the end of <expr2>
size_t e = c + 1;
skipBrackets(expr, e, +1);
std::string cond = calculate(expr_new.substr(s, q - s));
if (cond.length() == 0) { if (cond.length() == 0) {
return ""; return "";
} else if (cond[0] == '1') { } else if (cond[0] == '1') {
expr_new.erase(c1); // remove second expression after colon expr_new.erase(c, e + 1 - c); // remove second expression after colon
expr_new.erase(0, q + 1); // remove condition before questionmark expr_new.erase(s, q + 1 - s); // remove condition before questionmark
} else if (cond[0] == '0') { } else if (cond[0] == '0') {
expr_new.erase(0, c1 + 1); // remove condition and first expression expr_new.erase(s, c + 1 - s); // remove condition and first expression
} else { } else {
return ""; // error return ""; // error
} }
q = expr_new.find_first_of('?'); // search next instance
} }
return calculate(expr_new); return calculate(expr_new);

View File

@@ -80,38 +80,44 @@ void shuntingYard_test12() {
void shuntingYard_test13() { void shuntingYard_test13() {
std::string expected_result = "5"; std::string expected_result = "5";
std::string test_value = "1 < 2 ? (3 < 4 ? 5 : 6) : 7"; std::string test_value = "1<2?(3<4?5:6):7";
//TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str());
} }
void shuntingYard_test14() { void shuntingYard_test14() {
std::string expected_result = "6"; std::string expected_result = "7";
std::string test_value = "1 < 2 ? (3 > 4 ? 5 : 6) : 7"; std::string test_value = "1>2?(3<4?5:6):7";
//TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str());
} }
void shuntingYard_test15() { void shuntingYard_test15() {
std::string expected_result = "3"; std::string expected_result = "6";
std::string test_value = "1 < 2 ? 3 : (4 < 5 ? 6 : 7)"; std::string test_value = "1<2?(3>4?5:6):7";
TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str());
} }
void shuntingYard_test16() { void shuntingYard_test16() {
std::string expected_result = "6"; std::string expected_result = "3";
std::string test_value = "1 > 2 ? 3 : (4 < 5 ? 6 : 7)"; std::string test_value = "1<2?3:(4<5?6:7)";
//TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str());
} }
void shuntingYard_test17() { void shuntingYard_test17() {
std::string expected_result = "7"; std::string expected_result = "6";
std::string test_value = "1 > 2 ? 3 : (4 > 5 ? 6 : 7)"; std::string test_value = "1>2?3:(4<5?6:7)";
//TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str());
} }
void shuntingYard_test18() { void shuntingYard_test18() {
std::string expected_result = "7";
std::string test_value = "1>2?3:(4>5?6:7)";
TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str());
}
void shuntingYard_test19() {
std::string expected_result = "44"; std::string expected_result = "44";
std::string test_value = "(1 > 2 ? 3 : 4) + (10 > 20 ? 30 : 40)"; std::string test_value = "(1>2?3:4)+(10>20?30:40)";
//TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str());
} }
void run_shuntingYard_tests() { void run_shuntingYard_tests() {
@@ -133,4 +139,5 @@ void run_shuntingYard_tests() {
RUN_TEST(shuntingYard_test16); RUN_TEST(shuntingYard_test16);
RUN_TEST(shuntingYard_test17); RUN_TEST(shuntingYard_test17);
RUN_TEST(shuntingYard_test18); RUN_TEST(shuntingYard_test18);
RUN_TEST(shuntingYard_test19);
} }