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.
This commit is contained in:
parent
7044a0cff2
commit
67bd445885
|
@ -1,13 +1,13 @@
|
||||||
# Setup file lists up
|
# Setup file lists up
|
||||||
set(YYCC_HEADER ${CMAKE_CURRENT_LIST_DIR})
|
set(YYCC_HEADER ${CMAKE_CURRENT_LIST_DIR})
|
||||||
set(YYCC_SRC
|
set(YYCC_SRC
|
||||||
|
ConsoleHelper.cpp
|
||||||
DialogHelper.cpp
|
DialogHelper.cpp
|
||||||
EncodingHelper.cpp
|
EncodingHelper.cpp
|
||||||
ExceptionHelper.cpp
|
ExceptionHelper.cpp
|
||||||
IOHelper.cpp
|
IOHelper.cpp
|
||||||
ParserHelper.cpp
|
ParserHelper.cpp
|
||||||
StringHelper.cpp
|
StringHelper.cpp
|
||||||
TerminalHelper.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create static library
|
# Create static library
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#include "TerminalHelper.hpp"
|
#include "ConsoleHelper.hpp"
|
||||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||||
|
|
||||||
#include "EncodingHelper.hpp"
|
#include "EncodingHelper.hpp"
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include "WinImportSuffix.hpp"
|
#include "WinImportSuffix.hpp"
|
||||||
|
|
||||||
namespace YYCC::TerminalHelper {
|
namespace YYCC::ConsoleHelper {
|
||||||
|
|
||||||
bool EnsureTerminalColor(FILE* fs) {
|
bool EnsureTerminalColor(FILE* fs) {
|
||||||
if (!_isatty(_fileno(fs))) return false;
|
if (!_isatty(_fileno(fs))) return false;
|
|
@ -5,7 +5,7 @@
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace YYCC::TerminalHelper {
|
namespace YYCC::ConsoleHelper {
|
||||||
|
|
||||||
#define YYCC_TERMCOLHDR_BLACK "\033[30m"
|
#define YYCC_TERMCOLHDR_BLACK "\033[30m"
|
||||||
#define YYCC_TERMCOLHDR_RED "\033[31m"
|
#define YYCC_TERMCOLHDR_RED "\033[31m"
|
|
@ -6,55 +6,45 @@
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <limits>
|
#include <charconv>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
namespace YYCC::ParserHelper {
|
namespace YYCC::ParserHelper {
|
||||||
|
|
||||||
template<class>
|
// Reference: https://zh.cppreference.com/w/cpp/utility/from_chars
|
||||||
constexpr bool g_AlwaysFalse = false;
|
|
||||||
|
|
||||||
template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0>
|
template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0>
|
||||||
bool TryParse(const std::string& strl, _Ty& num) {
|
bool TryParse(const std::string& strl, _Ty& num) {
|
||||||
try {
|
auto [ptr, ec] = std::from_chars(strl.c_str(), strl.c_str() + strl.size(), num, std::chars_format::general);
|
||||||
// float types
|
if (ec == std::errc()) {
|
||||||
if constexpr (std::is_same_v<_Ty, float>) {
|
// check whether the full string is matched
|
||||||
num = std::stof(strl, nullptr);
|
return ptr == strl.c_str() + strl.size();
|
||||||
} else if constexpr (std::is_same_v<_Ty, double>) {
|
} else if (ec == std::errc::invalid_argument) {
|
||||||
num = std::stod(strl, nullptr);
|
// given string is invalid
|
||||||
} else if constexpr (std::is_same_v<_Ty, long double>) {
|
return false;
|
||||||
num = std::stold(strl, nullptr);
|
} else if (ec == std::errc::result_out_of_range) {
|
||||||
|
// given string is out of range
|
||||||
|
return false;
|
||||||
} else {
|
} else {
|
||||||
static_assert(g_AlwaysFalse<_Ty>, "Invalid float type.");
|
// unreachable
|
||||||
}
|
throw std::runtime_error("unreachable code.");
|
||||||
return true;
|
|
||||||
} catch (const std::invalid_argument&) {
|
|
||||||
return false;
|
|
||||||
} catch (const std::out_of_range&) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
template<typename _Ty, std::enable_if_t<std::is_integral_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
|
template<typename _Ty, std::enable_if_t<std::is_integral_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
|
||||||
bool TryParse(const std::string& strl, _Ty& num, int base = 10) {
|
bool TryParse(const std::string& strl, _Ty& num, int base = 10) {
|
||||||
try {
|
auto [ptr, ec] = std::from_chars(strl.c_str(), strl.c_str() + strl.size(), num, base);
|
||||||
// integer type
|
if (ec == std::errc()) {
|
||||||
// decide integer type
|
// check whether the full string is matched
|
||||||
using container_t = std::conditional_t<std::is_unsigned_v<_Ty>, unsigned long long, long long>;
|
return ptr == strl.c_str() + strl.size();
|
||||||
// parse it from string according to whether integer type is signed.
|
} else if (ec == std::errc::invalid_argument) {
|
||||||
container_t cache;
|
// given string is invalid
|
||||||
if constexpr (std::is_unsigned_v<_Ty>) {
|
return false;
|
||||||
cache = std::stoull(strl, nullptr, base);
|
} else if (ec == std::errc::result_out_of_range) {
|
||||||
|
// given string is out of range
|
||||||
|
return false;
|
||||||
} else {
|
} else {
|
||||||
cache = std::stoll(strl, nullptr, base);
|
// unreachable
|
||||||
}
|
throw std::runtime_error("unreachable code.");
|
||||||
// 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&) {
|
|
||||||
return false;
|
|
||||||
} catch (const std::out_of_range&) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, bool>, int> = 0>
|
template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, bool>, int> = 0>
|
||||||
|
@ -72,12 +62,25 @@ namespace YYCC::ParserHelper {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty>, int> = 0>
|
// Reference: https://en.cppreference.com/w/cpp/utility/to_chars
|
||||||
|
|
||||||
|
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
|
||||||
std::string ToString(_Ty num) {
|
std::string ToString(_Ty num) {
|
||||||
return std::to_string(num);
|
std::array<char, 64> 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>(bool num) {
|
template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, bool>, int> = 0>
|
||||||
|
std::string ToString(_Ty num) {
|
||||||
if (num) return std::string("true");
|
if (num) return std::string("true");
|
||||||
else return std::string("false");
|
else return std::string("false");
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,6 @@
|
||||||
|
|
||||||
#include "StringHelper.hpp"
|
#include "StringHelper.hpp"
|
||||||
#include "EncodingHelper.hpp"
|
#include "EncodingHelper.hpp"
|
||||||
#include "TerminalHelper.hpp"
|
#include "ConsoleHelper.hpp"
|
||||||
#include "DialogHelper.hpp"
|
#include "DialogHelper.hpp"
|
||||||
#include "ParserHelper.hpp"
|
#include "ParserHelper.hpp"
|
||||||
|
|
|
@ -5,23 +5,44 @@ namespace Testbench {
|
||||||
|
|
||||||
static void Assert(bool condition, const char* description) {
|
static void Assert(bool condition, const char* description) {
|
||||||
if (condition) {
|
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 {
|
} 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();
|
std::abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void TerminalTestbench() {
|
static void TerminalTestbench() {
|
||||||
YYCC::TerminalHelper::EnsureTerminalUTF8(stdout);
|
// UTF8 Test
|
||||||
YYCC::TerminalHelper::FPuts("你好世界\n", stdout);
|
// Ref: https://stackoverflow.com/questions/478201/how-to-test-an-application-for-correct-encoding-e-g-utf-8
|
||||||
YYCC::TerminalHelper::EnsureTerminalColor(stdout);
|
YYCC::ConsoleHelper::EnsureTerminalUTF8(stdout);
|
||||||
YYCC::TerminalHelper::FPuts(YYCC_TERMCOL_LIGHT_CYAN("Colorful Terminal\n"), stdout);
|
YYCC::ConsoleHelper::FPuts("UTF8 Test:\n", stdout);
|
||||||
|
static std::vector<const char*> 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() {
|
static void StringTestbench() {
|
||||||
auto test_printf = YYCC::StringHelper::Printf("%s == %s", "Hello World", "你好世界");
|
auto test_printf = YYCC::StringHelper::Printf("%s == %s", "Hello World", "Hello, world");
|
||||||
Assert(test_printf == "Hello World == 你好世界", "YYCC::StringHelper::Printf");
|
Assert(test_printf == "Hello World == Hello, world", "YYCC::StringHelper::Printf");
|
||||||
|
|
||||||
auto test_lower = YYCC::StringHelper::Lower("LOWER");
|
auto test_lower = YYCC::StringHelper::Lower("LOWER");
|
||||||
Assert(test_lower == "lower", "YYCC::StringHelper::Lower");
|
Assert(test_lower == "lower", "YYCC::StringHelper::Lower");
|
||||||
|
@ -130,20 +151,20 @@ namespace Testbench {
|
||||||
filters.Add("All Files (*.*)", {"*.*"});
|
filters.Add("All Files (*.*)", {"*.*"});
|
||||||
params.SetDefaultFileTypeIndex(0u);
|
params.SetDefaultFileTypeIndex(0u);
|
||||||
if (YYCC::DialogHelper::OpenFileDialog(params, ret)) {
|
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)) {
|
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) {
|
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)) {
|
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();
|
params.Clear();
|
||||||
if (YYCC::DialogHelper::OpenFolderDialog(params, ret)) {
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user