diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bbb7e6e..ec954ad 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -51,6 +51,7 @@ FILES yycc/rust/option.hpp yycc/rust/result.hpp yycc/rust/parse.hpp + yycc/rust/stringify.hpp yycc/windows/unsafe_suppressor.hpp yycc/windows/import_guard_head.hpp yycc/windows/import_guard_tail.hpp diff --git a/src/yycc/prelude/rust.hpp b/src/yycc/prelude/rust.hpp index b7ddcd6..8ad4525 100644 --- a/src/yycc/prelude/rust.hpp +++ b/src/yycc/prelude/rust.hpp @@ -5,6 +5,9 @@ // Rust prelude section #include "../rust/primitive.hpp" +#include "../rust/result.hpp" +#include "../rust/option.hpp" +#include "../rust/panic.hpp" #include namespace yycc::prelude::rust { @@ -35,6 +38,14 @@ namespace yycc::prelude::rust { using String = ::yycc::string::u8string; template using Vec = std::vector; + + // Expose Result and Option + using namespace ::yycc::rust::option; + using namespace ::yycc::rust::result; + + // Panic are introduced by including header file + // so we do not need re-expose it. + } // namespace yycc::prelude::rust // Expose all members diff --git a/src/yycc/rust/option.hpp b/src/yycc/rust/option.hpp index 93ee435..11288f6 100644 --- a/src/yycc/rust/option.hpp +++ b/src/yycc/rust/option.hpp @@ -11,7 +11,7 @@ namespace yycc::rust::option { using Option = std::optional; template - OptionType Some(Args &&... args) { + OptionType Some(Args &&...args) { return OptionType(std::in_place, std::forward(args)...); } @@ -20,4 +20,4 @@ namespace yycc::rust::option { return OptionType(std::nullopt); } -} +} // namespace yycc::rust::option diff --git a/src/yycc/rust/parse.hpp b/src/yycc/rust/parse.hpp index c42ed84..87e6215 100644 --- a/src/yycc/rust/parse.hpp +++ b/src/yycc/rust/parse.hpp @@ -4,21 +4,96 @@ #include "panic.hpp" #include "result.hpp" +#define NS_YYCC_STRING ::yycc::string #define NS_YYCC_STRING_PARSE ::yycc::string::parse +#define NS_YYCC_RUST_RESULT ::yycc::rust::result +/** + * @namespace yycc::rust::parse + * @brief Provides Rust-inspired parsing utilities for converting strings to various types. + * @details + * This namespace contains template functions for parsing strings into different types + * (floating-point, integral, boolean) with Rust-like Result error handling. + */ namespace yycc::rust::parse { #if defined(YYCC_CPPFEAT_EXPECTED) + /// @brief The error type of parsing. using Error = NS_YYCC_STRING_PARSE::ParseError; - // template - // using Result = std::expected; + /// @brief The result type of parsing. + /// @tparam T The expected value type in result. + template + using Result = NS_YYCC_RUST_RESULT::Result; + /** + * @brief Parses a string into a floating-point value. + * @tparam T Floating-point type to parse into (float, double, etc.) + * @param strl String view to parse + * @param fmt Formatting flags for parsing (default: general) + * @return Result containing either the parsed value or an error + */ + template, int> = 0> + Result parse(const NS_YYCC_STRING::u8string_view& strl, + std::chars_format fmt = std::chars_format::general) { + auto rv = NS_YYCC_STRING_PARSE::priv_parse(strl, fmt); + if (const auto* ptr = std::get_if(&rv)) { + return NS_YYCC_RUST_RESULT::Ok>(*ptr); + } else if (const auto* ptr = std::get_if(&rv)) { + return NS_YYCC_RUST_RESULT::Err>(*ptr); + } else { + // Unreachable + RS_PANIC("unreachable code."); + } + } + + /** + * @brief Parses a string into an integral value (excluding bool). + * @tparam T Integral type to parse into (int, long, etc.) + * @param strl String view to parse + * @param base Numeric base for parsing (default: 10) + * @return Result containing either the parsed value or an error + */ + template && !std::is_same_v, int> = 0> + Result parse(const NS_YYCC_STRING::u8string_view& strl, int base = 10) { + auto rv = NS_YYCC_STRING_PARSE::priv_parse(strl, base); + + if (const auto* ptr = std::get_if(&rv)) { + return NS_YYCC_RUST_RESULT::Ok>(*ptr); + } else if (const auto* ptr = std::get_if(&rv)) { + return NS_YYCC_RUST_RESULT::Err>(*ptr); + } else { + // Unreachable + RS_PANIC("unreachable code."); + } + } + + /** + * @brief Parses a string into a boolean value. + * @tparam T Must be bool type + * @param strl String view to parse + * @return Result containing either the parsed value or an error + */ + template, int> = 0> + Result parse(const NS_YYCC_STRING::u8string_view& strl) { + auto rv = NS_YYCC_STRING_PARSE::priv_parse(strl); + + if (const auto* ptr = std::get_if(&rv)) { + return NS_YYCC_RUST_RESULT::Ok>(*ptr); + } else if (const auto* ptr = std::get_if(&rv)) { + return NS_YYCC_RUST_RESULT::Err>(*ptr); + } else { + // Unreachable + RS_PANIC("unreachable code."); + } + } #endif } +#undef NS_YYCC_RUST_RESULT #undef NS_YYCC_STRING_PARSE +#undef NS_YYCC_STRING diff --git a/src/yycc/rust/result.hpp b/src/yycc/rust/result.hpp index 9602275..be8be32 100644 --- a/src/yycc/rust/result.hpp +++ b/src/yycc/rust/result.hpp @@ -82,4 +82,4 @@ namespace yycc::rust::result { #endif -} +} // namespace yycc::rust::result diff --git a/src/yycc/rust/stringify.hpp b/src/yycc/rust/stringify.hpp new file mode 100644 index 0000000..d360360 --- /dev/null +++ b/src/yycc/rust/stringify.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "../string/stringify.hpp" + +namespace yycc::rust::stringify { + + // There is no modification for legacy "stringify" functions like "parse". + // So we simply expose all functions into this namespace. + using namespace ::yycc::string::stringify; + +} // namespace yycc::rust::stringify diff --git a/testbench/CMakeLists.txt b/testbench/CMakeLists.txt index c648fdc..8e5d410 100644 --- a/testbench/CMakeLists.txt +++ b/testbench/CMakeLists.txt @@ -10,6 +10,8 @@ PRIVATE yycc/string/reinterpret.cpp yycc/string/parse.cpp yycc/string/stringify.cpp + yycc/rust/parse.cpp + yycc/rust/stringify.cpp ) # Setup headers target_include_directories(YYCCTestbench diff --git a/testbench/yycc/rust/parse.cpp b/testbench/yycc/rust/parse.cpp new file mode 100644 index 0000000..6d7f812 --- /dev/null +++ b/testbench/yycc/rust/parse.cpp @@ -0,0 +1,87 @@ +#include +#include +#include + +#include + +#define PARSE ::yycc::rust::parse + +namespace yycctest::rust::parse { + + // We only want to test it if C++ support it. +#if defined(YYCC_CPPFEAT_EXPECTED) + + // This namespace is just a wrapper for legacy "parse" module. + // So the test is just a copy of original implementation. + // Please update this if original test was updated. + +#define TEST_SUCCESS(type_t, expected_value, string_value, ...) \ + { \ + u8string cache_string(YYCC_U8(string_value)); \ + auto rv = PARSE::parse(cache_string, ##__VA_ARGS__); \ + ASSERT_TRUE(rv.has_value()); \ + EXPECT_EQ(rv.value(), expected_value); \ + } + +#define TEST_FAIL(type_t, string_value, ...) \ + { \ + u8string cache_string(YYCC_U8(string_value)); \ + auto rv = PARSE::parse(cache_string, ##__VA_ARGS__); \ + EXPECT_FALSE(rv.has_value()); \ + } + + TEST(RustParse, Common) { + TEST_SUCCESS(i8, INT8_C(-61), "-61"); + TEST_SUCCESS(u8, UINT8_C(200), "200"); + TEST_SUCCESS(i16, INT16_C(6161), "6161"); + TEST_SUCCESS(u16, UINT16_C(32800), "32800"); + TEST_SUCCESS(i32, INT32_C(61616161), "61616161"); + TEST_SUCCESS(u32, UINT32_C(4294967293), "4294967293"); + TEST_SUCCESS(i64, INT64_C(616161616161), "616161616161"); + TEST_SUCCESS(u64, UINT64_C(9223372036854775807), "9223372036854775807"); + + TEST_SUCCESS(float, 1.0f, "1.0"); + TEST_SUCCESS(double, 1.0, "1.0"); + + TEST_SUCCESS(bool, true, "true"); + TEST_SUCCESS(bool, false, "false"); + } + + TEST(RustParse, Radix) { + TEST_SUCCESS(u32, UINT32_C(0xffff), "ffff", 16); + TEST_SUCCESS(u32, UINT32_C(032), "032", 8); + TEST_SUCCESS(u32, UINT32_C(0B1011), "1011", 2); + } + + TEST(RustParse, CaseInsensitive) { + TEST_SUCCESS(bool, true, "tRUE"); + } + + TEST(RustParse, Overflow) { + TEST_FAIL(i8, "6161"); + TEST_FAIL(u8, "32800"); + TEST_FAIL(i16, "61616161"); + TEST_FAIL(u16, "4294967293"); + TEST_FAIL(i32, "616161616161"); + TEST_FAIL(u32, "9223372036854775807"); + TEST_FAIL(i64, "616161616161616161616161"); + TEST_FAIL(u64, "92233720368547758079223372036854775807"); + + TEST_FAIL(float, "1e40"); + TEST_FAIL(double, "1e114514"); + } + + TEST(RustParse, BadRadix) { + TEST_FAIL(u32, "fghj", 16); + TEST_FAIL(u32, "099", 8); + TEST_FAIL(u32, "12345", 2); + } + + TEST(RustParse, InvalidWords) { + TEST_FAIL(u32, "hello, world!"); + TEST_FAIL(bool, "hello, world!"); + } + +#endif + +} // namespace yycctest::rust::parse diff --git a/testbench/yycc/rust/stringify.cpp b/testbench/yycc/rust/stringify.cpp new file mode 100644 index 0000000..d2d54c7 --- /dev/null +++ b/testbench/yycc/rust/stringify.cpp @@ -0,0 +1,14 @@ +#include +#include +#include + +#include + +#define STRINGIFY ::yycc::string::stringify + +namespace yycctest::rust::stringify { + + // There is not testbench for this + // because it just a map to original implementation without any modification. + +} diff --git a/testbench/yycc/string/parse.cpp b/testbench/yycc/string/parse.cpp index 17fd0f2..75d1477 100644 --- a/testbench/yycc/string/parse.cpp +++ b/testbench/yycc/string/parse.cpp @@ -12,20 +12,22 @@ namespace yycctest::string::parse { // These 2 test macros build string container via given string. // Check `try_parse` first, and then check `parse`. -#define TEST_SUCCESS(type_t, value, string_value, ...) { \ - u8string cache_string(YYCC_U8(string_value)); \ - type_t cache; \ - ASSERT_TRUE(PARSE::try_parse(cache_string, cache, ##__VA_ARGS__)); \ - EXPECT_EQ(cache, value); \ - EXPECT_EQ(PARSE::parse(cache_string, ##__VA_ARGS__), value); \ -} +#define TEST_SUCCESS(type_t, value, string_value, ...) \ + { \ + u8string cache_string(YYCC_U8(string_value)); \ + type_t cache; \ + ASSERT_TRUE(PARSE::try_parse(cache_string, cache, ##__VA_ARGS__)); \ + EXPECT_EQ(cache, value); \ + EXPECT_EQ(PARSE::parse(cache_string, ##__VA_ARGS__), value); \ + } -#define TEST_FAIL(type_t, string_value, ...) { \ - u8string cache_string(YYCC_U8(string_value)); \ - type_t cache; \ - EXPECT_FALSE(PARSE::try_parse(cache_string, cache, ##__VA_ARGS__)); \ - EXPECT_ANY_THROW(PARSE::parse(cache_string, ##__VA_ARGS__)); \ -} +#define TEST_FAIL(type_t, string_value, ...) \ + { \ + u8string cache_string(YYCC_U8(string_value)); \ + type_t cache; \ + EXPECT_FALSE(PARSE::try_parse(cache_string, cache, ##__VA_ARGS__)); \ + EXPECT_ANY_THROW(PARSE::parse(cache_string, ##__VA_ARGS__)); \ + } TEST(StringParse, Common) { TEST_SUCCESS(i8, INT8_C(-61), "-61"); @@ -79,4 +81,4 @@ namespace yycctest::string::parse { TEST_FAIL(bool, "hello, world!"); } -} +} // namespace yycctest::string::parse diff --git a/testbench/yycc/string/stringify.cpp b/testbench/yycc/string/stringify.cpp index 2fa566a..fba5152 100644 --- a/testbench/yycc/string/stringify.cpp +++ b/testbench/yycc/string/stringify.cpp @@ -1,7 +1,7 @@ #include #include -#include #include +#include #include @@ -9,11 +9,12 @@ namespace yycctest::string::stringify { -#define TEST_SUCCESS(type_t, value, string_value, ...) { \ - type_t cache = value; \ - u8string ret = STRINGIFY::stringify(cache, ##__VA_ARGS__); \ - EXPECT_EQ(ret, YYCC_U8(string_value)); \ -} +#define TEST_SUCCESS(type_t, value, string_value, ...) \ + { \ + type_t cache = value; \ + u8string ret = STRINGIFY::stringify(cache, ##__VA_ARGS__); \ + EXPECT_EQ(ret, YYCC_U8(string_value)); \ + } TEST(StringStringify, Common) { TEST_SUCCESS(i8, INT8_C(-61), "-61"); @@ -38,4 +39,4 @@ namespace yycctest::string::stringify { TEST_SUCCESS(u32, UINT32_C(0B1011), "1011", 2); } -} +} // namespace yycctest::string::stringify