refactor: add Rust infrastructure: Option, Result and panic

This commit is contained in:
yyc12345 2025-06-23 16:22:55 +08:00
parent 28ff7008a8
commit 3abd0969c0
10 changed files with 297 additions and 11 deletions

View File

@ -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

View File

@ -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

23
src/yycc/rust/option.hpp Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include <optional>
/// @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<typename T>
using Option = std::optional<T>;
template<typename OptionType, typename... Args>
OptionType Some(Args &&... args) {
return OptionType(std::in_place, std::forward<Args>(args)...);
}
template<typename OptionType>
OptionType None() {
return OptionType(std::nullopt);
}
}

43
src/yycc/rust/panic.cpp Normal file
View File

@ -0,0 +1,43 @@
#include "panic.hpp"
#include "../macro/feature_probe.hpp"
#include <cstdlib>
#include <iomanip>
#include <iostream>
#if defined(YYCC_CPPFEAT_STACKTRACE)
#include <stacktrace>
#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<Color::Red>;
// 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

59
src/yycc/rust/panic.hpp Normal file
View File

@ -0,0 +1,59 @@
#pragma once
#include "../macro/feature_probe.hpp"
#include <string_view>
#if defined(YYCC_CPPFEAT_FORMAT)
#include <format>
#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

View File

@ -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<typename T>
// using Result = std::expected<T, Error>;
#endif
}
#undef NS_YYCC_STRING_PARSE

View File

@ -1,5 +1,5 @@
#pragma once
#include <cinttypes>
#include <cstdint>
#include <cstddef>
#include "../string.hpp"

86
src/yycc/rust/result.hpp Normal file
View File

@ -0,0 +1,86 @@
#pragma once
#include "../macro/feature_probe.hpp"
#if defined(YYCC_CPPFEAT_EXPECTED)
#include <expected>
#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<typename T, typename E>
using Result = std::expected<T, E>;
/**
* @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<typename ResultType, typename... Args>
ResultType Ok(Args &&...args) {
using T = ResultType::value_type;
if constexpr (!std::is_void_v<T>) {
return ResultType(std::in_place, std::forward<Args>(args)...);
} else {
static_assert(sizeof...(Args) == 0, "Ok<void> 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<typename ResultType, typename... Args>
ResultType Err(Args &&...args) {
return ResultType(std::unexpect, std::forward<Args>(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<T, E>>(result.error());`简单地将类型Ea转换为类型Eb。
// YYC MARK:
// 在Rust中如果你想获得非预期值的人类可读说明你就必须要实现名为Display的trait。
// 而你不需要在C++中这样做,你必须编写自己的转换函数,以适应各种输出需求。
// 例如在使用std::format时你需要编写适合std::format的格式化适配器。
// 又如你在使用std::cerr的operator<<重载时,你也需要编写合适的适配器。
#endif
}

View File

@ -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<typename T, std::enable_if_t<!std::is_same_v<T, ParseError>, int> = 0>
using ParseResult = std::variant<T, ParseError>;
/**
* @private
* @brief priv_parse
* @param strl
* @param fmt
* @return
*/
template<typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
ParseResult<T> _priv_parse(const NS_YYCC_STRING::u8string_view& strl, std::chars_format fmt) {
ParseResult<T> 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<typename T, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, int> = 0>
ParseResult<T> _priv_parse(const NS_YYCC_STRING::u8string_view& strl, int base) {
ParseResult<T> 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<typename T, std::enable_if_t<std::is_same_v<T, bool>, int> = 0>
ParseResult<T> _priv_parse(const NS_YYCC_STRING::u8string_view& strl) {
ParseResult<T> 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<T>(strl, fmt);
auto rv = priv_parse<T>(strl, fmt);
if (const auto* ptr = std::get_if<T>(rv)) {
num = *ptr;
return true;
@ -125,7 +148,7 @@ namespace yycc::string::parse {
*/
template<typename T, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, int> = 0>
bool try_parse(const NS_YYCC_STRING::u8string_view& strl, T& num, int base = 10) {
auto rv = _priv_parse<T>(strl, base);
auto rv = priv_parse<T>(strl, base);
if (const auto* ptr = std::get_if<T>(rv)) {
num = *ptr;
return true;
@ -147,7 +170,7 @@ namespace yycc::string::parse {
*/
template<typename T, std::enable_if_t<std::is_same_v<T, bool>, int> = 0>
bool try_parse(const NS_YYCC_STRING::u8string_view& strl, T& num) {
auto rv = _priv_parse<T>(strl);
auto rv = priv_parse<T>(strl);
if (const auto* ptr = std::get_if<T>(rv)) {
num = *ptr;
return true;

View File

@ -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<NS_YYCC_STRING::u8char, STRINGIFY_BUFFER_SIZE>;
/**
@ -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