mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 00:09:51 +03:00
shuntingyard as a class library
This commit is contained in:
@@ -17,37 +17,10 @@
|
|||||||
// copy from https://gist.github.com/t-mat/b9f681b7591cdae712f6
|
// copy from https://gist.github.com/t-mat/b9f681b7591cdae712f6
|
||||||
// modified MDvP, 06.2024
|
// modified MDvP, 06.2024
|
||||||
//
|
//
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <deque>
|
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
class Token {
|
#include "emsesp.h"
|
||||||
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)
|
#include "shuntingYard.h"
|
||||||
: type{type}
|
|
||||||
, str(s)
|
|
||||||
, precedence{precedence}
|
|
||||||
, rightAssociative{rightAssociative} {
|
|
||||||
}
|
|
||||||
|
|
||||||
const Type type;
|
|
||||||
const std::string str;
|
|
||||||
const int8_t precedence;
|
|
||||||
const bool rightAssociative;
|
|
||||||
};
|
|
||||||
|
|
||||||
// find tokens
|
// find tokens
|
||||||
std::deque<Token> exprToTokens(const std::string & expr) {
|
std::deque<Token> exprToTokens(const std::string & expr) {
|
||||||
@@ -357,7 +330,7 @@ bool isnum(const std::string & s) {
|
|||||||
|
|
||||||
|
|
||||||
// replace commands like "<device>/<hc>/<cmd>" with its value"
|
// replace commands like "<device>/<hc>/<cmd>" 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);
|
auto expr_new = emsesp::Helpers::toLower(expr);
|
||||||
for (uint8_t device = 0; device < emsesp::EMSdevice::DeviceType::UNKNOWN; device++) {
|
for (uint8_t device = 0; device < emsesp::EMSdevice::DeviceType::UNKNOWN; device++) {
|
||||||
const char * d = emsesp::EMSdevice::device_type_2_device_name(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);
|
expr_new.copy(cmd, l, f);
|
||||||
cmd[l] = '\0';
|
cmd[l] = '\0';
|
||||||
|
|
||||||
if (strstr(cmd, "/value") == nullptr) {
|
if (strstr(cmd, "/value") == nullptr) {
|
||||||
strlcat(cmd, "/value", sizeof(cmd) - 6);
|
strlcat(cmd, "/value", sizeof(cmd) - 6);
|
||||||
}
|
}
|
||||||
@@ -445,23 +419,21 @@ std::string calculate(const std::string & expr) {
|
|||||||
commands(expr_new);
|
commands(expr_new);
|
||||||
|
|
||||||
const auto tokens = exprToTokens(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()) {
|
if (tokens.empty()) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
auto queue = shuntingYard(tokens);
|
auto queue = shuntingYard(tokens);
|
||||||
if (queue.empty()) {
|
if (queue.empty()) {
|
||||||
return "";
|
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<std::string> stack;
|
std::vector<std::string> stack;
|
||||||
|
|
||||||
while (!queue.empty()) {
|
while (!queue.empty()) {
|
||||||
86
src/core/shuntingYard.h
Normal file
86
src/core/shuntingYard.h
Normal file
@@ -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 <HTTPClient.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <deque>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
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<Token> exprToTokens(const std::string & expr);
|
||||||
|
|
||||||
|
// sort tokens to RPN form
|
||||||
|
std::deque<Token> shuntingYard(const std::deque<Token> & tokens);
|
||||||
|
|
||||||
|
// check if string is a number
|
||||||
|
bool isnum(const std::string & s);
|
||||||
|
|
||||||
|
// replace commands like "<device>/<hc>/<cmd>" 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 <cond> ? <expr1> : <expr2>
|
||||||
|
std::string compute(const std::string & expr);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <unity.h>
|
|
||||||
#include <HTTPClient.h>
|
|
||||||
#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);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user