From 6c6b5b060d65f8c874b576b44f83fce9da3601fa Mon Sep 17 00:00:00 2001 From: wingphil Date: Thu, 6 Mar 2025 13:27:12 +0000 Subject: [PATCH 1/6] test: Copy shuntingYard tests so that we can assert the output --- test/test_api/test_api.cpp | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/test_api/test_api.cpp b/test/test_api/test_api.cpp index 8338af0c1..3eac21233 100644 --- a/test/test_api/test_api.cpp +++ b/test/test_api/test_api.cpp @@ -23,6 +23,8 @@ #include #include "ESPAsyncWebServer.h" #include "web/WebAPIService.h" +#include +#include "core/shuntingYard.hpp" using namespace emsesp; @@ -333,6 +335,55 @@ void run_console_tests() { RUN_TEST(console_test3); } +// test shunting yard +void shuntingYard_tests() { + std::string test_value, expected_result; + + expected_result = "locale is en"; + test_value = "\"locale is \"system/settings/locale"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); + + // test with negative value + expected_result = "rssi is -23"; + test_value = "\"rssi is \"0+system/network/rssi"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); + + expected_result = "rssi is -23 dbm"; + test_value = "\"rssi is \"(system/network/rssi)\" dBm\""; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); + + expected_result = "14"; + test_value = "(custom/seltemp/value)"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); + + expected_result = "seltemp=14"; + test_value = "\"seltemp=\"(custom/seltemp/value)"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); + + expected_result = "14"; + test_value = "(custom/seltemp)"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); + + // note: the following will fail unless test("boiler") is loaded before hand + + expected_result = "40"; + test_value = "boiler/flowtempoffset"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); + + expected_result = "40"; + test_value = "(boiler/flowtempoffset/value)"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); + + expected_result = "53.8"; + test_value = "(boiler/storagetemp1/value)"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); + + // (14 - 40) * 2.8 + 5 = -67.8 + expected_result = "-67.8"; + test_value = "(custom/seltemp - boiler/flowtempoffset) * 2.8 + 5"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} + // auto-generate the tests void create_tests() { // These match the calls in test_api.h @@ -455,6 +506,7 @@ int main() { run_tests(); // execute the generated tests run_manual_tests(); // execute some other manual tests from this file run_console_tests(); // execute some console tests + RUN_TEST(shuntingYard_tests); // execute the shuntingYard tests return UNITY_END(); } From 4474868afca85a6eb8d2c7772bfefbb43a7c3ab5 Mon Sep 17 00:00:00 2001 From: wingphil Date: Thu, 6 Mar 2025 13:59:20 +0000 Subject: [PATCH 2/6] Add tests for inline conditionals, try to capture current behaviour --- test/test_api/test_api.cpp | 136 +++++++++++++++++++++++++++++-------- 1 file changed, 109 insertions(+), 27 deletions(-) diff --git a/test/test_api/test_api.cpp b/test/test_api/test_api.cpp index 3eac21233..a1e718ea3 100644 --- a/test/test_api/test_api.cpp +++ b/test/test_api/test_api.cpp @@ -335,55 +335,137 @@ void run_console_tests() { RUN_TEST(console_test3); } -// test shunting yard -void shuntingYard_tests() { - std::string test_value, expected_result; - - expected_result = "locale is en"; - test_value = "\"locale is \"system/settings/locale"; +void shuntingYard_test1() { + std::string expected_result = "locale is en"; + std::string test_value = "\"locale is \"system/settings/locale"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} +void shuntingYard_test2() { // test with negative value - expected_result = "rssi is -23"; - test_value = "\"rssi is \"0+system/network/rssi"; + std::string expected_result = "rssi is -23"; + std::string test_value = "\"rssi is \"0+system/network/rssi"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} - expected_result = "rssi is -23 dbm"; - test_value = "\"rssi is \"(system/network/rssi)\" dBm\""; +void shuntingYard_test3() { + std::string expected_result = "rssi is -23 dbm"; + std::string test_value = "\"rssi is \"(system/network/rssi)\" dBm\""; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} - expected_result = "14"; - test_value = "(custom/seltemp/value)"; +void shuntingYard_test4() { + std::string expected_result = "14"; + std::string test_value = "(custom/seltemp/value)"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} - expected_result = "seltemp=14"; - test_value = "\"seltemp=\"(custom/seltemp/value)"; +void shuntingYard_test5() { + std::string expected_result = "seltemp=14"; + std::string test_value = "\"seltemp=\"(custom/seltemp/value)"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} - expected_result = "14"; - test_value = "(custom/seltemp)"; +void shuntingYard_test6() { + std::string expected_result = "14"; + std::string test_value = "(custom/seltemp)"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} - // note: the following will fail unless test("boiler") is loaded before hand - - expected_result = "40"; - test_value = "boiler/flowtempoffset"; +void shuntingYard_test7() { + std::string expected_result = "40"; + std::string test_value = "boiler/flowtempoffset"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} - expected_result = "40"; - test_value = "(boiler/flowtempoffset/value)"; +void shuntingYard_test8() { + std::string expected_result = "40"; + std::string test_value = "(boiler/flowtempoffset/value)"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} - expected_result = "53.8"; - test_value = "(boiler/storagetemp1/value)"; +void shuntingYard_test9() { + std::string expected_result = "53.8"; + std::string test_value = "(boiler/storagetemp1/value)"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} +void shuntingYard_test10() { // (14 - 40) * 2.8 + 5 = -67.8 - expected_result = "-67.8"; - test_value = "(custom/seltemp - boiler/flowtempoffset) * 2.8 + 5"; + std::string expected_result = "-67.8"; + std::string test_value = "(custom/seltemp - boiler/flowtempoffset) * 2.8 + 5"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); } +void shuntingYard_test11() { + std::string expected_result = "4"; + std::string test_value = "1 > 2 ? 3 : 4"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} + +void shuntingYard_test12() { + std::string expected_result = "3"; + std::string test_value = "1 < 2 ? 3 : 4"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} + +void shuntingYard_test13() { + std::string expected_result = "5"; + 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_test14() { + std::string expected_result = "6"; + 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_test15() { + std::string expected_result = "3"; + 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_test16() { + std::string expected_result = "6"; + 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_test17() { + 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_test18() { + std::string expected_result = "44"; + 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()); +} + +void run_shuntingYard_tests() { + RUN_TEST(shuntingYard_test1); + RUN_TEST(shuntingYard_test2); + RUN_TEST(shuntingYard_test3); + RUN_TEST(shuntingYard_test4); + RUN_TEST(shuntingYard_test5); + RUN_TEST(shuntingYard_test6); + RUN_TEST(shuntingYard_test7); + RUN_TEST(shuntingYard_test8); + RUN_TEST(shuntingYard_test9); + RUN_TEST(shuntingYard_test10); + RUN_TEST(shuntingYard_test11); + RUN_TEST(shuntingYard_test12); + RUN_TEST(shuntingYard_test13); + RUN_TEST(shuntingYard_test14); + RUN_TEST(shuntingYard_test15); + RUN_TEST(shuntingYard_test16); + RUN_TEST(shuntingYard_test17); + RUN_TEST(shuntingYard_test18); +} + // auto-generate the tests void create_tests() { // These match the calls in test_api.h @@ -506,7 +588,7 @@ int main() { run_tests(); // execute the generated tests run_manual_tests(); // execute some other manual tests from this file run_console_tests(); // execute some console tests - RUN_TEST(shuntingYard_tests); // execute the shuntingYard tests + run_shuntingYard_tests(); // execute the shuntingYard tests return UNITY_END(); } From 45eca462e74d7130c668c37462e80680af1264f2 Mon Sep 17 00:00:00 2001 From: wingphil Date: Thu, 6 Mar 2025 14:07:51 +0000 Subject: [PATCH 3/6] test: Move shuntingYard tests to new file --- test/test_api/test_api.cpp | 134 +-------------------------- test/test_api/test_shuntingYard.hpp | 136 ++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 133 deletions(-) create mode 100644 test/test_api/test_shuntingYard.hpp diff --git a/test/test_api/test_api.cpp b/test/test_api/test_api.cpp index a1e718ea3..987d26325 100644 --- a/test/test_api/test_api.cpp +++ b/test/test_api/test_api.cpp @@ -23,8 +23,7 @@ #include #include "ESPAsyncWebServer.h" #include "web/WebAPIService.h" -#include -#include "core/shuntingYard.hpp" +#include "test_shuntingYard.hpp" using namespace emsesp; @@ -335,137 +334,6 @@ void run_console_tests() { RUN_TEST(console_test3); } -void shuntingYard_test1() { - std::string expected_result = "locale is en"; - std::string test_value = "\"locale is \"system/settings/locale"; - TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); -} - -void shuntingYard_test2() { - // test with negative value - std::string expected_result = "rssi is -23"; - std::string test_value = "\"rssi is \"0+system/network/rssi"; - TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); -} - -void shuntingYard_test3() { - std::string expected_result = "rssi is -23 dbm"; - std::string test_value = "\"rssi is \"(system/network/rssi)\" dBm\""; - TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); -} - -void shuntingYard_test4() { - std::string expected_result = "14"; - std::string test_value = "(custom/seltemp/value)"; - TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); -} - -void shuntingYard_test5() { - std::string expected_result = "seltemp=14"; - std::string test_value = "\"seltemp=\"(custom/seltemp/value)"; - TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); -} - -void shuntingYard_test6() { - std::string expected_result = "14"; - std::string test_value = "(custom/seltemp)"; - TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); -} - -void shuntingYard_test7() { - std::string expected_result = "40"; - std::string test_value = "boiler/flowtempoffset"; - TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); -} - -void shuntingYard_test8() { - std::string expected_result = "40"; - std::string test_value = "(boiler/flowtempoffset/value)"; - TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); -} - -void shuntingYard_test9() { - std::string expected_result = "53.8"; - std::string test_value = "(boiler/storagetemp1/value)"; - TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); -} - -void shuntingYard_test10() { - // (14 - 40) * 2.8 + 5 = -67.8 - std::string expected_result = "-67.8"; - std::string test_value = "(custom/seltemp - boiler/flowtempoffset) * 2.8 + 5"; - TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); -} - -void shuntingYard_test11() { - std::string expected_result = "4"; - std::string test_value = "1 > 2 ? 3 : 4"; - TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); -} - -void shuntingYard_test12() { - std::string expected_result = "3"; - std::string test_value = "1 < 2 ? 3 : 4"; - TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); -} - -void shuntingYard_test13() { - std::string expected_result = "5"; - 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_test14() { - std::string expected_result = "6"; - 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_test15() { - std::string expected_result = "3"; - 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_test16() { - std::string expected_result = "6"; - 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_test17() { - 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_test18() { - std::string expected_result = "44"; - 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()); -} - -void run_shuntingYard_tests() { - RUN_TEST(shuntingYard_test1); - RUN_TEST(shuntingYard_test2); - RUN_TEST(shuntingYard_test3); - RUN_TEST(shuntingYard_test4); - RUN_TEST(shuntingYard_test5); - RUN_TEST(shuntingYard_test6); - RUN_TEST(shuntingYard_test7); - RUN_TEST(shuntingYard_test8); - RUN_TEST(shuntingYard_test9); - RUN_TEST(shuntingYard_test10); - RUN_TEST(shuntingYard_test11); - RUN_TEST(shuntingYard_test12); - RUN_TEST(shuntingYard_test13); - RUN_TEST(shuntingYard_test14); - RUN_TEST(shuntingYard_test15); - RUN_TEST(shuntingYard_test16); - RUN_TEST(shuntingYard_test17); - RUN_TEST(shuntingYard_test18); -} - // auto-generate the tests void create_tests() { // These match the calls in test_api.h diff --git a/test/test_api/test_shuntingYard.hpp b/test/test_api/test_shuntingYard.hpp new file mode 100644 index 000000000..e3a5fa5ef --- /dev/null +++ b/test/test_api/test_shuntingYard.hpp @@ -0,0 +1,136 @@ + +#include +#include +#include +#include "core/shuntingYard.hpp" + +void shuntingYard_test1() { + std::string expected_result = "locale is en"; + std::string test_value = "\"locale is \"system/settings/locale"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} + +void shuntingYard_test2() { + // test with negative value + std::string expected_result = "rssi is -23"; + std::string test_value = "\"rssi is \"0+system/network/rssi"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} + +void shuntingYard_test3() { + std::string expected_result = "rssi is -23 dbm"; + std::string test_value = "\"rssi is \"(system/network/rssi)\" dBm\""; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} + +void shuntingYard_test4() { + std::string expected_result = "14"; + std::string test_value = "(custom/seltemp/value)"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} + +void shuntingYard_test5() { + std::string expected_result = "seltemp=14"; + std::string test_value = "\"seltemp=\"(custom/seltemp/value)"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} + +void shuntingYard_test6() { + std::string expected_result = "14"; + std::string test_value = "(custom/seltemp)"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} + +void shuntingYard_test7() { + std::string expected_result = "40"; + std::string test_value = "boiler/flowtempoffset"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} + +void shuntingYard_test8() { + std::string expected_result = "40"; + std::string test_value = "(boiler/flowtempoffset/value)"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} + +void shuntingYard_test9() { + std::string expected_result = "53.8"; + std::string test_value = "(boiler/storagetemp1/value)"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} + +void shuntingYard_test10() { + // (14 - 40) * 2.8 + 5 = -67.8 + std::string expected_result = "-67.8"; + std::string test_value = "(custom/seltemp - boiler/flowtempoffset) * 2.8 + 5"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} + +void shuntingYard_test11() { + std::string expected_result = "4"; + std::string test_value = "1 > 2 ? 3 : 4"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} + +void shuntingYard_test12() { + std::string expected_result = "3"; + std::string test_value = "1 < 2 ? 3 : 4"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} + +void shuntingYard_test13() { + std::string expected_result = "5"; + 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_test14() { + std::string expected_result = "6"; + 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_test15() { + std::string expected_result = "3"; + 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_test16() { + std::string expected_result = "6"; + 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_test17() { + 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_test18() { + std::string expected_result = "44"; + 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()); +} + +void run_shuntingYard_tests() { + RUN_TEST(shuntingYard_test1); + RUN_TEST(shuntingYard_test2); + RUN_TEST(shuntingYard_test3); + RUN_TEST(shuntingYard_test4); + RUN_TEST(shuntingYard_test5); + RUN_TEST(shuntingYard_test6); + RUN_TEST(shuntingYard_test7); + RUN_TEST(shuntingYard_test8); + RUN_TEST(shuntingYard_test9); + RUN_TEST(shuntingYard_test10); + RUN_TEST(shuntingYard_test11); + RUN_TEST(shuntingYard_test12); + RUN_TEST(shuntingYard_test13); + RUN_TEST(shuntingYard_test14); + RUN_TEST(shuntingYard_test15); + RUN_TEST(shuntingYard_test16); + RUN_TEST(shuntingYard_test17); + RUN_TEST(shuntingYard_test18); +} From 9874ecde820c998d57d5bd3d0d105540fbc7b30c Mon Sep 17 00:00:00 2001 From: wingphil Date: Fri, 7 Mar 2025 16:53:41 +0000 Subject: [PATCH 4/6] feat: Support nested conditionals in shuntingYard --- src/core/shuntingYard.hpp | 59 +++++++++++++++++++++-------- test/test_api/test_shuntingYard.hpp | 37 ++++++++++-------- 2 files changed, 65 insertions(+), 31 deletions(-) diff --git a/src/core/shuntingYard.hpp b/src/core/shuntingYard.hpp index 3448fa1fc..932b21294 100644 --- a/src/core/shuntingYard.hpp +++ b/src/core/shuntingYard.hpp @@ -626,6 +626,27 @@ std::string calculate(const std::string & expr) { 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 ? : std::string compute(const std::string & 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); } - // positions: q-questionmark, c-colon - auto q = expr_new.find_first_of('?'); - while (q != std::string::npos) { - // find corresponding colon - auto c1 = expr_new.find_first_of(':', q + 1); - auto q1 = expr_new.find_first_of('?', q + 1); - while (q1 < c1 && q1 != std::string::npos && c1 != std::string::npos) { - q1 = expr_new.find_first_of('?', q1 + 1); - c1 = expr_new.find_first_of(':', c1 + 1); - } - if (c1 == std::string::npos) { + // ? : + // We don't allow nested conditionals in but we do in or + // Therefore if you nest a conditional, it's ? is always to the right of the parent one + // Therefore the rightmost ? never has nested conditionals + // We will also insist that a nested conditional is surrounded by brackets + // positions: s-start, q-questionmark, c-colon, e:end + size_t q; + while ((q = expr_new.find_last_of('?')) != std::string::npos) { + // find corresponding colon, remember we know there's no nested ones to worry about + size_t c = expr_new.find_first_of(':', q); + if (c == std::string::npos) { return ""; // error: missing colon } - std::string cond = calculate(expr_new.substr(0, q)); + // Find the start of + size_t s = q - 1; + skipBrackets(expr, s, -1); + // Find the end of + size_t e = c + 1; + skipBrackets(expr, e, +1); + + std::string cond = calculate(expr_new.substr(s, q - s)); if (cond.length() == 0) { return ""; } else if (cond[0] == '1') { - expr_new.erase(c1); // remove second expression after colon - expr_new.erase(0, q + 1); // remove condition before questionmark + expr_new.erase(c, e + 1 - c); // remove second expression after colon + expr_new.erase(s, q + 1 - s); // remove condition before questionmark } 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 { return ""; // error } - q = expr_new.find_first_of('?'); // search next instance } return calculate(expr_new); diff --git a/test/test_api/test_shuntingYard.hpp b/test/test_api/test_shuntingYard.hpp index e3a5fa5ef..7c44de813 100644 --- a/test/test_api/test_shuntingYard.hpp +++ b/test/test_api/test_shuntingYard.hpp @@ -80,38 +80,44 @@ void shuntingYard_test12() { void shuntingYard_test13() { std::string expected_result = "5"; - std::string test_value = "1 < 2 ? (3 < 4 ? 5 : 6) : 7"; - //TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); + 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_test14() { - std::string expected_result = "6"; - std::string test_value = "1 < 2 ? (3 > 4 ? 5 : 6) : 7"; - //TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); + 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_test15() { - std::string expected_result = "3"; - std::string test_value = "1 < 2 ? 3 : (4 < 5 ? 6 : 7)"; + std::string expected_result = "6"; + 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_test16() { - std::string expected_result = "6"; - std::string test_value = "1 > 2 ? 3 : (4 < 5 ? 6 : 7)"; - //TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); + std::string expected_result = "3"; + 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_test17() { - 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()); + std::string expected_result = "6"; + 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_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 test_value = "(1 > 2 ? 3 : 4) + (10 > 20 ? 30 : 40)"; - //TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); + 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()); } void run_shuntingYard_tests() { @@ -133,4 +139,5 @@ void run_shuntingYard_tests() { RUN_TEST(shuntingYard_test16); RUN_TEST(shuntingYard_test17); RUN_TEST(shuntingYard_test18); + RUN_TEST(shuntingYard_test19); } From a6f77250b5de39f1b7304de0c884aefb35fbe9d2 Mon Sep 17 00:00:00 2001 From: wingphil Date: Fri, 7 Mar 2025 17:08:05 +0000 Subject: [PATCH 5/6] Format with clang --- src/core/shuntingYard.hpp | 14 ++++++----- test/test_api/test_api.cpp | 6 ++--- test/test_api/test_shuntingYard.hpp | 38 ++++++++++++++--------------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/core/shuntingYard.hpp b/src/core/shuntingYard.hpp index 932b21294..030c52734 100644 --- a/src/core/shuntingYard.hpp +++ b/src/core/shuntingYard.hpp @@ -627,15 +627,18 @@ std::string calculate(const std::string & expr) { } void skipBrackets(const std::string & expr, size_t & pos, const int direction) { - int i = pos; + int i = pos; char open = '(', close = ')'; if (direction == -1) { - open = ')'; close = '('; + open = ')'; + close = '('; } size_t depth = 1; while ((i >= 0) && (i < expr.size()) && depth > 0) { - if (expr[i] == open) depth++; - if (expr[i] == close) depth--; + if (expr[i] == open) + depth++; + if (expr[i] == close) + depth--; i += direction; } if (depth > 0) { @@ -644,7 +647,6 @@ void skipBrackets(const std::string & expr, size_t & pos, const int direction) { } else { pos = i - direction * 2; } - } // check for multiple instances of ? : @@ -726,7 +728,7 @@ std::string compute(const std::string & expr) { if (cond.length() == 0) { return ""; } else if (cond[0] == '1') { - expr_new.erase(c, e + 1 - c); // remove second expression after colon + expr_new.erase(c, e + 1 - c); // remove second expression after colon expr_new.erase(s, q + 1 - s); // remove condition before questionmark } else if (cond[0] == '0') { expr_new.erase(s, c + 1 - s); // remove condition and first expression diff --git a/test/test_api/test_api.cpp b/test/test_api/test_api.cpp index 987d26325..ff7b15ee0 100644 --- a/test/test_api/test_api.cpp +++ b/test/test_api/test_api.cpp @@ -453,9 +453,9 @@ int main() { // UNITY_BEGIN(); - run_tests(); // execute the generated tests - run_manual_tests(); // execute some other manual tests from this file - run_console_tests(); // execute some console tests + run_tests(); // execute the generated tests + run_manual_tests(); // execute some other manual tests from this file + run_console_tests(); // execute some console tests run_shuntingYard_tests(); // execute the shuntingYard tests return UNITY_END(); diff --git a/test/test_api/test_shuntingYard.hpp b/test/test_api/test_shuntingYard.hpp index 7c44de813..31fb0c93b 100644 --- a/test/test_api/test_shuntingYard.hpp +++ b/test/test_api/test_shuntingYard.hpp @@ -6,117 +6,117 @@ void shuntingYard_test1() { std::string expected_result = "locale is en"; - std::string test_value = "\"locale is \"system/settings/locale"; + std::string test_value = "\"locale is \"system/settings/locale"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); } void shuntingYard_test2() { // test with negative value std::string expected_result = "rssi is -23"; - std::string test_value = "\"rssi is \"0+system/network/rssi"; + std::string test_value = "\"rssi is \"0+system/network/rssi"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); } void shuntingYard_test3() { std::string expected_result = "rssi is -23 dbm"; - std::string test_value = "\"rssi is \"(system/network/rssi)\" dBm\""; + std::string test_value = "\"rssi is \"(system/network/rssi)\" dBm\""; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); } void shuntingYard_test4() { std::string expected_result = "14"; - std::string test_value = "(custom/seltemp/value)"; + std::string test_value = "(custom/seltemp/value)"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); } void shuntingYard_test5() { std::string expected_result = "seltemp=14"; - std::string test_value = "\"seltemp=\"(custom/seltemp/value)"; + std::string test_value = "\"seltemp=\"(custom/seltemp/value)"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); } void shuntingYard_test6() { std::string expected_result = "14"; - std::string test_value = "(custom/seltemp)"; + std::string test_value = "(custom/seltemp)"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); } void shuntingYard_test7() { std::string expected_result = "40"; - std::string test_value = "boiler/flowtempoffset"; + std::string test_value = "boiler/flowtempoffset"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); } void shuntingYard_test8() { std::string expected_result = "40"; - std::string test_value = "(boiler/flowtempoffset/value)"; + std::string test_value = "(boiler/flowtempoffset/value)"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); } void shuntingYard_test9() { std::string expected_result = "53.8"; - std::string test_value = "(boiler/storagetemp1/value)"; + std::string test_value = "(boiler/storagetemp1/value)"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); } void shuntingYard_test10() { // (14 - 40) * 2.8 + 5 = -67.8 std::string expected_result = "-67.8"; - std::string test_value = "(custom/seltemp - boiler/flowtempoffset) * 2.8 + 5"; + std::string test_value = "(custom/seltemp - boiler/flowtempoffset) * 2.8 + 5"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); } void shuntingYard_test11() { std::string expected_result = "4"; - std::string test_value = "1 > 2 ? 3 : 4"; + std::string test_value = "1 > 2 ? 3 : 4"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); } void shuntingYard_test12() { std::string expected_result = "3"; - std::string test_value = "1 < 2 ? 3 : 4"; + std::string test_value = "1 < 2 ? 3 : 4"; TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); } void shuntingYard_test13() { 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()); } void shuntingYard_test14() { 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()); } void shuntingYard_test15() { 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()); } void shuntingYard_test16() { 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()); } void shuntingYard_test17() { 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()); } void shuntingYard_test18() { 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()); } void shuntingYard_test19() { 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()); } From 9d1ee275331b36af265c814e4b5a03c16636eb89 Mon Sep 17 00:00:00 2001 From: wingphil Date: Mon, 10 Mar 2025 10:09:44 +0000 Subject: [PATCH 6/6] Add test for nested conditionals without brackets --- test/test_api/test_shuntingYard.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/test_api/test_shuntingYard.hpp b/test/test_api/test_shuntingYard.hpp index 31fb0c93b..d3e2b2c0e 100644 --- a/test/test_api/test_shuntingYard.hpp +++ b/test/test_api/test_shuntingYard.hpp @@ -120,6 +120,12 @@ void shuntingYard_test19() { TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); } +void shuntingYard_test20() { + std::string expected_result = "8"; + std::string test_value = "1<2 ? 3>4 ? 5 : 6<7 ? 8 : 9 : 10"; + TEST_ASSERT_EQUAL_STRING(expected_result.c_str(), compute(test_value).c_str()); +} + void run_shuntingYard_tests() { RUN_TEST(shuntingYard_test1); RUN_TEST(shuntingYard_test2); @@ -140,4 +146,5 @@ void run_shuntingYard_tests() { RUN_TEST(shuntingYard_test17); RUN_TEST(shuntingYard_test18); RUN_TEST(shuntingYard_test19); + RUN_TEST(shuntingYard_test20); }