Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/spider/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ set(SPIDER_TDL_SHARED_SOURCES
tdl/parser/ast/Node.cpp
tdl/parser/ast/node_impl/Identifier.cpp
tdl/parser/ast/node_impl/NamedVar.cpp
tdl/parser/ast/node_impl/StructSpec.cpp
tdl/parser/ast/node_impl/type_impl/container_impl/List.cpp
tdl/parser/ast/node_impl/type_impl/container_impl/Map.cpp
tdl/parser/ast/node_impl/type_impl/container_impl/Tuple.cpp
Expand All @@ -216,6 +217,7 @@ set(SPIDER_TDL_SHARED_HEADERS
tdl/parser/ast/IntSpec.hpp
tdl/parser/ast/node_impl/Identifier.hpp
tdl/parser/ast/node_impl/NamedVar.hpp
tdl/parser/ast/node_impl/StructSpec.hpp
tdl/parser/ast/node_impl/Type.hpp
tdl/parser/ast/node_impl/type_impl/Container.hpp
tdl/parser/ast/node_impl/type_impl/container_impl/List.hpp
Expand All @@ -238,6 +240,7 @@ target_include_directories(spider_tdl PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..)
target_link_libraries(
spider_tdl
PUBLIC
absl::flat_hash_set
fmt::fmt
ystdlib::error_handling
)
Expand Down
1 change: 1 addition & 0 deletions src/spider/tdl/parser/ast/Node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <cstdint>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>

#include <ystdlib/error_handling/ErrorCode.hpp>
Expand Down
97 changes: 97 additions & 0 deletions src/spider/tdl/parser/ast/node_impl/StructSpec.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#include "StructSpec.hpp"

#include <cstddef>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include <absl/container/flat_hash_set.h>
#include <fmt/format.h>
#include <fmt/ranges.h>
#include <ystdlib/error_handling/ErrorCode.hpp>
#include <ystdlib/error_handling/Result.hpp>

#include <spider/tdl/parser/ast/Node.hpp>
#include <spider/tdl/parser/ast/node_impl/Identifier.hpp>
#include <spider/tdl/parser/ast/node_impl/NamedVar.hpp>
#include <spider/tdl/parser/ast/utils.hpp>

using spider::tdl::parser::ast::node_impl::StructSpec;
using StructSpecErrorCodeCategory
= ystdlib::error_handling::ErrorCategory<StructSpec::ErrorCodeEnum>;

template <>
auto StructSpecErrorCodeCategory::name() const noexcept -> char const* {
return "spider::tdl::parser::ast::node_impl::StructSpec";
}

template <>
auto StructSpecErrorCodeCategory::message(StructSpec::ErrorCodeEnum error_enum) const
-> std::string {
switch (error_enum) {
case StructSpec::ErrorCodeEnum::DuplicatedFieldName:
return "The struct spec has duplicated field names.";
case StructSpec::ErrorCodeEnum::EmptyStruct:
return "The struct spec is empty.";
default:
return "Unknown error code enum";
}
}

namespace spider::tdl::parser::ast::node_impl {
auto StructSpec::create(std::unique_ptr<Node> name, std::vector<std::unique_ptr<Node>> fields)
-> ystdlib::error_handling::Result<std::shared_ptr<StructSpec>> {
YSTDLIB_ERROR_HANDLING_TRYV(validate_child_node_type<Identifier>(name.get()));

if (fields.empty()) {
return ErrorCode{ErrorCodeEnum::EmptyStruct};
}

absl::flat_hash_set<std::string_view> field_names;
for (auto const& field : fields) {
YSTDLIB_ERROR_HANDLING_TRYV(validate_child_node_type<NamedVar>(field.get()));
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
auto const field_name{static_cast<NamedVar const&>(*field).get_id()->get_name()};
if (field_names.contains(field_name)) {
return ErrorCode{ErrorCodeEnum::DuplicatedFieldName};
}
field_names.emplace(field_name);
}

auto struct_spec{std::make_shared<StructSpec>(StructSpec{})};
YSTDLIB_ERROR_HANDLING_TRYV(struct_spec->add_child(std::move(name)));
for (auto& field : fields) {
YSTDLIB_ERROR_HANDLING_TRYV(struct_spec->add_child(std::move(field)));
}
return struct_spec;
}

auto StructSpec::serialize_to_str(size_t indentation_level) const
-> ystdlib::error_handling::Result<std::string> {
std::vector<std::string> serialized_fields;
YSTDLIB_ERROR_HANDLING_TRYV(
visit_fields([&](NamedVar const& child) -> ystdlib::error_handling::Result<void> {
serialized_fields.emplace_back(
fmt::format(
"{}Fields[{}]:\n{}",
create_indentation(indentation_level + 1),
serialized_fields.size(),
YSTDLIB_ERROR_HANDLING_TRYX(
child.serialize_to_str(indentation_level + 2)
)
)
);
return ystdlib::error_handling::success();
})
);
return fmt::format(
"{}[StructSpec]:\n{}Name:{}\n{}",
create_indentation(indentation_level),
create_indentation(indentation_level + 1),
get_name(),
fmt::join(serialized_fields, "\n")
);
}
} // namespace spider::tdl::parser::ast::node_impl
96 changes: 96 additions & 0 deletions src/spider/tdl/parser/ast/node_impl/StructSpec.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#ifndef SPIDER_TDL_PARSER_AST_NODE_IMPL_STRUCTSPEC_HPP
#define SPIDER_TDL_PARSER_AST_NODE_IMPL_STRUCTSPEC_HPP

#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>

#include <ystdlib/error_handling/ErrorCode.hpp>
#include <ystdlib/error_handling/Result.hpp>

#include <spider/tdl/parser/ast/Node.hpp>
#include <spider/tdl/parser/ast/node_impl/Identifier.hpp>
#include <spider/tdl/parser/ast/node_impl/NamedVar.hpp>

namespace spider::tdl::parser::ast::node_impl {
/**
* Represents the specification of a struct in the TDL.
*/
class StructSpec : public Node {
public:
// Types
enum class ErrorCodeEnum : uint8_t {
DuplicatedFieldName = 1,
EmptyStruct,
};

using ErrorCode = ystdlib::error_handling::ErrorCode<ErrorCodeEnum>;

// Factory function
/**
* @param name
* @param fields
* @return A result containing a shared pointer to a new `StructSpec` instance with the name and
* fields on success, or an error code indicating the failure:
* - StructSpec::ErrorCodeEnum::DuplicatedFieldName if the `fields` contains duplicated field
* names.
* - StructSpec::ErrorCodeEnum::EmptyStruct if the `fields` is empty.
* - Forwards `validate_child_node_type`'s return values.
*/
[[nodiscard]] static auto
create(std::unique_ptr<Node> name, std::vector<std::unique_ptr<Node>> fields)
-> ystdlib::error_handling::Result<std::shared_ptr<StructSpec>>;

// Methods implementing `Node`
[[nodiscard]] auto serialize_to_str(size_t indentation_level) const
-> ystdlib::error_handling::Result<std::string> 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<Identifier const*>(get_child_unsafe(0))->get_name();
}

[[nodiscard]] auto get_num_fields() const -> size_t { return get_num_children() - 1; }

/**
* Visits the fields.
* @tparam FieldVisitor
* @param visitor
* @return A void result on success, or an error code indicating the failure:
* - Forwards `visitor`'s return values.
*/
template <typename FieldVisitor>
requires(std::is_invocable_r_v<
ystdlib::error_handling::Result<void>,
FieldVisitor,
NamedVar const&>)
[[nodiscard]] auto visit_fields(FieldVisitor visitor) const
-> ystdlib::error_handling::Result<void> {
for (size_t child_idx{1}; 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<NamedVar const&>(*get_child_unsafe(child_idx)))
);
}
return ystdlib::error_handling::success();
}

private:
// Constructor
StructSpec() = default;
};
} // namespace spider::tdl::parser::ast::node_impl

