diff --git a/src/yycc/constraint/builder.hpp b/src/yycc/constraint/builder.hpp index 8ff6801..4593d41 100644 --- a/src/yycc/constraint/builder.hpp +++ b/src/yycc/constraint/builder.hpp @@ -34,32 +34,61 @@ namespace yycc::constraint::builder { * @brief Get constraint for enum values by enumerating all possible values. * @tparam T An enum type (except bool) of underlying stored value. * @param[in] il An initializer list storing all possible values. + * @param[in] default_index The index of default value in given list. * @return The generated constraint instance which can be directly applied. */ template, int> = 0> - Constraint enum_constraint(const std::initializer_list& il) { - std::set data(il); + Constraint enum_constraint(const std::initializer_list& il, size_t default_index = 0u) { + if (default_index >= il.size()) + throw std::invalid_argument("the default index must be a valid index in given list"); - auto fn_check = [data](const T& val) -> bool { return data.find(val) != data.end(); }; - return Constraint(std::move(fn_check), nullptr); + T default_entry = il.begin()[default_index]; + std::set entries(il); + + // TODO: modify it as `contain` once we finish patch namespace. + auto fn_check = [entries](const T& val) -> bool { + return entries.find(val) != entries.end(); + }; + auto fn_clamp = [entries, default_entry](const T& val) -> T { + if (entries.find(val) != entries.end()) { + return val; + } else { + return default_entry; + } + }; + return Constraint(std::move(fn_check), fn_clamp); } /** * @brief Get constraint for string values by enumerating all possible values. * @param[in] il An initializer list storing all possible values. + * @param[in] default_index The index of default value in given list. * @return The generated constraint instance which can be directly applied. - * @remarks - * Caller must make sure that the string view passed in initializer list is valid until this Constraint life time gone. - * Becasue this generator will not copy your given string view into string. */ - inline Constraint GetStringEnumerationConstraint( - const std::initializer_list& il) { - std::set data(il); + inline Constraint strenum_constraint( + const std::initializer_list& il, size_t default_index = 0u) { + if (default_index >= il.size()) + throw std::invalid_argument("the default index must be a valid index in given list"); - auto fn_check = [data](const NS_YYCC_STRING::u8string& val) -> bool { - return data.find(NS_YYCC_STRING::u8string_view(val)) != data.end(); + NS_YYCC_STRING::u8string default_entry = NS_YYCC_STRING::u8string(il.begin()[default_index]); + std::set entries; + for (const auto& i : il) { + entries.emplace(i); + } + + // TODO: modify it as `contain` once we finish patch namespace. + auto fn_check = [entries](const NS_YYCC_STRING::u8string& val) -> bool { + return entries.find(val) != entries.end(); }; - return Constraint(std::move(fn_check), nullptr); + auto fn_clamp = [entries, default_entry]( + const NS_YYCC_STRING::u8string& val) -> NS_YYCC_STRING::u8string { + if (entries.find(val) != entries.end()) { + return val; + } else { + return default_entry; + } + }; + return Constraint(std::move(fn_check), fn_clamp); } } // namespace yycc::constraint::builder diff --git a/testbench/yycc/constraint/builder.cpp b/testbench/yycc/constraint/builder.cpp index 24cf2d4..059e36c 100644 --- a/testbench/yycc/constraint/builder.cpp +++ b/testbench/yycc/constraint/builder.cpp @@ -1,5 +1,83 @@ #include +#include +#include +#include + +#include + +#define BUILDER ::yycc::constraint::builder namespace yycctest::constraint::builder { -} +#define TEST_SUCCESS(constraint, value) \ + EXPECT_TRUE(constraint.check(value)); \ + EXPECT_EQ(constraint.clamp(value), value); + +#define TEST_FAIL(constraint, value, clamped_value) \ + EXPECT_FALSE(constraint.check(value)); \ + EXPECT_EQ(constraint.clamp(value), clamped_value); + + TEST(ConstraintBuilder, MinMaxConstraint) { + // Integral type + { + auto c = BUILDER::min_max_constraint(5, 61); + ASSERT_TRUE(c.support_check()); + ASSERT_TRUE(c.support_clamp()); + TEST_SUCCESS(c, 5); + TEST_SUCCESS(c, 6); + TEST_SUCCESS(c, 61); + TEST_FAIL(c, -2, 5); + TEST_FAIL(c, 0, 5); + TEST_FAIL(c, 66, 61); + } + // Unisgned integral type + { + auto c = BUILDER::min_max_constraint(5, 61); + ASSERT_TRUE(c.support_check()); + ASSERT_TRUE(c.support_clamp()); + TEST_SUCCESS(c, 5); + TEST_SUCCESS(c, 6); + TEST_SUCCESS(c, 61); + TEST_FAIL(c, 0, 5); + TEST_FAIL(c, 66, 61); + } + // Float point type + { + auto c = BUILDER::min_max_constraint(5.0f, 61.0f); + ASSERT_TRUE(c.support_check()); + ASSERT_TRUE(c.support_clamp()); + TEST_SUCCESS(c, 5.0f); + TEST_SUCCESS(c, 6.0f); + TEST_SUCCESS(c, 61.0f); + TEST_FAIL(c, 0.0f, 5.0f); + TEST_FAIL(c, 66.0f, 61.0f); + } + } + + enum class TestEnum : u8 { Entry1 = 0, Entry2 = 1, Entry3 = 2 }; + + TEST(ConstraintBuilder, EnumConstraint) { + auto c = BUILDER::enum_constraint({TestEnum::Entry1, TestEnum::Entry2, TestEnum::Entry3}, + 1u); + ASSERT_TRUE(c.support_check()); + ASSERT_TRUE(c.support_clamp()); + TEST_SUCCESS(c, TestEnum::Entry1); + TEST_SUCCESS(c, TestEnum::Entry2); + TEST_SUCCESS(c, TestEnum::Entry3); + TEST_FAIL(c, static_cast(UINT8_C(61)), TestEnum::Entry2); + } + + TEST(ConstraintBuilder, StrEnumConstraint) { + auto c = BUILDER::strenum_constraint({YYCC_U8("first-entry"), + YYCC_U8("second-entry"), + YYCC_U8("third-entry")}, + 1u); + ASSERT_TRUE(c.support_check()); + ASSERT_TRUE(c.support_clamp()); + TEST_SUCCESS(c, YYCC_U8("first-entry")); + TEST_SUCCESS(c, YYCC_U8("second-entry")); + TEST_SUCCESS(c, YYCC_U8("third-entry")); + TEST_FAIL(c, YYCC_U8("wtf?"), YYCC_U8("second-entry")); + } + +} // namespace yycctest::constraint::builder