From 3abd0969c014429d44b9d72448e27a3ee5ec86fe Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Mon, 23 Jun 2025 16:22:55 +0800 Subject: [PATCH] refactor: add Rust infrastructure: Option, Result and panic --- src/CMakeLists.txt | 7 +++ src/yycc/macro/feature_probe.hpp | 23 ++++++++- src/yycc/rust/option.hpp | 23 +++++++++ src/yycc/rust/panic.cpp | 43 ++++++++++++++++ src/yycc/rust/panic.hpp | 59 ++++++++++++++++++++++ src/yycc/rust/parse.hpp | 24 +++++++++ src/yycc/rust/primitive.hpp | 2 +- src/yycc/rust/result.hpp | 86 ++++++++++++++++++++++++++++++++ src/yycc/string/parse.hpp | 35 ++++++++++--- src/yycc/string/stringify.hpp | 6 ++- 10 files changed, 297 insertions(+), 11 deletions(-) create mode 100644 src/yycc/rust/option.hpp create mode 100644 src/yycc/rust/panic.cpp create mode 100644 src/yycc/rust/panic.hpp create mode 100644 src/yycc/rust/result.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4177601..bbb7e6e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,6 +13,7 @@ PRIVATE # Sources yycc/string/reinterpret.cpp yycc/string/op.cpp + yycc/rust/panic.cpp # YYCC/COMHelper.cpp # YYCC/ArgParser.cpp # YYCC/ConfigManager.cpp @@ -43,7 +44,13 @@ FILES yycc/string.hpp yycc/string/reinterpret.hpp yycc/string/op.hpp + yycc/string/parse.hpp + yycc/string/stringify.hpp yycc/rust/primitive.hpp + yycc/rust/panic.hpp + yycc/rust/option.hpp + yycc/rust/result.hpp + yycc/rust/parse.hpp yycc/windows/unsafe_suppressor.hpp yycc/windows/import_guard_head.hpp yycc/windows/import_guard_tail.hpp diff --git a/src/yycc/macro/feature_probe.hpp b/src/yycc/macro/feature_probe.hpp index 43cf737..b5cae23 100644 --- a/src/yycc/macro/feature_probe.hpp +++ b/src/yycc/macro/feature_probe.hpp @@ -4,6 +4,8 @@ // __cplusplus macro need special compiler switch enabled when compiling. // So we use _MSVC_LANG check it instead. +// ===== C++ Version ===== + // Detect C++ 20 #if __cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) #define YYCC_CPPFEAT_GE_CPP20 @@ -14,6 +16,8 @@ #define YYCC_CPPFEAT_GE_CPP23 #endif +// ===== C++ Features ===== + // Check whether there is support of `contains` for `set` and `map` including their varients. #if defined(YYCC_CPPFEAT_GE_CPP20) #define YYCC_CPPFEAT_CONTAINS @@ -21,10 +25,25 @@ // 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 + #define YYCC_CPPFEAT_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 + #define YYCC_CPPFEAT_EXPECTED +#endif + +// Check whether there is support of `std::format`. +#if defined(YYCC_CPPFEAT_GE_CPP20) + #define YYCC_CPPFEAT_FORMAT +#endif + +// Check whether there is support of `__VA_OPT__` +#if defined(YYCC_CPPFEAT_GE_CPP20) + #define YYCC_CPPFEAT_VA_OPT +#endif + +// Check whether there is support of `std::stacktrace` and its formatter. +#if (defined(__cpp_lib_starts_ends_with) && defined(__cpp_lib_formatters)) || defined(YYCC_CPPFEAT_GE_CPP23) + #define YYCC_CPPFEAT_STACKTRACE #endif diff --git a/src/yycc/rust/option.hpp b/src/yycc/rust/option.hpp new file mode 100644 index 0000000..93ee435 --- /dev/null +++ b/src/yycc/rust/option.hpp @@ -0,0 +1,23 @@ +#pragma once +#include + +/// @brief The reproduction of Rust Option type. +/// @details +/// This namespace reproduce Rust Option type, and its members Some and None in C++. +/// However Option is not important than Result, so its implementation is very casual. +namespace yycc::rust::option { + + template + using Option = std::optional; + + template + OptionType Some(Args &&... args) { + return OptionType(std::in_place, std::forward(args)...); + } + + template + OptionType None() { + return OptionType(std::nullopt); + } + +} diff --git a/src/yycc/rust/panic.cpp b/src/yycc/rust/panic.cpp new file mode 100644 index 0000000..998f2f4 --- /dev/null +++ b/src/yycc/rust/panic.cpp @@ -0,0 +1,43 @@ +#include "panic.hpp" +#include "../macro/feature_probe.hpp" + +#include +#include +#include +#if defined(YYCC_CPPFEAT_STACKTRACE) +#include +#endif + +namespace yycc::rust::panic { + + void panic(const char* file, int line, const std::string_view& msg) { + // Output message in stderr. + auto& dst = std::cerr; + + // TODO: Fix colorful output when finishing `termcolor` lib. + + // Print error message if we support it. + // // Setup color + // dst << FOREGROUND; + // File name and line number message + dst << "program paniked at " << std::quoted(file) << ":Ln" << line << std::endl; + // User custom message + dst << "note: " << msg << std::endl; + // Stacktrace message if we support it. +#if defined(YYCC_CPPFEAT_STACKTRACE) + dst << "stacktrace: " << std::endl; + dst << std::stacktrace::current() << std::endl; +#else + dst << "there is no stacktrace because your C++ runtime do not support it." << std::endl; +#endif + // // Restore color + // dst << RESET; + + // Make sure all messages are flushed into screen. + dst.flush(); + + // Force exit + std::abort(); + } + +} // namespace yycc::rust::panic diff --git a/src/yycc/rust/panic.hpp b/src/yycc/rust/panic.hpp new file mode 100644 index 0000000..1dfc105 --- /dev/null +++ b/src/yycc/rust/panic.hpp @@ -0,0 +1,59 @@ +#pragma once +#include "../macro/feature_probe.hpp" +#include + +#if defined(YYCC_CPPFEAT_FORMAT) +#include +#endif + +/// @brief +namespace yycc::rust::panic { + + // TODO: Move these comments into Doxygen comments of this namespace. + + // YYC MARK: + // 在使用Rust编写程序后,我深刻地认识到,将错误立即进行处理的必要性。 + // 而在陷入不可恢复错误后,也应当立即退出程序并报告错误,这是对程序健壮性的保证。 + // 因此我引入了这个命名空间,并带来了与Rust中panic!宏等效的宏和函数。 + + // YYC MARK: + // 遗憾的是,我无法改变标准库中的异常机制。 + // 标准库中会抛出异常的内容仍然会抛出异常,我不能阻止它们。 + // 因此我在这里规定,在Stardust项目中,任何C++异常都应立即被视为错误,并导致程序崩溃退出。 + // 也正因为于此,规定不允许注册任何未处理错误的回调,以防止意料之外的继续运行。 + // 而对于我们自己编写的,能控制的代码,则应用本文件中提供的宏来代替异常抛出。 + // 这样一来,我们代码中的非预期行为会使得程序立即退出,并输出错误信息和堆栈。 + // 而标准库中的异常,也会使得程序退出,只不过没有堆栈信息罢了。 + + /** + * @brief 像Rust的panic!宏一样立即使整个程序崩溃。 + * @details 宏参数为需要附加的提示信息。 + */ +#define RS_PANIC(msg) ::yycc::rust::panic::panic(__FILE__, __LINE__, (msg)) + + /** + * @brief 像Rust的panic!宏一样立即使整个程序崩溃。 + * @details + * 宏参数为需要格式化的信息和参数,格式和参数请参考std::format。 + * 因为本质上这个宏就是调用std::format来实现的。 + */ +#if defined(YYCC_CPPFEAT_FORMAT) +#if defined(YYCC_CPPFEAT_VA_OPT) +#define RS_PANICF(msg, ...) RS_PANIC(std::format(msg __VA_OPT__(, ) __VA_ARGS__)) +#else +#define RS_PANICF(msg, ...) RS_PANIC(std::format(msg, ##__VA_ARGS__)) +#endif +#endif + + /** + * @brief 像Rust的panic!宏一样立即使整个程序崩溃。 + * @details + * 此函数是宏所调用的真正的崩溃输出函数。 + * 崩溃信息将被写入到stderr中,信息中还包含堆栈信息。 + * @param[in] file Panic时的源代码文件。该参数一般由宏代为填写。 + * @param[in] line Panic时的源代码文件中的行号。该参数一般由宏代为填写。 + * @param[in] msg Panic时需要显示的提示信息。 + */ + [[noreturn]] void panic(const char* file, int line, const std::string_view& msg); + +} // namespace yycc::rust::panic diff --git a/src/yycc/rust/parse.hpp b/src/yycc/rust/parse.hpp index e69de29..c42ed84 100644 --- a/src/yycc/rust/parse.hpp +++ b/src/yycc/rust/parse.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "../macro/feature_probe.hpp" +#include "../string/parse.hpp" +#include "panic.hpp" +#include "result.hpp" + +#define NS_YYCC_STRING_PARSE ::yycc::string::parse + +namespace yycc::rust::parse { + +#if defined(YYCC_CPPFEAT_EXPECTED) + + using Error = NS_YYCC_STRING_PARSE::ParseError; + + // template + // using Result = std::expected; + + + +#endif + +} + +#undef NS_YYCC_STRING_PARSE diff --git a/src/yycc/rust/primitive.hpp b/src/yycc/rust/primitive.hpp index 7a4d3cf..81f0855 100644 --- a/src/yycc/rust/primitive.hpp +++ b/src/yycc/rust/primitive.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include #include "../string.hpp" diff --git a/src/yycc/rust/result.hpp b/src/yycc/rust/result.hpp new file mode 100644 index 0000000..da39ea7 --- /dev/null +++ b/src/yycc/rust/result.hpp @@ -0,0 +1,86 @@ +#pragma once +#include "../macro/feature_probe.hpp" + +#if defined(YYCC_CPPFEAT_EXPECTED) +#include +#endif + +/// @brief The reproduction of Rust Option type. +/// @details +/// After writing a program in Rust, I deeply realized the advantages of Rust and its indispensable infrastructure, \c Result. +/// Therefore, it is essential to introduce Result into C++ to strengthen the security of the code. +/// I simulated Result in C++ as best I could, and its members Ok and Err (which are actually done by DeepSeek). +/// +/// Why not write it the C++ way? Because the way C++ uses Result is too ugly and not explicit enough. +/// In the way C++ is written, the expected value is gotten by returning directly, +/// and if you encounter void specialization, you must write a pair of curly braces, which is very unclear. +/// For unexpected value, you need to manually build it by calling \c std::unexpected, which is more of a pain. +/// If you need to construct an unexpected value in place, you even need to put a \c std::in_place as the first argument to the constructor, +/// Otherwise, the std::unexpected constructor will not forward the given arguments to the underlying constructor. +/// +/// +/// @remarks +/// This namespace only work with environment supporting `std::expected` (i.e. C++ 23). +namespace yycc::rust::result { + +#if defined(YYCC_CPPFEAT_EXPECTED) + + /** + * @brief Equivalent Rust \c Result in C++ + */ + template + using Result = std::expected; + + /** + * @brief Equvialent Rust \c Result::Ok in C++ + * @param[in] args The arguments for building expected value. + * @return An built Result instance with expected value. + */ + template + ResultType Ok(Args &&...args) { + using T = ResultType::value_type; + if constexpr (!std::is_void_v) { + return ResultType(std::in_place, std::forward(args)...); + } else { + static_assert(sizeof...(Args) == 0, "Ok cannot accept arguments"); + return ResultType(std::in_place); + } + } + + /** + * @brief Equvialent Rust \c Result::Err in C++ + * @param[in] args The arguments for building unexpected value. + * @return An built Result instance with unexpected value. + */ + template + ResultType Err(Args &&...args) { + return ResultType(std::unexpect, std::forward(args)...); + } + + // TODO: Move these comments into Doxygen comments of this namespace. + + // YYC MARK: + // Result类型中,类型E可以是根据你需求的任意值。 + // 在Rust中,一个非期望值类型Ea可以被转换为另一个非期望值类型Eb。 + // 这一特性通过From trait来实现。这样你就可以在一个函数中安全地将一种非期望值包装成另一种非期望值。 + // 但在C++中,我们有C++的方式来做同样的事。 + // 假设对于每一个类型E,我们都分别定义一个struct来描述它们, + // 那么我们只需要为struct添加一些额外的构造函数,就可以将它们从一个类型转换到另一个。 + // --- + // 例如,类型Ea是一个名为IoError的struct。 + // 在这个struct中,有一个类型为IoErrorKind的成员,指示了该IO错误的类别。 + // 与此同时,它还有一个以自己类型为唯一参数的构造函数,用于构建(复制或移动)它自己。 + // 现在在一个函数中。我们希望将它转换为另一个名为SystemError的类型Eb。 + // 你需要做的仅仅是新建一个名为SystemError的struct,然后为它编写所有必要的构造函数和其它函数。 + // 然后,重点是,为它添加一个形参类型为`const IoError&`的构造函数。 + // 这样一来,我们就可以通过类似这样的调用:`Err>(result.error());`,简单地将类型Ea转换为类型Eb。 + + // YYC MARK: + // 在Rust中,如果你想获得非预期值的人类可读说明,你就必须要实现名为Display的trait。 + // 而你不需要在C++中这样做,你必须编写自己的转换函数,以适应各种输出需求。 + // 例如,在使用std::format时,你需要编写适合std::format的格式化适配器。 + // 又如,你在使用std::cerr的operator<<重载时,你也需要编写合适的适配器。 + +#endif + +} diff --git a/src/yycc/string/parse.hpp b/src/yycc/string/parse.hpp index 5fd570e..9a5e93f 100644 --- a/src/yycc/string/parse.hpp +++ b/src/yycc/string/parse.hpp @@ -16,6 +16,7 @@ namespace yycc::string::parse { // Developer Notes: // Reference: https://zh.cppreference.com/w/cpp/utility/from_chars + /// @private /// @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. @@ -23,11 +24,20 @@ namespace yycc::string::parse { OutOfRange, ///< Given string is valid but its value out of the range of given number type. }; + /// @private + /// @brief The return value of internal parse function which ape `std::expected`. template, int> = 0> using ParseResult = std::variant; + /** + * @private + * @brief priv_parse + * @param strl + * @param fmt + * @return + */ template, int> = 0> - ParseResult _priv_parse(const NS_YYCC_STRING::u8string_view& strl, std::chars_format fmt) { + ParseResult priv_parse(const NS_YYCC_STRING::u8string_view& strl, std::chars_format fmt) { namespace reinterpret = NS_YYCC_STRING_REINTERPRET; T rv; @@ -52,8 +62,15 @@ namespace yycc::string::parse { } } + /** + * @private + * @brief priv_parse + * @param strl + * @param base + * @return + */ template && !std::is_same_v, int> = 0> - ParseResult _priv_parse(const NS_YYCC_STRING::u8string_view& strl, int base) { + ParseResult priv_parse(const NS_YYCC_STRING::u8string_view& strl, int base) { namespace reinterpret = NS_YYCC_STRING_REINTERPRET; T rv; @@ -78,8 +95,14 @@ namespace yycc::string::parse { } } + /** + * @private + * @brief priv_parse + * @param strl + * @return + */ template, int> = 0> - ParseResult _priv_parse(const NS_YYCC_STRING::u8string_view& strl) { + 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 @@ -102,7 +125,7 @@ namespace yycc::string::parse { 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); + auto rv = priv_parse(strl, fmt); if (const auto* ptr = std::get_if(rv)) { num = *ptr; return true; @@ -125,7 +148,7 @@ namespace yycc::string::parse { */ 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); + auto rv = priv_parse(strl, base); if (const auto* ptr = std::get_if(rv)) { num = *ptr; return true; @@ -147,7 +170,7 @@ namespace yycc::string::parse { */ template, int> = 0> bool try_parse(const NS_YYCC_STRING::u8string_view& strl, T& num) { - auto rv = _priv_parse(strl); + auto rv = priv_parse(strl); if (const auto* ptr = std::get_if(rv)) { num = *ptr; return true; diff --git a/src/yycc/string/stringify.hpp b/src/yycc/string/stringify.hpp index 4e02c86..d06673f 100644 --- a/src/yycc/string/stringify.hpp +++ b/src/yycc/string/stringify.hpp @@ -15,7 +15,9 @@ namespace yycc::string::stringify { // 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 + /// @private inline constexpr size_t STRINGIFY_BUFFER_SIZE = 64u; + /// @private using StringifyBuffer = std::array; /** @@ -41,7 +43,7 @@ namespace yycc::string::stringify { 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 + // Too short buffer. This should not happen. throw std::out_of_range("stringify() buffer is not sufficient."); } else { // Unreachable @@ -67,7 +69,7 @@ namespace yycc::string::stringify { 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 + // Too short buffer. This should not happen. throw std::out_of_range("stringify() buffer is not sufficient."); } else { // Unreachable