YSTDLIB_ERROR_HANDLING_MARK_AS_ERROR_CODE_ENUM(
spider::tdl::parser::ast::node_impl::StructSpec::ErrorCodeEnum
);

#endif // SPIDER_TDL_PARSER_AST_NODE_IMPL_STRUCTSPEC_HPP
97 changes: 97 additions & 0 deletions tests/tdl/test-parser-ast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <spider/tdl/parser/ast/Node.hpp>
#include <spider/tdl/parser/ast/node_impl/Identifier.hpp>
#include <spider/tdl/parser/ast/node_impl/NamedVar.hpp>
#include <spider/tdl/parser/ast/node_impl/StructSpec.hpp>
#include <spider/tdl/parser/ast/node_impl/type_impl/container_impl/List.hpp>
#include <spider/tdl/parser/ast/node_impl/type_impl/container_impl/Map.hpp>
#include <spider/tdl/parser/ast/node_impl/type_impl/container_impl/Tuple.hpp>
Expand All @@ -29,6 +30,7 @@ TEST_CASE("test-ast-node", "[tdl][ast][Node]") {
using spider::tdl::parser::ast::Node;
using spider::tdl::parser::ast::node_impl::Identifier;
using spider::tdl::parser::ast::node_impl::NamedVar;
using spider::tdl::parser::ast::node_impl::StructSpec;
using spider::tdl::parser::ast::node_impl::type_impl::container_impl::List;
using spider::tdl::parser::ast::node_impl::type_impl::container_impl::Map;
using spider::tdl::parser::ast::node_impl::type_impl::container_impl::Tuple;
Expand Down Expand Up @@ -287,6 +289,101 @@ TEST_CASE("test-ast-node", "[tdl][ast][Node]") {
REQUIRE(serialized_result.value() == cExpectedSerializedResult);
}
}

