diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 612a00e..4177601 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,6 +37,7 @@ FILES yycc/prelude/core.hpp yycc/prelude/rust.hpp yycc/macro/version_cmp.hpp + yycc/macro/feature_probe.hpp yycc/macro/os_detector.hpp yycc/macro/class_copy_move.hpp yycc/string.hpp diff --git a/src/yycc/macro/feature_probe.hpp b/src/yycc/macro/feature_probe.hpp new file mode 100644 index 0000000..43cf737 --- /dev/null +++ b/src/yycc/macro/feature_probe.hpp @@ -0,0 +1,30 @@ +#pragma once + +// Hint for C++ feature detection: +// __cplusplus macro need special compiler switch enabled when compiling. +// So we use _MSVC_LANG check it instead. + +// Detect C++ 20 +#if __cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) + #define YYCC_CPPFEAT_GE_CPP20 +#endif + +// Detect C++ 23 +#if __cplusplus >= 202302L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202302L) + #define YYCC_CPPFEAT_GE_CPP23 +#endif + +// Check whether there is support of `contains` for `set` and `map` including their varients. +#if defined(YYCC_CPPFEAT_GE_CPP20) + #define YYCC_CPPFEAT_CONTAINS +#endif + +// Check whether there is support of `starts_with` and `ends_with` for `basic_string`. +#if defined(__cpp_lib_starts_ends_with) || defined(YYCC_CPPFEAT_GE_CPP20) + #define YYCC_CPPTEST_STARTS_ENDS_WITH +#endif + +// Check whether there is support of `std::expected`. +#if defined(__cpp_lib_expected) || defined(YYCC_CPPFEAT_GE_CPP23) + #define YYCC_CPPTEST_EXPECTED +#endif diff --git a/src/yycc/string/op.cpp b/src/yycc/string/op.cpp index 6400589..c277110 100644 --- a/src/yycc/string/op.cpp +++ b/src/yycc/string/op.cpp @@ -158,7 +158,7 @@ namespace yycc::string::op { generic_lower_upper(strl); } - NS_YYCC_STRING::u8string lower(const NS_YYCC_STRING::u8string_view& strl) { + NS_YYCC_STRING::u8string to_lower(const NS_YYCC_STRING::u8string_view& strl) { NS_YYCC_STRING::u8string ret(strl); lower(ret); return ret; @@ -168,7 +168,7 @@ namespace yycc::string::op { generic_lower_upper(strl); } - NS_YYCC_STRING::u8string upper(const NS_YYCC_STRING::u8string_view& strl) { + NS_YYCC_STRING::u8string to_upper(const NS_YYCC_STRING::u8string_view& strl) { // same as Lower, just replace char transform function. NS_YYCC_STRING::u8string ret(strl); upper(ret); diff --git a/src/yycc/string/op.hpp b/src/yycc/string/op.hpp index 8bd74e7..7e93a26 100644 --- a/src/yycc/string/op.hpp +++ b/src/yycc/string/op.hpp @@ -113,7 +113,7 @@ namespace yycc::string::op { * @param[in] strl The string to be lowercase. * @return The copy of the string converted to lowercase. */ - NS_YYCC_STRING::u8string lower(const NS_YYCC_STRING::u8string_view& strl); + NS_YYCC_STRING::u8string to_lower(const NS_YYCC_STRING::u8string_view& strl); /** * @brief Convert given string to uppercase. * @param[in,out] strl The string to be uppercase. @@ -124,7 +124,7 @@ namespace yycc::string::op { * @param[in] strl The string to be uppercase. * @return The copy of the string converted to uppercase. */ - NS_YYCC_STRING::u8string upper(const NS_YYCC_STRING::u8string_view& strl); + NS_YYCC_STRING::u8string to_upper(const NS_YYCC_STRING::u8string_view& strl); /** * @brief Split given string with specified delimiter as string view. diff --git a/src/yycc/string/parse.hpp b/src/yycc/string/parse.hpp new file mode 100644 index 0000000..5fd570e --- /dev/null +++ b/src/yycc/string/parse.hpp @@ -0,0 +1,218 @@ +#pragma once +#include "../string.hpp" +#include "op.hpp" +#include "reinterpret.hpp" +#include +#include +#include +#include + +#define NS_YYCC_STRING ::yycc::string +#define NS_YYCC_STRING_REINTERPRET ::yycc::string::reinterpret +#define NS_YYCC_STRING_OP ::yycc::string::op + +namespace yycc::string::parse { + + // Developer Notes: + // Reference: https://zh.cppreference.com/w/cpp/utility/from_chars + + /// @brief The error kind when parsing string into number. + enum class ParseError { + PartiallyParsed, ///< Only a part of given string was parsed. The whole string may be invalid. + InvalidString, ///< Given string is a invalid number string. + OutOfRange, ///< Given string is valid but its value out of the range of given number type. + }; + + template, int> = 0> + using ParseResult = std::variant; + + template, int> = 0> + ParseResult _priv_parse(const NS_YYCC_STRING::u8string_view& strl, std::chars_format fmt) { + namespace reinterpret = NS_YYCC_STRING_REINTERPRET; + + T rv; + const auto* head = reinterpret::as_ordinary(strl.data()); + const auto* tail = reinterpret::as_ordinary(strl.data() + strl.size()); + auto [ptr, ec] = std::from_chars(head, tail, rv, fmt); + + if (ec == std::errc()) { + // Parse completely. + // But we need to check whether the whole string was parsed. + if (ptr == tail) return rv; + else return ParseError::PartiallyParsed; + } else if (ec == std::errc::invalid_argument) { + // Given string is invalid + return ParseError::InvalidString; + } else if (ec == std::errc::result_out_of_range) { + // Given string is out of range + return ParseError::OutOfRange; + } else { + // Unreachable + throw std::runtime_error("invalid ec."); + } + } + + template && !std::is_same_v, int> = 0> + ParseResult _priv_parse(const NS_YYCC_STRING::u8string_view& strl, int base) { + namespace reinterpret = NS_YYCC_STRING_REINTERPRET; + + T rv; + const auto* head = reinterpret::as_ordinary(strl.data()); + const auto* tail = reinterpret::as_ordinary(strl.data() + strl.size()); + auto [ptr, ec] = std::from_chars(head, tail, rv, base); + + if (ec == std::errc()) { + // Parse completely. + // But we need to check whether the whole string was parsed. + if (ptr == tail) return rv; + else return ParseError::PartiallyParsed; + } else if (ec == std::errc::invalid_argument) { + // Given string is invalid + return ParseError::InvalidString; + } else if (ec == std::errc::result_out_of_range) { + // Given string is out of range + return ParseError::OutOfRange; + } else { + // Unreachable + throw std::runtime_error("invalid ec."); + } + } + + template, int> = 0> + ParseResult _priv_parse(const NS_YYCC_STRING::u8string_view& strl) { + // Get lower case + auto lower_case = NS_YYCC_STRING_OP::to_lower(strl); + // Compare result + if (strl == YYCC_U8("true")) return true; + else if (strl == YYCC_U8("false")) return false; + else return ParseError::InvalidString; + } + + /** + * @brief Try parsing given string to floating point types. + * @tparam T The type derived from floating point type. + * @param[in] strl The string need to be parsed. + * @param[out] num + * The variable receiving result. + * There is no guarantee that the content is not modified when parsing failed. + * @param[in] fmt The floating point format used when try parsing. + * @return True if success, otherwise false. + */ + template, int> = 0> + bool try_parse(const NS_YYCC_STRING::u8string_view& strl, + T& num, + std::chars_format fmt = std::chars_format::general) { + auto rv = _priv_parse(strl, fmt); + if (const auto* ptr = std::get_if(rv)) { + num = *ptr; + return true; + } else if (const auto* ptr = std::get_if(rv)) { + return false; + } else { + // Unreachable + throw std::runtime_error("unreachable code."); + } + } + /** + * @brief Try parsing given string to integral types. + * @tparam T The type derived from integral type except bool type. + * @param[in] strl The string need to be parsed. + * @param[out] num + * The variable receiving result. + * There is no guarantee that the content is not modified when parsing failed. + * @param[in] base Integer base to use: a value between 2 and 36 (inclusive). + * @return True if success, otherwise false. + */ + template && !std::is_same_v, int> = 0> + bool try_parse(const NS_YYCC_STRING::u8string_view& strl, T& num, int base = 10) { + auto rv = _priv_parse(strl, base); + if (const auto* ptr = std::get_if(rv)) { + num = *ptr; + return true; + } else if (const auto* ptr = std::get_if(rv)) { + return false; + } else { + // Unreachable + throw std::runtime_error("unreachable code."); + } + } + /** + * @brief Try parsing given string to bool types. + * @tparam T The type derived from bool type. + * @param[in] strl The string need to be parsed ("true" or "false", case insensitive). + * @param[out] num + * The variable receiving result. + * There is no guarantee that the content is not modified when parsing failed. + * @return True if success, otherwise false. + */ + template, int> = 0> + bool try_parse(const NS_YYCC_STRING::u8string_view& strl, T& num) { + auto rv = _priv_parse(strl); + if (const auto* ptr = std::get_if(rv)) { + num = *ptr; + return true; + } else if (const auto* ptr = std::get_if(rv)) { + return false; + } else { + // Unreachable + throw std::runtime_error("unreachable code."); + } + } + + /** + * @brief Parse given string to floating point types. + * @tparam T The type derived from floating point type. + * @param[in] strl The string need to be parsed. + * @param[in] fmt The floating point format used when try parsing. + * @return + * The parsing result. + * There is no guarantee about the content of this return value when parsing failed. + * It may be any possible value but usually is its default value. + * @exception std::invalid_argument Can not parse given string. + */ + template, int> = 0> + T parse(const NS_YYCC_STRING::u8string_view& strl, + std::chars_format fmt = std::chars_format::general) { + T rv; + if (try_parse(strl, rv, fmt)) return rv; + else throw std::invalid_argument("can not parse given string"); + } + /** + * @brief Parse given string to integral type types. + * @tparam T The type derived from integral type except bool type. + * @param[in] strl The string need to be parsed. + * @param[in] base Integer base to use: a value between 2 and 36 (inclusive). + * @return + * The parsing result. + * There is no guarantee about the content of this return value when parsing failed. + * It may be any possible value but usually is its default value. + * @exception std::invalid_argument Can not parse given string. + */ + template && !std::is_same_v, int> = 0> + T parse(const NS_YYCC_STRING::u8string_view& strl, int base = 10) { + T rv; + if (try_parse(strl, rv, base)) return rv; + else throw std::invalid_argument("can not parse given string"); + } + /** + * @brief Parse given string to bool types. + * @tparam T The type derived from bool type. + * @param[in] strl The string need to be parsed ("true" or "false", case insensitive). + * @return + * The parsing result. + * There is no guarantee about the content of this return value when parsing failed. + * It may be any possible value but usually is its default value. + * @exception std::invalid_argument Can not parse given string. + */ + template, int> = 0> + T parse(const NS_YYCC_STRING::u8string_view& strl) { + T rv; + if (try_parse(strl, rv)) return rv; + else throw std::invalid_argument("can not parse given string"); + } + +} // namespace yycc::string::parse + +#undef NS_YYCC_STRING_OP +#undef NS_YYCC_STRING_REINTERPRET +#undef NS_YYCC_STRING diff --git a/src/yycc/string/stringify.hpp b/src/yycc/string/stringify.hpp new file mode 100644 index 0000000..4e02c86 --- /dev/null +++ b/src/yycc/string/stringify.hpp @@ -0,0 +1,92 @@ +#pragma once +#include "../string.hpp" +#include "reinterpret.hpp" +#include +#include +#include +#include + +#define NS_YYCC_STRING ::yycc::string +#define NS_YYCC_STRING_REINTERPRET ::yycc::string::reinterpret + +namespace yycc::string::stringify { + + // Developer Notes: + // Reference: https://en.cppreference.com/w/cpp/utility/to_chars + // Default float precision = 6 is gotten from: https://en.cppreference.com/w/c/io/fprintf + + inline constexpr size_t STRINGIFY_BUFFER_SIZE = 64u; + using StringifyBuffer = std::array; + + /** + * @brief Return the string representation of given floating point value. + * @tparam T The type derived from floating point type. + * @param[in] num The value need to get string representation. + * @param[in] fmt The floating point format used when getting string representation. + * @param[in] precision The floating point precision used when getting string representation. + * @return The string representation of given value. + */ + template, int> = 0> + NS_YYCC_STRING::u8string stringify(T num, + std::chars_format fmt = std::chars_format::general, + int precision = 6) { + namespace reinterpret = NS_YYCC_STRING_REINTERPRET; + StringifyBuffer buffer; + auto [ptr, ec] = std::to_chars(reinterpret::as_ordinary(buffer.data()), + reinterpret::as_ordinary(buffer.data() + buffer.size()), + num, + fmt, + precision); + if (ec == std::errc()) { + return NS_YYCC_STRING::u8string(buffer.data(), + reinterpret::as_utf8(ptr) - buffer.data()); + } else if (ec == std::errc::value_too_large) { + // Too short buffer. This should not happened + throw std::out_of_range("stringify() buffer is not sufficient."); + } else { + // Unreachable + throw std::runtime_error("unreachable code."); + } + } + /** + * @brief Return the string representation of given integral value. + * @tparam T The type derived from integral type except bool type. + * @param[in] num The value need to get string representation. + * @param[in] base Integer base used when getting string representation: a value between 2 and 36 (inclusive). + * @return The string representation of given value. + */ + template && !std::is_same_v, int> = 0> + NS_YYCC_STRING::u8string stringify(T num, int base = 10) { + namespace reinterpret = NS_YYCC_STRING_REINTERPRET; + StringifyBuffer buffer; + auto [ptr, ec] = std::to_chars(reinterpret::as_ordinary(buffer.data()), + reinterpret::as_ordinary(buffer.data() + buffer.size()), + num, + base); + if (ec == std::errc()) { + return NS_YYCC_STRING::u8string(buffer.data(), + reinterpret::as_utf8(ptr) - buffer.data()); + } else if (ec == std::errc::value_too_large) { + // Too short buffer. This should not happened + throw std::out_of_range("stringify() buffer is not sufficient."); + } else { + // Unreachable + throw std::runtime_error("unreachable code."); + } + } + /** + * @brief Return the string representation of given bool value. + * @tparam T The type derived from bool type. + * @param[in] num The value need to get string representation. + * @return The string representation of given value ("true" or "false"). + */ + template, int> = 0> + NS_YYCC_STRING::u8string stringify(T num) { + if (num) return NS_YYCC_STRING::u8string(YYCC_U8("true")); + else return NS_YYCC_STRING::u8string(YYCC_U8("false")); + } + +} // namespace yycc::string::stringify + +#undef NS_YYCC_STRING_REINTERPRET +#undef NS_YYCC_STRING diff --git a/testbench/CMakeLists.txt b/testbench/CMakeLists.txt index c5ee56d..0c6cf35 100644 --- a/testbench/CMakeLists.txt +++ b/testbench/CMakeLists.txt @@ -6,7 +6,6 @@ PRIVATE main.cpp yycc/constraint.cpp yycc/constraint/builder.cpp - yycc/string.cpp yycc/string/op.cpp yycc/string/reinterpret.cpp ) diff --git a/testbench/yycc/string.cpp b/testbench/yycc/string.cpp deleted file mode 100644 index 257ceaf..0000000 --- a/testbench/yycc/string.cpp +++ /dev/null @@ -1,2 +0,0 @@ - -namespace yycctest::string {} diff --git a/testbench/yycc/string/op.cpp b/testbench/yycc/string/op.cpp index ae23a75..47453fa 100644 --- a/testbench/yycc/string/op.cpp +++ b/testbench/yycc/string/op.cpp @@ -48,12 +48,12 @@ namespace yycctest::string::op { } TEST(StringOp, Lower) { - auto rv = OP::lower(YYCC_U8("LOWER")); + auto rv = OP::to_lower(YYCC_U8("LOWER")); EXPECT_EQ(rv, YYCC_U8("lower")); } TEST(StringOp, Upper) { - auto rv = OP::upper(YYCC_U8("upper")); + auto rv = OP::to_upper(YYCC_U8("upper")); EXPECT_EQ(rv, YYCC_U8("UPPER")); }