From 67bd445885f2b069e87f765c957c7ca7a6f8529d Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Fri, 31 May 2024 12:12:48 +0800 Subject: [PATCH] refactor: update ParserHelper - update the implementation of ParserHelper from locale-based std::to_string, std::stod, std::stoull, to locale-independent function std::from_chars and std::to_chars. - rename TerminalHelper to ConsoleHelper. --- src/CMakeLists.txt | 2 +- src/{TerminalHelper.cpp => ConsoleHelper.cpp} | 4 +- src/{TerminalHelper.hpp => ConsoleHelper.hpp} | 2 +- src/ParserHelper.hpp | 81 ++++++++++--------- src/YYCCommonplace.hpp | 2 +- testbench/main.cpp | 47 ++++++++--- 6 files changed, 81 insertions(+), 57 deletions(-) rename src/{TerminalHelper.cpp => ConsoleHelper.cpp} (95%) rename src/{TerminalHelper.hpp => ConsoleHelper.hpp} (98%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9801ed2..4a0137d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,13 +1,13 @@ # Setup file lists up set(YYCC_HEADER ${CMAKE_CURRENT_LIST_DIR}) set(YYCC_SRC + ConsoleHelper.cpp DialogHelper.cpp EncodingHelper.cpp ExceptionHelper.cpp IOHelper.cpp ParserHelper.cpp StringHelper.cpp - TerminalHelper.cpp ) # Create static library diff --git a/src/TerminalHelper.cpp b/src/ConsoleHelper.cpp similarity index 95% rename from src/TerminalHelper.cpp rename to src/ConsoleHelper.cpp index 3b3a757..8741514 100644 --- a/src/TerminalHelper.cpp +++ b/src/ConsoleHelper.cpp @@ -1,4 +1,4 @@ -#include "TerminalHelper.hpp" +#include "ConsoleHelper.hpp" #if YYCC_OS == YYCC_OS_WINDOWS #include "EncodingHelper.hpp" @@ -11,7 +11,7 @@ #include #include "WinImportSuffix.hpp" -namespace YYCC::TerminalHelper { +namespace YYCC::ConsoleHelper { bool EnsureTerminalColor(FILE* fs) { if (!_isatty(_fileno(fs))) return false; diff --git a/src/TerminalHelper.hpp b/src/ConsoleHelper.hpp similarity index 98% rename from src/TerminalHelper.hpp rename to src/ConsoleHelper.hpp index 17f7608..5ca8964 100644 --- a/src/TerminalHelper.hpp +++ b/src/ConsoleHelper.hpp @@ -5,7 +5,7 @@ #include #include -namespace YYCC::TerminalHelper { +namespace YYCC::ConsoleHelper { #define YYCC_TERMCOLHDR_BLACK "\033[30m" #define YYCC_TERMCOLHDR_RED "\033[31m" diff --git a/src/ParserHelper.hpp b/src/ParserHelper.hpp index 062679c..b23dba7 100644 --- a/src/ParserHelper.hpp +++ b/src/ParserHelper.hpp @@ -6,55 +6,45 @@ #include #include #include -#include +#include +#include namespace YYCC::ParserHelper { - template - constexpr bool g_AlwaysFalse = false; + // Reference: https://zh.cppreference.com/w/cpp/utility/from_chars template, int> = 0> bool TryParse(const std::string& strl, _Ty& num) { - try { - // float types - if constexpr (std::is_same_v<_Ty, float>) { - num = std::stof(strl, nullptr); - } else if constexpr (std::is_same_v<_Ty, double>) { - num = std::stod(strl, nullptr); - } else if constexpr (std::is_same_v<_Ty, long double>) { - num = std::stold(strl, nullptr); - } else { - static_assert(g_AlwaysFalse<_Ty>, "Invalid float type."); - } - return true; - } catch (const std::invalid_argument&) { + auto [ptr, ec] = std::from_chars(strl.c_str(), strl.c_str() + strl.size(), num, std::chars_format::general); + if (ec == std::errc()) { + // check whether the full string is matched + return ptr == strl.c_str() + strl.size(); + } else if (ec == std::errc::invalid_argument) { + // given string is invalid return false; - } catch (const std::out_of_range&) { + } else if (ec == std::errc::result_out_of_range) { + // given string is out of range return false; + } else { + // unreachable + throw std::runtime_error("unreachable code."); } } template && !std::is_same_v<_Ty, bool>, int> = 0> bool TryParse(const std::string& strl, _Ty& num, int base = 10) { - try { - // integer type - // decide integer type - using container_t = std::conditional_t, unsigned long long, long long>; - // parse it from string according to whether integer type is signed. - container_t cache; - if constexpr (std::is_unsigned_v<_Ty>) { - cache = std::stoull(strl, nullptr, base); - } else { - cache = std::stoll(strl, nullptr, base); - } - // check its range - if (cache < std::numeric_limits<_Ty>::min() || cache > std::numeric_limits<_Ty>::max()) - return false; - num = static_cast<_Ty>(cache); - return true; - } catch (const std::invalid_argument&) { + auto [ptr, ec] = std::from_chars(strl.c_str(), strl.c_str() + strl.size(), num, base); + if (ec == std::errc()) { + // check whether the full string is matched + return ptr == strl.c_str() + strl.size(); + } else if (ec == std::errc::invalid_argument) { + // given string is invalid return false; - } catch (const std::out_of_range&) { + } else if (ec == std::errc::result_out_of_range) { + // given string is out of range return false; + } else { + // unreachable + throw std::runtime_error("unreachable code."); } } template, int> = 0> @@ -72,12 +62,25 @@ namespace YYCC::ParserHelper { return ret; } - template, int> = 0> + // Reference: https://en.cppreference.com/w/cpp/utility/to_chars + + template && !std::is_same_v<_Ty, bool>, int> = 0> std::string ToString(_Ty num) { - return std::to_string(num); + std::array buffer; + auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), num); + if (ec == std::errc()) { + return std::string(buffer.data(), ptr - buffer.data()); + } else if (ec == std::errc::value_too_large) { + // too short buffer + // this should not happend + throw std::out_of_range("ToString() buffer is not sufficient."); + } else { + // unreachable + throw std::runtime_error("unreachable code."); + } } - template<> - std::string ToString(bool num) { + template, int> = 0> + std::string ToString(_Ty num) { if (num) return std::string("true"); else return std::string("false"); } diff --git a/src/YYCCommonplace.hpp b/src/YYCCommonplace.hpp index 21180fa..4b375f4 100644 --- a/src/YYCCommonplace.hpp +++ b/src/YYCCommonplace.hpp @@ -4,6 +4,6 @@ #include "StringHelper.hpp" #include "EncodingHelper.hpp" -#include "TerminalHelper.hpp" +#include "ConsoleHelper.hpp" #include "DialogHelper.hpp" #include "ParserHelper.hpp" diff --git a/testbench/main.cpp b/testbench/main.cpp index aff8989..2996234 100644 --- a/testbench/main.cpp +++ b/testbench/main.cpp @@ -5,23 +5,44 @@ namespace Testbench { static void Assert(bool condition, const char* description) { if (condition) { - YYCC::TerminalHelper::FPrintf(stdout, YYCC_TERMCOL_LIGHT_GREEN("OK: %s\n"), description); + YYCC::ConsoleHelper::FPrintf(stdout, YYCC_TERMCOL_LIGHT_GREEN("OK: %s\n"), description); } else { - YYCC::TerminalHelper::FPrintf(stdout, YYCC_TERMCOL_LIGHT_RED("Failed: %s\n"), description); + YYCC::ConsoleHelper::FPrintf(stdout, YYCC_TERMCOL_LIGHT_RED("Failed: %s\n"), description); std::abort(); } } static void TerminalTestbench() { - YYCC::TerminalHelper::EnsureTerminalUTF8(stdout); - YYCC::TerminalHelper::FPuts("你好世界\n", stdout); - YYCC::TerminalHelper::EnsureTerminalColor(stdout); - YYCC::TerminalHelper::FPuts(YYCC_TERMCOL_LIGHT_CYAN("Colorful Terminal\n"), stdout); + // UTF8 Test + // Ref: https://stackoverflow.com/questions/478201/how-to-test-an-application-for-correct-encoding-e-g-utf-8 + YYCC::ConsoleHelper::EnsureTerminalUTF8(stdout); + YYCC::ConsoleHelper::FPuts("UTF8 Test:\n", stdout); + static std::vector c_TestStrings { + "\u30E6\u30FC\u30B6\u30FC\u5225\u30B5\u30A4\u30C8", // JAPAN + "\u7B80\u4F53\u4E2D\u6587", // CHINA + "\uD06C\uB85C\uC2A4 \uD50C\uB7AB\uD3FC\uC73C\uB85C", // KOREA + "\u05DE\u05D3\u05D5\u05E8\u05D9\u05DD \u05DE\u05D1\u05D5\u05E7\u05E9\u05D9\u05DD", // ISRAEL + "\u0623\u0641\u0636\u0644 \u0627\u0644\u0628\u062D\u0648\u062B", // EGYPT + "\u03A3\u1F72 \u03B3\u03BD\u03C9\u03C1\u03AF\u03B6\u03C9 \u1F00\u03C0\u1F78", // GREECE + "\u0414\u0435\u0441\u044F\u0442\u0443\u044E \u041C\u0435\u0436\u0434\u0443\u043D\u0430\u0440\u043E\u0434\u043D\u0443\u044E", // RUSSIA + "\u0E41\u0E1C\u0E48\u0E19\u0E14\u0E34\u0E19\u0E2E\u0E31\u0E48\u0E19\u0E40\u0E2A\u0E37\u0E48\u0E2D\u0E21\u0E42\u0E17\u0E23\u0E21\u0E41\u0E2A\u0E19\u0E2A\u0E31\u0E07\u0E40\u0E27\u0E0A", // THAILAND + "fran\u00E7ais langue \u00E9trang\u00E8re", // FRANCE + "ma\u00F1ana ol\u00E9", // SPAIN + "\u222E E\u22C5da = Q, n \u2192 \u221E, \u2211 f(i) = \u220F g(i)", // MATHMATICS + //"\xF0\x9F\x8D\xA3 \xE2\x9C\x96 \xF0\x9F\x8D\xBA", // EMOJI + }; + for (const auto* ptr : c_TestStrings) { + YYCC::ConsoleHelper::FPrintf(stdout, "\t%s\n", ptr); + } + + // Color Test + YYCC::ConsoleHelper::EnsureTerminalColor(stdout); + YYCC::ConsoleHelper::FPuts(YYCC_TERMCOL_LIGHT_CYAN("Colorful Terminal\n"), stdout); } static void StringTestbench() { - auto test_printf = YYCC::StringHelper::Printf("%s == %s", "Hello World", "你好世界"); - Assert(test_printf == "Hello World == 你好世界", "YYCC::StringHelper::Printf"); + auto test_printf = YYCC::StringHelper::Printf("%s == %s", "Hello World", "Hello, world"); + Assert(test_printf == "Hello World == Hello, world", "YYCC::StringHelper::Printf"); auto test_lower = YYCC::StringHelper::Lower("LOWER"); Assert(test_lower == "lower", "YYCC::StringHelper::Lower"); @@ -130,20 +151,20 @@ namespace Testbench { filters.Add("All Files (*.*)", {"*.*"}); params.SetDefaultFileTypeIndex(0u); if (YYCC::DialogHelper::OpenFileDialog(params, ret)) { - YYCC::TerminalHelper::FPrintf(stdout, "Open File: %s\n", ret.c_str()); + YYCC::ConsoleHelper::FPrintf(stdout, "Open File: %s\n", ret.c_str()); } if (YYCC::DialogHelper::OpenMultipleFileDialog(params, rets)) { - YYCC::TerminalHelper::FPuts("Open Multiple Files:\n", stdout); + YYCC::ConsoleHelper::FPuts("Open Multiple Files:\n", stdout); for (const auto& item : rets) { - YYCC::TerminalHelper::FPrintf(stdout, "\t%s\n", item.c_str()); + YYCC::ConsoleHelper::FPrintf(stdout, "\t%s\n", item.c_str()); } } if (YYCC::DialogHelper::SaveFileDialog(params, ret)) { - YYCC::TerminalHelper::FPrintf(stdout, "Save File: %s\n", ret.c_str()); + YYCC::ConsoleHelper::FPrintf(stdout, "Save File: %s\n", ret.c_str()); } params.Clear(); if (YYCC::DialogHelper::OpenFolderDialog(params, ret)) { - YYCC::TerminalHelper::FPrintf(stdout, "Open Folder: %s\n", ret.c_str()); + YYCC::ConsoleHelper::FPrintf(stdout, "Open Folder: %s\n", ret.c_str()); } }