SECTION("StructSpec") {
constexpr std::string_view cTestStructName{"TestStruct"};

auto int_field_result{
NamedVar::create(Identifier::create("m_int"), Int::create(IntSpec::Int64))
};
REQUIRE_FALSE(int_field_result.has_error());
auto float_field_result{
NamedVar::create(Identifier::create("m_float"), Float::create(FloatSpec::Double))
};
REQUIRE_FALSE(float_field_result.has_error());
auto map_result{Map::create(Int::create(IntSpec::Int64), Float::create(FloatSpec::Double))};
REQUIRE_FALSE(map_result.has_error());
auto map_field_result{
NamedVar::create(Identifier::create("m_map"), std::move(map_result.value()))
};
REQUIRE_FALSE(map_field_result.has_error());
std::vector<std::unique_ptr<Node>> fields;
fields.emplace_back(std::move(int_field_result.value()));
fields.emplace_back(std::move(float_field_result.value()));
fields.emplace_back(std::move(map_field_result.value()));

SECTION("Basic") {
auto struct_spec_result{StructSpec::create(
Identifier::create(std::string{cTestStructName}),
std::move(fields)
)};
REQUIRE_FALSE(struct_spec_result.has_error());
auto const* struct_spec_node{
dynamic_cast<StructSpec const*>(struct_spec_result.value().get())
};
REQUIRE(nullptr != struct_spec_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("Fields with duplicated name") {
auto duplicated_int_field_result{
NamedVar::create(Identifier::create("m_int"), Int::create(IntSpec::Int64))
};
REQUIRE_FALSE(duplicated_int_field_result.has_error());
// The execution model of `SECTION` ensures `fields` is not moved when this section is
// executed, so using `fields` here is safe.
// NOLINTNEXTLINE(bugprone-use-after-move)
fields.emplace_back(std::move(duplicated_int_field_result.value()));
auto struct_spec_result{StructSpec::create(
Identifier::create(std::string{cTestStructName}),
std::move(fields)
)};
REQUIRE(struct_spec_result.has_error());
REQUIRE(struct_spec_result.error()
== StructSpec::ErrorCode{StructSpec::ErrorCodeEnum::DuplicatedFieldName});
}

SECTION("Empty") {
auto struct_spec_result{
StructSpec::create(Identifier::create(std::string{cTestStructName}), {})
};
REQUIRE(struct_spec_result.has_error());
REQUIRE(struct_spec_result.error()
== StructSpec::ErrorCode{StructSpec::ErrorCodeEnum::EmptyStruct});
}
}
}
} // namespace

Expand Down