From 3996776dd23a746bbd8a7bfc90b9a9855aa84a41 Mon Sep 17 00:00:00 2001 From: LinZhihao-723 Date: Wed, 13 Aug 2025 21:48:45 -0400 Subject: [PATCH 1/6] Implement struct. --- src/spider/CMakeLists.txt | 2 + .../parser/ast/node_impl/type_impl/Struct.cpp | 72 ++++++++++++++ .../parser/ast/node_impl/type_impl/Struct.hpp | 75 ++++++++++++++ tests/tdl/test-parser-ast.cpp | 98 +++++++++++++------ 4 files changed, 217 insertions(+), 30 deletions(-) create mode 100644 src/spider/tdl/parser/ast/node_impl/type_impl/Struct.cpp create mode 100644 src/spider/tdl/parser/ast/node_impl/type_impl/Struct.hpp diff --git a/src/spider/CMakeLists.txt b/src/spider/CMakeLists.txt index eedd30936..aba322547 100644 --- a/src/spider/CMakeLists.txt +++ b/src/spider/CMakeLists.txt @@ -206,6 +206,7 @@ set(SPIDER_TDL_SHARED_SOURCES tdl/parser/ast/node_impl/type_impl/primitive_impl/Bool.cpp tdl/parser/ast/node_impl/type_impl/primitive_impl/Float.cpp tdl/parser/ast/node_impl/type_impl/primitive_impl/Int.cpp + tdl/parser/ast/node_impl/type_impl/Struct.cpp tdl/parser/ast/utils.cpp CACHE INTERNAL "spider task definition language shared source files" @@ -227,6 +228,7 @@ set(SPIDER_TDL_SHARED_HEADERS tdl/parser/ast/node_impl/type_impl/primitive_impl/Bool.hpp tdl/parser/ast/node_impl/type_impl/primitive_impl/Float.hpp tdl/parser/ast/node_impl/type_impl/primitive_impl/Int.hpp + tdl/parser/ast/node_impl/type_impl/Struct.hpp tdl/parser/ast/utils.hpp CACHE INTERNAL "spider task definition language shared header files" diff --git a/src/spider/tdl/parser/ast/node_impl/type_impl/Struct.cpp b/src/spider/tdl/parser/ast/node_impl/type_impl/Struct.cpp new file mode 100644 index 000000000..e8da6d07c --- /dev/null +++ b/src/spider/tdl/parser/ast/node_impl/type_impl/Struct.cpp @@ -0,0 +1,72 @@ +#include "Struct.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +using spider::tdl::parser::ast::node_impl::type_impl::Struct; +using StructErrorCodeCategory = ystdlib::error_handling::ErrorCategory; + +template <> +auto StructErrorCodeCategory::name() const noexcept -> char const* { + return "spider::tdl::parser::ast::node_impl::type_impl::Struct"; +} + +template <> +auto StructErrorCodeCategory::message(Struct::ErrorCodeEnum error_enum) const -> std::string { + switch (error_enum) { + case Struct::ErrorCodeEnum::StructSpecAlreadySet: + return "The struct spec is already set."; + case Struct::ErrorCodeEnum::StructSpecNameMismatch: + return "The struct spec name does not match the type name."; + default: + return "Unknown error code enum"; + } +} + +namespace spider::tdl::parser::ast::node_impl::type_impl { +auto Struct::create(std::unique_ptr name) + -> ystdlib::error_handling::Result> { + YSTDLIB_ERROR_HANDLING_TRYV(validate_child_node_type(name.get())); + + auto struct_node{std::make_unique(Struct{})}; + YSTDLIB_ERROR_HANDLING_TRYV(struct_node->add_child(std::move(name))); + return struct_node; +} + +auto Struct::serialize_to_str(size_t indentation_level) const + -> ystdlib::error_handling::Result { + return fmt::format( + "{}[Type[Struct]]:\n{}Name:\n{}", + create_indentation(indentation_level), + create_indentation(indentation_level + 1), + YSTDLIB_ERROR_HANDLING_TRYX( + // The factory function ensures that the first child is of type `Identifier`. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + get_child_unsafe(0)->serialize_to_str(indentation_level + 2) + ) + ); +} + +auto Struct::set_spec(std::shared_ptr spec) -> ystdlib::error_handling::Result { + if (nullptr != m_spec) { + return Struct::ErrorCode{Struct::ErrorCodeEnum::StructSpecAlreadySet}; + } + + if (get_name() != spec->get_name()) { + return Struct::ErrorCode{Struct::ErrorCodeEnum::StructSpecNameMismatch}; + } + + m_spec = std::move(spec); + return ystdlib::error_handling::success(); +} +} // namespace spider::tdl::parser::ast::node_impl::type_impl diff --git a/src/spider/tdl/parser/ast/node_impl/type_impl/Struct.hpp b/src/spider/tdl/parser/ast/node_impl/type_impl/Struct.hpp new file mode 100644 index 000000000..b65d80476 --- /dev/null +++ b/src/spider/tdl/parser/ast/node_impl/type_impl/Struct.hpp @@ -0,0 +1,75 @@ +#ifndef SPIDER_TDL_PARSER_AST_NODE_IMPL_TYPE_IMPL_STRUCT_HPP +#define SPIDER_TDL_PARSER_AST_NODE_IMPL_TYPE_IMPL_STRUCT_HPP + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +namespace spider::tdl::parser::ast::node_impl::type_impl { +class Struct : public Type { +public: + // Types + enum class ErrorCodeEnum : uint8_t { + StructSpecAlreadySet = 1, + StructSpecNameMismatch, + }; + + using ErrorCode = ystdlib::error_handling::ErrorCode; + + // Factory function + /** + * @param name + * @return A result containing a unique pointer to a new `Struct` instance with the given name + * on success, or an error code indicating the failure: + * - Forwards `validate_child_node_type`'s return values. + */ + [[nodiscard]] static auto create(std::unique_ptr name) + -> ystdlib::error_handling::Result>; + + // Methods implementing `Node` + [[nodiscard]] auto serialize_to_str(size_t indentation_level) const + -> ystdlib::error_handling::Result override; + + // Methods + [[nodiscard]] auto get_name() const -> std::string_view { + // The factory function ensures that the first child is of type `Identifier`. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + return static_cast(get_child_unsafe(0))->get_name(); + } + + /** + * Sets the specification for this struct. + * @param spec + * @return A void result on success, or an error code indicating the failure: + * - ErrorCodeEnum::StructSpecNameMismatch if `spec`'s name does not match the underlying name. + * - ErrorCodeEnum::StructSpecAlreadySet if the specification has already been set. + */ + [[nodiscard]] auto set_spec(std::shared_ptr spec) + -> ystdlib::error_handling::Result; + + [[nodiscard]] auto get_spec() const -> StructSpec const* { return m_spec.get(); } + +private: + // Constructor + Struct() = default; + + // Variables + std::shared_ptr m_spec; +}; +} // namespace spider::tdl::parser::ast::node_impl::type_impl + +YSTDLIB_ERROR_HANDLING_MARK_AS_ERROR_CODE_ENUM( + spider::tdl::parser::ast::node_impl::type_impl::Struct::ErrorCodeEnum +); + +#endif // SPIDER_TDL_PARSER_AST_NODE_IMPL_TYPE_IMPL_STRUCT_HPP diff --git a/tests/tdl/test-parser-ast.cpp b/tests/tdl/test-parser-ast.cpp index f9b6562e6..1df77ad2c 100644 --- a/tests/tdl/test-parser-ast.cpp +++ b/tests/tdl/test-parser-ast.cpp @@ -22,6 +22,7 @@ #include #include #include +#include namespace { TEST_CASE("test-ast-node", "[tdl][ast][Node]") { @@ -37,6 +38,7 @@ TEST_CASE("test-ast-node", "[tdl][ast][Node]") { using spider::tdl::parser::ast::node_impl::type_impl::primitive_impl::Bool; using spider::tdl::parser::ast::node_impl::type_impl::primitive_impl::Float; using spider::tdl::parser::ast::node_impl::type_impl::primitive_impl::Int; + using spider::tdl::parser::ast::node_impl::type_impl::Struct; using ystdlib::error_handling::Result; SECTION("Identifier") { @@ -312,7 +314,7 @@ TEST_CASE("test-ast-node", "[tdl][ast][Node]") { fields.emplace_back(std::move(float_field_result.value())); fields.emplace_back(std::move(map_field_result.value())); - SECTION("Basic") { + SECTION("With Struct") { auto struct_spec_result{StructSpec::create( Identifier::create(std::string{cTestStructName}), std::move(fields) @@ -326,35 +328,71 @@ TEST_CASE("test-ast-node", "[tdl][ast][Node]") { REQUIRE(struct_spec_node->get_num_children() == 4); REQUIRE(struct_spec_node->get_name() == cTestStructName); - constexpr std::string_view cExpectedSerializedResult{ - "[StructSpec]:\n" - " Name:TestStruct\n" - " Fields[0]:\n" - " [NamedVar]:\n" - " Id:\n" - " [Identifier]:m_int\n" - " Type:\n" - " [Type[Primitive[Int]]]:int64\n" - " Fields[1]:\n" - " [NamedVar]:\n" - " Id:\n" - " [Identifier]:m_float\n" - " Type:\n" - " [Type[Primitive[Float]]]:double\n" - " Fields[2]:\n" - " [NamedVar]:\n" - " Id:\n" - " [Identifier]:m_map\n" - " Type:\n" - " [Type[Container[Map]]]:\n" - " KeyType:\n" - " [Type[Primitive[Int]]]:int64\n" - " ValueType:\n" - " [Type[Primitive[Float]]]:double" - }; - auto const serialized_result{struct_spec_node->serialize_to_str(0)}; - REQUIRE_FALSE(serialized_result.has_error()); - REQUIRE(serialized_result.value() == cExpectedSerializedResult); + SECTION("StructSpec serialization") { + constexpr std::string_view cExpectedSerializedResult{ + "[StructSpec]:\n" + " Name:TestStruct\n" + " Fields[0]:\n" + " [NamedVar]:\n" + " Id:\n" + " [Identifier]:m_int\n" + " Type:\n" + " [Type[Primitive[Int]]]:int64\n" + " Fields[1]:\n" + " [NamedVar]:\n" + " Id:\n" + " [Identifier]:m_float\n" + " Type:\n" + " [Type[Primitive[Float]]]:double\n" + " Fields[2]:\n" + " [NamedVar]:\n" + " Id:\n" + " [Identifier]:m_map\n" + " Type:\n" + " [Type[Container[Map]]]:\n" + " KeyType:\n" + " [Type[Primitive[Int]]]:int64\n" + " ValueType:\n" + " [Type[Primitive[Float]]]:double" + }; + auto const serialized_result{struct_spec_node->serialize_to_str(0)}; + REQUIRE_FALSE(serialized_result.has_error()); + REQUIRE(serialized_result.value() == cExpectedSerializedResult); + } + + auto struct_result{Struct::create(Identifier::create(std::string{cTestStructName}))}; + REQUIRE_FALSE(struct_result.has_error()); + auto* struct_node{dynamic_cast(struct_result.value().get())}; + REQUIRE(nullptr != struct_node); + + REQUIRE(struct_node->get_num_children() == 1); + REQUIRE(cTestStructName == struct_node->get_name()); + + REQUIRE_FALSE(struct_node->set_spec(struct_spec_result.value()).has_error()); + auto const duplicated_set_spec{struct_node->set_spec(struct_spec_result.value())}; + REQUIRE(duplicated_set_spec.has_error()); + REQUIRE(duplicated_set_spec.error() + == Struct::ErrorCode{Struct::ErrorCodeEnum::StructSpecAlreadySet}); + + SECTION("Struct serialization") { + constexpr std::string_view cExpectedSerializedResult{"[Type[Struct]]:\n" + " Name:\n" + " [Identifier]:TestStruct"}; + auto const serialized_result{struct_node->serialize_to_str(0)}; + REQUIRE_FALSE(serialized_result.has_error()); + REQUIRE(serialized_result.value() == cExpectedSerializedResult); + } + + SECTION("Set spec to a wrong Struct") { + auto wrong_struct_result{Struct::create(Identifier::create("WrongStruct"))}; + REQUIRE_FALSE(wrong_struct_result.has_error()); + auto* wrong_struct_node{dynamic_cast(wrong_struct_result.value().get())}; + REQUIRE(nullptr != wrong_struct_node); + auto const set_spec_result{wrong_struct_node->set_spec(struct_spec_result.value())}; + REQUIRE(set_spec_result.has_error()); + REQUIRE(set_spec_result.error() + == Struct::ErrorCode{Struct::ErrorCodeEnum::StructSpecNameMismatch}); + } } SECTION("Fields with duplicated name") { From 930d32c8e206cf7a61aea39466b36709669c26be Mon Sep 17 00:00:00 2001 From: LinZhihao-723 Date: Thu, 14 Aug 2025 08:53:46 -0400 Subject: [PATCH 2/6] Fix unit tests according to code review comments. --- tests/tdl/test-parser-ast.cpp | 154 +++++++++++++++++++--------------- 1 file changed, 88 insertions(+), 66 deletions(-) diff --git a/tests/tdl/test-parser-ast.cpp b/tests/tdl/test-parser-ast.cpp index 1df77ad2c..f830995f2 100644 --- a/tests/tdl/test-parser-ast.cpp +++ b/tests/tdl/test-parser-ast.cpp @@ -314,7 +314,7 @@ TEST_CASE("test-ast-node", "[tdl][ast][Node]") { fields.emplace_back(std::move(float_field_result.value())); fields.emplace_back(std::move(map_field_result.value())); - SECTION("With Struct") { + SECTION("Basic") { auto struct_spec_result{StructSpec::create( Identifier::create(std::string{cTestStructName}), std::move(fields) @@ -328,71 +328,35 @@ TEST_CASE("test-ast-node", "[tdl][ast][Node]") { REQUIRE(struct_spec_node->get_num_children() == 4); REQUIRE(struct_spec_node->get_name() == cTestStructName); - SECTION("StructSpec serialization") { - constexpr std::string_view cExpectedSerializedResult{ - "[StructSpec]:\n" - " Name:TestStruct\n" - " Fields[0]:\n" - " [NamedVar]:\n" - " Id:\n" - " [Identifier]:m_int\n" - " Type:\n" - " [Type[Primitive[Int]]]:int64\n" - " Fields[1]:\n" - " [NamedVar]:\n" - " Id:\n" - " [Identifier]:m_float\n" - " Type:\n" - " [Type[Primitive[Float]]]:double\n" - " Fields[2]:\n" - " [NamedVar]:\n" - " Id:\n" - " [Identifier]:m_map\n" - " Type:\n" - " [Type[Container[Map]]]:\n" - " KeyType:\n" - " [Type[Primitive[Int]]]:int64\n" - " ValueType:\n" - " [Type[Primitive[Float]]]:double" - }; - auto const serialized_result{struct_spec_node->serialize_to_str(0)}; - REQUIRE_FALSE(serialized_result.has_error()); - REQUIRE(serialized_result.value() == cExpectedSerializedResult); - } - - auto struct_result{Struct::create(Identifier::create(std::string{cTestStructName}))}; - REQUIRE_FALSE(struct_result.has_error()); - auto* struct_node{dynamic_cast(struct_result.value().get())}; - REQUIRE(nullptr != struct_node); - - REQUIRE(struct_node->get_num_children() == 1); - REQUIRE(cTestStructName == struct_node->get_name()); - - REQUIRE_FALSE(struct_node->set_spec(struct_spec_result.value()).has_error()); - auto const duplicated_set_spec{struct_node->set_spec(struct_spec_result.value())}; - REQUIRE(duplicated_set_spec.has_error()); - REQUIRE(duplicated_set_spec.error() - == Struct::ErrorCode{Struct::ErrorCodeEnum::StructSpecAlreadySet}); - - SECTION("Struct serialization") { - constexpr std::string_view cExpectedSerializedResult{"[Type[Struct]]:\n" - " Name:\n" - " [Identifier]:TestStruct"}; - auto const serialized_result{struct_node->serialize_to_str(0)}; - REQUIRE_FALSE(serialized_result.has_error()); - REQUIRE(serialized_result.value() == cExpectedSerializedResult); - } - - SECTION("Set spec to a wrong Struct") { - auto wrong_struct_result{Struct::create(Identifier::create("WrongStruct"))}; - REQUIRE_FALSE(wrong_struct_result.has_error()); - auto* wrong_struct_node{dynamic_cast(wrong_struct_result.value().get())}; - REQUIRE(nullptr != wrong_struct_node); - auto const set_spec_result{wrong_struct_node->set_spec(struct_spec_result.value())}; - REQUIRE(set_spec_result.has_error()); - REQUIRE(set_spec_result.error() - == Struct::ErrorCode{Struct::ErrorCodeEnum::StructSpecNameMismatch}); - } + constexpr std::string_view cExpectedSerializedResult{ + "[StructSpec]:\n" + " Name:TestStruct\n" + " Fields[0]:\n" + " [NamedVar]:\n" + " Id:\n" + " [Identifier]:m_int\n" + " Type:\n" + " [Type[Primitive[Int]]]:int64\n" + " Fields[1]:\n" + " [NamedVar]:\n" + " Id:\n" + " [Identifier]:m_float\n" + " Type:\n" + " [Type[Primitive[Float]]]:double\n" + " Fields[2]:\n" + " [NamedVar]:\n" + " Id:\n" + " [Identifier]:m_map\n" + " Type:\n" + " [Type[Container[Map]]]:\n" + " KeyType:\n" + " [Type[Primitive[Int]]]:int64\n" + " ValueType:\n" + " [Type[Primitive[Float]]]:double" + }; + auto const serialized_result{struct_spec_node->serialize_to_str(0)}; + REQUIRE_FALSE(serialized_result.has_error()); + REQUIRE(serialized_result.value() == cExpectedSerializedResult); } SECTION("Fields with duplicated name") { @@ -422,6 +386,64 @@ TEST_CASE("test-ast-node", "[tdl][ast][Node]") { == StructSpec::ErrorCode{StructSpec::ErrorCodeEnum::EmptyStruct}); } } + + SECTION("Struct") { + constexpr std::string_view cTestStructName{"TestStruct"}; + + // Create a `StructSpec` + auto int_field_result{ + NamedVar::create(Identifier::create("m_int"), Int::create(IntSpec::Int64)) + }; + REQUIRE_FALSE(int_field_result.has_error()); + std::vector> fields; + fields.emplace_back(std::move(int_field_result.value())); + + auto struct_spec_result{StructSpec::create( + Identifier::create(std::string{cTestStructName}), + std::move(fields) + )}; + REQUIRE_FALSE(struct_spec_result.has_error()); + REQUIRE(nullptr != dynamic_cast(struct_spec_result.value().get())); + + SECTION("Struct with StructSpec") { + auto struct_result{Struct::create(Identifier::create(std::string{cTestStructName}))}; + REQUIRE_FALSE(struct_result.has_error()); + auto* struct_node{dynamic_cast(struct_result.value().get())}; + REQUIRE(nullptr != struct_node); + + REQUIRE(struct_node->get_num_children() == 1); + REQUIRE(cTestStructName == struct_node->get_name()); + REQUIRE(nullptr == struct_node->get_spec()); + + constexpr std::string_view cExpectedSerializedResult{"[Type[Struct]]:\n" + " Name:\n" + " [Identifier]:TestStruct"}; + auto const serialized_result{struct_node->serialize_to_str(0)}; + REQUIRE_FALSE(serialized_result.has_error()); + REQUIRE(serialized_result.value() == cExpectedSerializedResult); + + // Set the `StructSpec` to the `Struct` + REQUIRE_FALSE(struct_node->set_spec(struct_spec_result.value()).has_error()); + REQUIRE(nullptr != struct_node->get_spec()); + + // Ensure `StructSpec` cannot be set again + auto const duplicated_set_spec{struct_node->set_spec(struct_spec_result.value())}; + REQUIRE(duplicated_set_spec.has_error()); + REQUIRE(duplicated_set_spec.error() + == Struct::ErrorCode{Struct::ErrorCodeEnum::StructSpecAlreadySet}); + } + + SECTION("Set spec to a wrong Struct") { + auto struct_result{Struct::create(Identifier::create("WrongStruct"))}; + REQUIRE_FALSE(struct_result.has_error()); + auto* struct_node{dynamic_cast(struct_result.value().get())}; + REQUIRE(nullptr != struct_node); + auto const set_spec_result{struct_node->set_spec(struct_spec_result.value())}; + REQUIRE(set_spec_result.has_error()); + REQUIRE(set_spec_result.error() + == Struct::ErrorCode{Struct::ErrorCodeEnum::StructSpecNameMismatch}); + } + } } } // namespace From 3986457a61359e53557a291fc66001333b9ce733 Mon Sep 17 00:00:00 2001 From: LinZhihao-723 Date: Thu, 14 Aug 2025 09:01:00 -0400 Subject: [PATCH 3/6] Add nullptr check for struct spec. --- src/spider/tdl/parser/ast/node_impl/type_impl/Struct.cpp | 7 +++++++ src/spider/tdl/parser/ast/node_impl/type_impl/Struct.hpp | 4 +++- tests/tdl/test-parser-ast.cpp | 8 +++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/spider/tdl/parser/ast/node_impl/type_impl/Struct.cpp b/src/spider/tdl/parser/ast/node_impl/type_impl/Struct.cpp index e8da6d07c..ad6b4fa27 100644 --- a/src/spider/tdl/parser/ast/node_impl/type_impl/Struct.cpp +++ b/src/spider/tdl/parser/ast/node_impl/type_impl/Struct.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -24,6 +25,8 @@ auto StructErrorCodeCategory::name() const noexcept -> char const* { template <> auto StructErrorCodeCategory::message(Struct::ErrorCodeEnum error_enum) const -> std::string { switch (error_enum) { + case Struct::ErrorCodeEnum::NullStructSpec: + return "The struct spec is NULL."; case Struct::ErrorCodeEnum::StructSpecAlreadySet: return "The struct spec is already set."; case Struct::ErrorCodeEnum::StructSpecNameMismatch: @@ -62,6 +65,10 @@ auto Struct::set_spec(std::shared_ptr spec) -> ystdlib::error_handli return Struct::ErrorCode{Struct::ErrorCodeEnum::StructSpecAlreadySet}; } + if (nullptr == spec) { + return Struct::ErrorCode{Struct::ErrorCodeEnum::NullStructSpec}; + } + if (get_name() != spec->get_name()) { return Struct::ErrorCode{Struct::ErrorCodeEnum::StructSpecNameMismatch}; } diff --git a/src/spider/tdl/parser/ast/node_impl/type_impl/Struct.hpp b/src/spider/tdl/parser/ast/node_impl/type_impl/Struct.hpp index b65d80476..811d6fa05 100644 --- a/src/spider/tdl/parser/ast/node_impl/type_impl/Struct.hpp +++ b/src/spider/tdl/parser/ast/node_impl/type_impl/Struct.hpp @@ -20,7 +20,8 @@ class Struct : public Type { public: // Types enum class ErrorCodeEnum : uint8_t { - StructSpecAlreadySet = 1, + NullStructSpec = 1, + StructSpecAlreadySet, StructSpecNameMismatch, }; @@ -51,6 +52,7 @@ class Struct : public Type { * Sets the specification for this struct. * @param spec * @return A void result on success, or an error code indicating the failure: + * - ErrorCodeEnum::NullStructSpec if `spec` is a nullptr. * - ErrorCodeEnum::StructSpecNameMismatch if `spec`'s name does not match the underlying name. * - ErrorCodeEnum::StructSpecAlreadySet if the specification has already been set. */ diff --git a/tests/tdl/test-parser-ast.cpp b/tests/tdl/test-parser-ast.cpp index f830995f2..44101192b 100644 --- a/tests/tdl/test-parser-ast.cpp +++ b/tests/tdl/test-parser-ast.cpp @@ -422,11 +422,17 @@ TEST_CASE("test-ast-node", "[tdl][ast][Node]") { REQUIRE_FALSE(serialized_result.has_error()); REQUIRE(serialized_result.value() == cExpectedSerializedResult); + // Ensure nullptr can't be set as `StructSpec` + auto const null_set_spec{struct_node->set_spec({})}; + REQUIRE(null_set_spec.has_error()); + REQUIRE(null_set_spec.error() + == Struct::ErrorCode{Struct::ErrorCodeEnum::NullStructSpec}); + // Set the `StructSpec` to the `Struct` REQUIRE_FALSE(struct_node->set_spec(struct_spec_result.value()).has_error()); REQUIRE(nullptr != struct_node->get_spec()); - // Ensure `StructSpec` cannot be set again + // Ensure `StructSpec` can't be set again auto const duplicated_set_spec{struct_node->set_spec(struct_spec_result.value())}; REQUIRE(duplicated_set_spec.has_error()); REQUIRE(duplicated_set_spec.error() From ecec2e8425026b8c04c6464fe28d8aa121007af2 Mon Sep 17 00:00:00 2001 From: LinZhihao-723 Date: Thu, 14 Aug 2025 11:05:58 -0400 Subject: [PATCH 4/6] Apply code rait's comment. --- tests/tdl/test-parser-ast.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/tdl/test-parser-ast.cpp b/tests/tdl/test-parser-ast.cpp index 44101192b..90647213e 100644 --- a/tests/tdl/test-parser-ast.cpp +++ b/tests/tdl/test-parser-ast.cpp @@ -427,6 +427,7 @@ TEST_CASE("test-ast-node", "[tdl][ast][Node]") { REQUIRE(null_set_spec.has_error()); REQUIRE(null_set_spec.error() == Struct::ErrorCode{Struct::ErrorCodeEnum::NullStructSpec}); + REQUIRE(nullptr == struct_node->get_spec()); // Set the `StructSpec` to the `Struct` REQUIRE_FALSE(struct_node->set_spec(struct_spec_result.value()).has_error()); From 3f6abbeb304ef27df2b4c710f1ad6897ac6f5923 Mon Sep 17 00:00:00 2001 From: LinZhihao-723 Date: Thu, 14 Aug 2025 12:43:46 -0400 Subject: [PATCH 5/6] Implement fn --- src/spider/CMakeLists.txt | 2 + .../tdl/parser/ast/node_impl/Function.cpp | 123 ++++++++++ .../tdl/parser/ast/node_impl/Function.hpp | 120 ++++++++++ tests/tdl/test-parser-ast.cpp | 217 ++++++++++++++++++ 4 files changed, 462 insertions(+) create mode 100644 src/spider/tdl/parser/ast/node_impl/Function.cpp create mode 100644 src/spider/tdl/parser/ast/node_impl/Function.hpp diff --git a/src/spider/CMakeLists.txt b/src/spider/CMakeLists.txt index aba322547..e0c348be4 100644 --- a/src/spider/CMakeLists.txt +++ b/src/spider/CMakeLists.txt @@ -197,6 +197,7 @@ add_library(spider::spider ALIAS spider_client) set(SPIDER_TDL_SHARED_SOURCES tdl/parser/ast/Node.cpp + tdl/parser/ast/node_impl/Function.cpp tdl/parser/ast/node_impl/Identifier.cpp tdl/parser/ast/node_impl/NamedVar.cpp tdl/parser/ast/node_impl/StructSpec.cpp @@ -216,6 +217,7 @@ set(SPIDER_TDL_SHARED_HEADERS tdl/parser/ast/Node.hpp tdl/parser/ast/FloatSpec.hpp tdl/parser/ast/IntSpec.hpp + tdl/parser/ast/node_impl/Function.hpp tdl/parser/ast/node_impl/Identifier.hpp tdl/parser/ast/node_impl/NamedVar.hpp tdl/parser/ast/node_impl/StructSpec.hpp diff --git a/src/spider/tdl/parser/ast/node_impl/Function.cpp b/src/spider/tdl/parser/ast/node_impl/Function.cpp new file mode 100644 index 000000000..a2987cf77 --- /dev/null +++ b/src/spider/tdl/parser/ast/node_impl/Function.cpp @@ -0,0 +1,123 @@ +#include "Function.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using spider::tdl::parser::ast::node_impl::Function; +using FunctionErrorCodeCategory = ystdlib::error_handling::ErrorCategory; + +template <> +auto FunctionErrorCodeCategory::name() const noexcept -> char const* { + return "spider::tdl::parser::ast::node_impl::Function"; +} + +template <> +auto FunctionErrorCodeCategory::message(Function::ErrorCodeEnum error_enum) const -> std::string { + switch (error_enum) { + case Function::ErrorCodeEnum::DuplicatedParamName: + return "The parameters have duplicated names."; + default: + return "Unknown error code enum"; + } +} + +namespace spider::tdl::parser::ast::node_impl { +auto Function::create( + std::unique_ptr name, + std::unique_ptr return_type, + std::vector> params +) -> ystdlib::error_handling::Result> { + YSTDLIB_ERROR_HANDLING_TRYV(validate_child_node_type(name.get())); + + bool const has_return{nullptr != return_type}; + if (has_return) { + YSTDLIB_ERROR_HANDLING_TRYV(validate_child_node_type(return_type.get())); + } + + absl::flat_hash_set param_names; + for (auto const& param : params) { + YSTDLIB_ERROR_HANDLING_TRYV(validate_child_node_type(param.get())); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + auto const param_name{static_cast(*param).get_id()->get_name()}; + if (param_names.contains(param_name)) { + return ErrorCode{ErrorCodeEnum::DuplicatedParamName}; + } + param_names.emplace(param_name); + } + + auto function{std::make_unique(Function{has_return})}; + YSTDLIB_ERROR_HANDLING_TRYV(function->add_child(std::move(name))); + if (has_return) { + YSTDLIB_ERROR_HANDLING_TRYV(function->add_child(std::move(return_type))); + } + for (auto& param : params) { + YSTDLIB_ERROR_HANDLING_TRYV(function->add_child(std::move(param))); + } + return function; +} + +auto Function::serialize_to_str(size_t indentation_level) const + -> ystdlib::error_handling::Result { + std::vector serialized_params; + YSTDLIB_ERROR_HANDLING_TRYV( + visit_params([&](NamedVar const& param) -> ystdlib::error_handling::Result { + serialized_params.emplace_back( + fmt::format( + "{}Params[{}]:\n{}", + create_indentation(indentation_level + 1), + serialized_params.size(), + YSTDLIB_ERROR_HANDLING_TRYX( + param.serialize_to_str(indentation_level + 2) + ) + ) + ); + return ystdlib::error_handling::success(); + }) + ); + + std::string const serialized_return_type{ + has_return() ? YSTDLIB_ERROR_HANDLING_TRYX( + get_return_type()->serialize_to_str(indentation_level + 2) + ) + : fmt::format("{}void", create_indentation(indentation_level + 2)) + }; + + if (false == serialized_params.empty()) { + return fmt::format( + "{}[Function]:\n{}Name:{}\n{}Return:\n{}\n{}", + create_indentation(indentation_level), + create_indentation(indentation_level + 1), + get_name(), + create_indentation(indentation_level + 1), + serialized_return_type, + fmt::join(serialized_params, "\n") + ); + } + + return fmt::format( + "{}[Function]:\n{}Name:{}\n{}Return:\n{}\n{}No Params", + create_indentation(indentation_level), + create_indentation(indentation_level + 1), + get_name(), + create_indentation(indentation_level + 1), + serialized_return_type, + create_indentation(indentation_level + 1) + ); +} +} // namespace spider::tdl::parser::ast::node_impl diff --git a/src/spider/tdl/parser/ast/node_impl/Function.hpp b/src/spider/tdl/parser/ast/node_impl/Function.hpp new file mode 100644 index 000000000..a6f8e23b0 --- /dev/null +++ b/src/spider/tdl/parser/ast/node_impl/Function.hpp @@ -0,0 +1,120 @@ +#ifndef SPIDER_TDL_PARSER_AST_NODE_IMPL_FUNCTION_HPP +#define SPIDER_TDL_PARSER_AST_NODE_IMPL_FUNCTION_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +namespace spider::tdl::parser::ast::node_impl { +class Function : public Node { +public: + // Types + enum class ErrorCodeEnum : uint8_t { + DuplicatedParamName = 1, + }; + + using ErrorCode = ystdlib::error_handling::ErrorCode; + + // Factory function + /** + * @param name + * @param return_type + * @param params + * @return A result containing a unique pointer to a new `Function` instance with the given + * name, return type, and parameters on success, or an error code indicating the failure: + * - ErrorCodeEnum::DuplicatedParamName if `params` contains duplicated parameter names. + * - Forwards `validate_child_node_type`'s return values. + */ + [[nodiscard]] static auto create( + std::unique_ptr name, + std::unique_ptr return_type, + std::vector> params + ) -> ystdlib::error_handling::Result>; + + // Methods implementing `Node` + [[nodiscard]] auto serialize_to_str(size_t indentation_level) const + -> ystdlib::error_handling::Result override; + + // Methods + [[nodiscard]] auto has_return() const noexcept -> bool { return m_has_return; } + + [[nodiscard]] auto get_name() const noexcept -> std::string_view { + // The factory function ensures that the first child is of type `Identifier`. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + return static_cast(get_child_unsafe(0))->get_name(); + } + + /** + * @return The return type of the function, or nullptr if the function doesn't have a return. + */ + [[nodiscard]] auto get_return_type() const noexcept -> Type const* { + if (false == m_has_return) { + return nullptr; + } + // The factory function ensures that the second child is of type `Type`, if not nullptr. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + return static_cast(get_child_unsafe(1)); + } + + [[nodiscard]] auto get_num_params() const noexcept -> size_t { + return get_num_children() - get_num_non_param_children(); + } + + /** + * Visits parameters. + * @tparam ParamVisitor + * @param visitor + * @return A void result on success, or an error code indicating the failure: + * - Forwards `visitor`'s return values. + */ + template + requires(std::is_invocable_r_v< + ystdlib::error_handling::Result, + ParamVisitor, + NamedVar const&>) + [[nodiscard]] auto visit_params(ParamVisitor visitor) const + -> ystdlib::error_handling::Result { + for (size_t child_idx{get_num_non_param_children()}; child_idx < get_num_children(); + ++child_idx) + { + // The factory function ensures that all the child nodes are `NamedVar` except the first + // one. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + YSTDLIB_ERROR_HANDLING_TRYV( + visitor(static_cast(*get_child_unsafe(child_idx))) + ); + } + return ystdlib::error_handling::success(); + } + +private: + // Constructor + explicit Function(bool has_return) : m_has_return{has_return} {} + + // Methods + [[nodiscard]] auto get_num_non_param_children() const noexcept -> size_t { + return m_has_return ? 2 : 1; + } + + // Variables + bool m_has_return; +}; +} // namespace spider::tdl::parser::ast::node_impl + +YSTDLIB_ERROR_HANDLING_MARK_AS_ERROR_CODE_ENUM( + spider::tdl::parser::ast::node_impl::Function::ErrorCodeEnum +); + +#endif // SPIDER_TDL_PARSER_AST_NODE_IMPL_FUNCTION_HPP diff --git a/tests/tdl/test-parser-ast.cpp b/tests/tdl/test-parser-ast.cpp index 90647213e..608ca6eaa 100644 --- a/tests/tdl/test-parser-ast.cpp +++ b/tests/tdl/test-parser-ast.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -25,10 +26,47 @@ #include namespace { +/** + * @param name + * @return A struct AST node with the given name. + */ +[[nodiscard]] auto create_struct_node(std::string_view name) + -> std::unique_ptr; + +/** + * @param name + * @param type + * @return A named-var AST node with the given name and type. + */ +[[nodiscard]] auto +create_named_var(std::string_view name, std::unique_ptr type) + -> std::unique_ptr; + +auto create_struct_node(std::string_view name) -> std::unique_ptr { + using spider::tdl::parser::ast::node_impl::Identifier; + using spider::tdl::parser::ast::node_impl::type_impl::Struct; + + auto struct_node_result{Struct::create(Identifier::create(std::string{name}))}; + REQUIRE_FALSE(struct_node_result.has_error()); + return std::move(struct_node_result.value()); +} + +auto create_named_var(std::string_view name, std::unique_ptr type) + -> std::unique_ptr { + using spider::tdl::parser::ast::node_impl::Identifier; + using spider::tdl::parser::ast::node_impl::NamedVar; + using spider::tdl::parser::ast::node_impl::type_impl::Struct; + + auto named_var_result{NamedVar::create(Identifier::create(std::string{name}), std::move(type))}; + REQUIRE_FALSE(named_var_result.has_error()); + return std::move(named_var_result.value()); +} + TEST_CASE("test-ast-node", "[tdl][ast][Node]") { using spider::tdl::parser::ast::FloatSpec; using spider::tdl::parser::ast::IntSpec; using spider::tdl::parser::ast::Node; + using spider::tdl::parser::ast::node_impl::Function; using spider::tdl::parser::ast::node_impl::Identifier; using spider::tdl::parser::ast::node_impl::NamedVar; using spider::tdl::parser::ast::node_impl::StructSpec; @@ -451,6 +489,185 @@ TEST_CASE("test-ast-node", "[tdl][ast][Node]") { == Struct::ErrorCode{Struct::ErrorCodeEnum::StructSpecNameMismatch}); } } + + SECTION("Function") { + constexpr std::string_view cTestFuncName{"test_function"}; + constexpr std::string_view cTestStructName{"TestStruct"}; + + auto function_name{Identifier::create(std::string{cTestFuncName})}; + + std::vector> tuple_elements; + tuple_elements.emplace_back(Int::create(IntSpec::Int64)); + tuple_elements.emplace_back(create_struct_node(cTestStructName)); + tuple_elements.emplace_back(Bool::create()); + + auto return_tuple_result{Tuple::create(std::move(tuple_elements))}; + REQUIRE_FALSE(return_tuple_result.has_error()); + + std::vector> parameters; + parameters.emplace_back(create_named_var("param_0", Int::create(IntSpec::Int64))); + parameters.emplace_back(create_named_var("param_1", create_struct_node(cTestStructName))); + + SECTION("Basic") { + auto func_result{Function::create( + std::move(function_name), + std::move(return_tuple_result.value()), + std::move(parameters) + )}; + REQUIRE_FALSE(func_result.has_error()); + auto const* func_node{dynamic_cast(func_result.value().get())}; + REQUIRE(nullptr != func_node); + + REQUIRE(func_node->get_num_children() == 4); + REQUIRE(func_node->get_num_params() == 2); + REQUIRE(func_node->get_name() == cTestFuncName); + REQUIRE(nullptr != func_node->get_return_type()); + + constexpr std::string_view cExpectedSerializedResult{ + "[Function]:\n" + " Name:test_function\n" + " Return:\n" + " [Type[Container[Tuple]]]:\n" + " Element[0]:\n" + " [Type[Primitive[Int]]]:int64\n" + " Element[1]:\n" + " [Type[Struct]]:\n" + " Name:\n" + " [Identifier]:TestStruct\n" + " Element[2]:\n" + " [Type[Primitive[Bool]]]\n" + " Params[0]:\n" + " [NamedVar]:\n" + " Id:\n" + " [Identifier]:param_0\n" + " Type:\n" + " [Type[Primitive[Int]]]:int64\n" + " Params[1]:\n" + " [NamedVar]:\n" + " Id:\n" + " [Identifier]:param_1\n" + " Type:\n" + " [Type[Struct]]:\n" + " Name:\n" + " [Identifier]:TestStruct" + }; + auto const serialized_result{func_node->serialize_to_str(0)}; + REQUIRE_FALSE(serialized_result.has_error()); + REQUIRE(serialized_result.value() == cExpectedSerializedResult); + } + + SECTION("No return type") { + // The execution model of `SECTION` ensures objects are not moved when this section is + // executed, so use move below is safe. + // NOLINTNEXTLINE(bugprone-use-after-move) + auto func_result{Function::create(std::move(function_name), {}, std::move(parameters))}; + REQUIRE_FALSE(func_result.has_error()); + auto const* func_node{dynamic_cast(func_result.value().get())}; + REQUIRE(nullptr != func_node); + + REQUIRE(func_node->get_num_children() == 3); + REQUIRE(func_node->get_num_params() == 2); + REQUIRE(func_node->get_name() == cTestFuncName); + REQUIRE(nullptr == func_node->get_return_type()); + + constexpr std::string_view cExpectedSerializedResult{ + "[Function]:\n" + " Name:test_function\n" + " Return:\n" + " void\n" + " Params[0]:\n" + " [NamedVar]:\n" + " Id:\n" + " [Identifier]:param_0\n" + " Type:\n" + " [Type[Primitive[Int]]]:int64\n" + " Params[1]:\n" + " [NamedVar]:\n" + " Id:\n" + " [Identifier]:param_1\n" + " Type:\n" + " [Type[Struct]]:\n" + " Name:\n" + " [Identifier]:TestStruct" + }; + auto const serialized_result{func_node->serialize_to_str(0)}; + REQUIRE_FALSE(serialized_result.has_error()); + REQUIRE(serialized_result.value() == cExpectedSerializedResult); + } + + SECTION("Empty param list") { + // The execution model of `SECTION` ensures objects are not moved when this section is + // executed, so use move below is safe. + // NOLINTNEXTLINE(bugprone-use-after-move) + auto func_result{Function::create( + std::move(function_name), + std::move(return_tuple_result.value()), + {} + )}; + REQUIRE_FALSE(func_result.has_error()); + auto const* func_node{dynamic_cast(func_result.value().get())}; + REQUIRE(nullptr != func_node); + + REQUIRE(func_node->get_num_children() == 2); + REQUIRE(func_node->get_num_params() == 0); + REQUIRE(func_node->get_name() == cTestFuncName); + REQUIRE(nullptr != func_node->get_return_type()); + + constexpr std::string_view cExpectedSerializedResult{ + "[Function]:\n" + " Name:test_function\n" + " Return:\n" + " [Type[Container[Tuple]]]:\n" + " Element[0]:\n" + " [Type[Primitive[Int]]]:int64\n" + " Element[1]:\n" + " [Type[Struct]]:\n" + " Name:\n" + " [Identifier]:TestStruct\n" + " Element[2]:\n" + " [Type[Primitive[Bool]]]\n" + " No Params" + }; + auto const serialized_result{func_node->serialize_to_str(0)}; + REQUIRE_FALSE(serialized_result.has_error()); + REQUIRE(serialized_result.value() == cExpectedSerializedResult); + } + + SECTION("Empty param list and no return") { + // The execution model of `SECTION` ensures objects are not moved when this section is + // executed, so use move below is safe. + // NOLINTNEXTLINE(bugprone-use-after-move) + auto func_result{Function::create(std::move(function_name), {}, {})}; + REQUIRE_FALSE(func_result.has_error()); + auto const* func_node{dynamic_cast(func_result.value().get())}; + REQUIRE(nullptr != func_node); + + REQUIRE(func_node->get_num_children() == 1); + REQUIRE(func_node->get_num_params() == 0); + REQUIRE(func_node->get_name() == cTestFuncName); + REQUIRE(nullptr == func_node->get_return_type()); + + constexpr std::string_view cExpectedSerializedResult{"[Function]:\n" + " Name:test_function\n" + " Return:\n" + " void\n" + " No Params"}; + auto const serialized_result{func_node->serialize_to_str(0)}; + REQUIRE_FALSE(serialized_result.has_error()); + REQUIRE(serialized_result.value() == cExpectedSerializedResult); + } + + SECTION("Duplicated param names") { + // The execution model of `SECTION` ensures objects are not moved when this section is + // executed, so use and move these objects should be safe. + // NOLINTNEXTLINE(bugprone-use-after-move) + parameters.emplace_back(create_named_var("param_0", Int::create(IntSpec::Int64))); + auto func_result{Function::create(std::move(function_name), {}, std::move(parameters))}; + REQUIRE(func_result.has_error()); + REQUIRE(func_result.error() + == Function::ErrorCode{Function::ErrorCodeEnum::DuplicatedParamName}); + } + } } } // namespace From c3e6286bfc3b9d528b48158f15860ffacd7a165e Mon Sep 17 00:00:00 2001 From: LinZhihao-723 Date: Thu, 14 Aug 2025 13:10:39 -0400 Subject: [PATCH 6/6] Linter... --- src/spider/tdl/parser/ast/node_impl/Function.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spider/tdl/parser/ast/node_impl/Function.hpp b/src/spider/tdl/parser/ast/node_impl/Function.hpp index a6f8e23b0..fed05b48a 100644 --- a/src/spider/tdl/parser/ast/node_impl/Function.hpp +++ b/src/spider/tdl/parser/ast/node_impl/Function.hpp @@ -2,11 +2,11 @@ #define SPIDER_TDL_PARSER_AST_NODE_IMPL_FUNCTION_HPP #include +#include #include #include #include #include -#include #include #include