From 76c0aa6be8dad4bda9df937be7ace83659111a92 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 21 Sep 2025 19:16:55 +0200 Subject: [PATCH] shuntingyard as a class library --- .../{shuntingYard.hpp => shuntingYard.cpp} | 50 ++---- src/core/shuntingYard.h | 86 ++++++++++ test/test_api/test_shuntingYard.hpp | 157 ------------------ 3 files changed, 97 insertions(+), 196 deletions(-) rename src/core/{shuntingYard.hpp => shuntingYard.cpp} (96%) create mode 100644 src/core/shuntingYard.h delete mode 100644 test/test_api/test_shuntingYard.hpp diff --git a/src/core/shuntingYard.hpp b/src/core/shuntingYard.cpp similarity index 96% rename from src/core/shuntingYard.hpp rename to src/core/shuntingYard.cpp index 14a993e19..529622bd8 100644 --- a/src/core/shuntingYard.hpp +++ b/src/core/shuntingYard.cpp @@ -17,37 +17,10 @@ // copy from https://gist.github.com/t-mat/b9f681b7591cdae712f6 // modified MDvP, 06.2024 // -#include -#include -#include -#include -class Token { - public: - enum class Type : uint8_t { - Unknown, - Number, - String, - Operator, - Compare, - Logic, - Unary, - LeftParen, - RightParen, - }; +#include "emsesp.h" - Token(Type type, const std::string & s, int8_t precedence = -1, bool rightAssociative = false) - : type{type} - , str(s) - , precedence{precedence} - , rightAssociative{rightAssociative} { - } - - const Type type; - const std::string str; - const int8_t precedence; - const bool rightAssociative; -}; +#include "shuntingYard.h" // find tokens std::deque exprToTokens(const std::string & expr) { @@ -357,7 +330,7 @@ bool isnum(const std::string & s) { // replace commands like "//" with its value" -std::string commands(std::string & expr, bool quotes = true) { +std::string commands(std::string & expr, bool quotes) { auto expr_new = emsesp::Helpers::toLower(expr); for (uint8_t device = 0; device < emsesp::EMSdevice::DeviceType::UNKNOWN; device++) { const char * d = emsesp::EMSdevice::device_type_2_device_name(device); @@ -375,6 +348,7 @@ std::string commands(std::string & expr, bool quotes = true) { } expr_new.copy(cmd, l, f); cmd[l] = '\0'; + if (strstr(cmd, "/value") == nullptr) { strlcat(cmd, "/value", sizeof(cmd) - 6); } @@ -445,23 +419,21 @@ std::string calculate(const std::string & expr) { commands(expr_new); const auto tokens = exprToTokens(expr_new); + // for debugging only + // for (const auto & t : tokens) { + // emsesp::EMSESP::logger().debug("shunt token: %s(%d)", t.str.c_str(), t.type); + // Serial.printf("shunt token: %s(%d)\n", t.str.c_str(), t.type); + // Serial.println(); + // } if (tokens.empty()) { return ""; } + auto queue = shuntingYard(tokens); if (queue.empty()) { return ""; } - /* - // debug only print tokens - #ifdef EMSESP_STANDALONE - for (const auto & t : queue) { - emsesp::EMSESP::logger().debug("shunt token: %s(%d)", t.str.c_str(), t.type); - } - #endif - */ - std::vector stack; while (!queue.empty()) { diff --git a/src/core/shuntingYard.h b/src/core/shuntingYard.h new file mode 100644 index 000000000..1eb44ae30 --- /dev/null +++ b/src/core/shuntingYard.h @@ -0,0 +1,86 @@ +// Shunting-yard Algorithm +// https://en.wikipedia.org/wiki/Shunting-yard_algorithm +// +// Implementation notes for unary operators by Austin Taylor +// https://stackoverflow.com/a/5240912 +// +// Example: +// https://ideone.com/VocUTq +// +// License: +// This code uses the following materials. +// (1) Wikipedia article [Shunting-yard algorithm](https://en.wikipedia.org/wiki/Shunting-yard_algorithm), +// which is released under the [Creative Commons Attribution-Share-Alike License 3.0](https://creativecommons.org/licenses/by-sa/3.0/). +// (2) [Implementation notes for unary operators in Shunting-Yard algorithm](https://stackoverflow.com/a/5240912) by Austin Taylor +// which is released under the [Creative Commons Attribution-Share-Alike License 2.5](https://creativecommons.org/licenses/by-sa/2.5/). +// +// copy from https://gist.github.com/t-mat/b9f681b7591cdae712f6 +// modified MDvP, 06.2024 +// + +#ifndef EMSESP_SHUNTING_YARD_H_ +#define EMSESP_SHUNTING_YARD_H_ + +#include +#include + +#include +#include +#include +#include + +class Token { + public: + enum class Type : uint8_t { + Unknown, + Number, + String, + Operator, + Compare, + Logic, + Unary, + LeftParen, + RightParen, + }; + + Token(Type type, const std::string & s, int8_t precedence = -1, bool rightAssociative = false) + : type{type} + , str(s) + , precedence{precedence} + , rightAssociative{rightAssociative} { + } + + const Type type; + const std::string str; + const int8_t precedence; + const bool rightAssociative; +}; + +// find tokens +std::deque exprToTokens(const std::string & expr); + +// sort tokens to RPN form +std::deque shuntingYard(const std::deque & tokens); + +// check if string is a number +bool isnum(const std::string & s); + +// replace commands like "//" with its value" +std::string commands(std::string & expr, bool quotes = true); + +// checks for logic value +int to_logic(const std::string & s); + +// number to string, remove trailing zeros +std::string to_string(double d); + +// number to hex string +std::string to_hex(uint32_t i); + +// RPN calculator +std::string calculate(const std::string & expr); + +// check for multiple instances of ? : +std::string compute(const std::string & expr); + +#endif diff --git a/test/test_api/test_shuntingYard.hpp b/test/test_api/test_shuntingYard.hpp deleted file mode 100644 index 0970f781a..000000000 --- a/test/test_api/test_shuntingYard.hpp +++ /dev/null @@ -1,157 +0,0 @@ - -#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 = "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"; - 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)"; - 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)"; - 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()); -} - -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 shuntingYard_test21() { - std::string expected_result = "1"; - std::string test_value = "boiler/storagetemp2 == \"\""; - 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); - RUN_TEST(shuntingYard_test19); - RUN_TEST(shuntingYard_test20); - RUN_TEST(shuntingYard_test21); -}