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..fed05b48a --- /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