#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "drake/common/drake_copyable.h" #include "drake/common/drake_throw.h" #include "drake/common/name_value.h" #include "drake/common/nice_type_name.h" #include "drake/common/unused.h" #include "drake/common/yaml/yaml_io_options.h" #include "drake/common/yaml/yaml_node.h" namespace drake { namespace yaml { namespace internal { // A helper class for @ref yaml_serialization "YAML Serialization" that loads // data from a YAML file into a C++ structure. class YamlReadArchive final { public: DRAKE_NO_COPY_NO_MOVE_NO_ASSIGN(YamlReadArchive) YamlReadArchive(internal::Node root, const LoadYamlOptions& options); static internal::Node LoadFileAsNode( const std::string& filename, const std::optional& child_name); static internal::Node LoadStringAsNode( const std::string& data, const std::optional& child_name); // Sets the contents `serializable` based on the YAML file associated this // archive. template void Accept(Serializable* serializable) { DRAKE_THROW_UNLESS(serializable != nullptr); this->DoAccept(serializable, static_cast(0)); CheckAllAccepted(); } // Sets the value pointed to by `nvp.value()` based on the YAML file // associated with this archive. Most users should call Accept, not Visit. template void Visit(const NameValuePair& nvp) { this->Visit(nvp, VisitShouldMemorizeType::kYes); } private: // N.B. In the private details below, we use "NVP" to abbreviate the // "NameValuePair" template concept. // Internal-use constructor during recursion. This constructor aliases all // of its arguments, so all must outlive this object. YamlReadArchive(const internal::Node* root, const YamlReadArchive* parent) : owned_root_(), root_(root), mapish_item_key_(nullptr), mapish_item_value_(nullptr), options_(parent->options_), parent_(parent) { DRAKE_DEMAND(root != nullptr); DRAKE_DEMAND(parent != nullptr); } // Internal-use constructor during recursion. This constructor aliases all // of its arguments, so all must outlive this object. The effect is as-if // we have a root of type NodeType::Mapping with a single (key, value) entry. YamlReadArchive(const char* mapish_item_key, const internal::Node* mapish_item_value, const YamlReadArchive* parent) : owned_root_(), root_(nullptr), mapish_item_key_(mapish_item_key), mapish_item_value_(mapish_item_value), options_(parent->options_), parent_(parent) { DRAKE_DEMAND(mapish_item_key != nullptr); DRAKE_DEMAND(mapish_item_value != nullptr); DRAKE_DEMAND(parent != nullptr); } enum class VisitShouldMemorizeType { kNo, kYes }; // Like the 1-arg Visit, except that the (private) caller can opt-out of this // visit frame appearing in error reports. template void Visit(const NameValuePair& nvp, VisitShouldMemorizeType trace) { if (trace == VisitShouldMemorizeType::kYes) { debug_visit_name_ = nvp.name(); debug_visit_type_ = &typeid(*nvp.value()); visited_names_.insert(nvp.name()); } // Use int32_t for the final argument to prefer the specialized overload. this->DoVisit(nvp, *nvp.value(), static_cast(0)); if (trace == VisitShouldMemorizeType::kYes) { debug_visit_name_ = nullptr; debug_visit_type_ = nullptr; } } // -------------------------------------------------------------------------- // @name Overloads for the Accept() implementation // This version applies when Serialize is member method. template auto DoAccept(Serializable* serializable, int32_t) -> decltype(serializable->Serialize(this)) { serializable->Serialize(this); } // This version applies when `value` is a std::map from std::string to // Serializable. The map's values must be serializable, but there is no // Serialize function required for the map itself. template void DoAccept(std::map* value, int32_t) { DRAKE_THROW_UNLESS(root_ != nullptr); DRAKE_THROW_UNLESS(root_->IsMapping()); VisitMapDirectly(*root_, value); for (const auto& [name, ignored] : *value) { unused(ignored); visited_names_.insert(name); } } // This version applies when Serialize is an ADL free function. template void DoAccept(Serializable* serializable, int64_t) { Serialize(this, serializable); } // -------------------------------------------------------------------------- // @name Overloads for the Visit() implementation // This version applies when the type has a Serialize member function. template auto DoVisit(const NVP& nvp, const T&, int32_t) -> decltype(nvp.value()->Serialize( static_cast(nullptr))) { this->VisitSerializable(nvp); } // This version applies when the type has an ADL Serialize function. template auto DoVisit(const NVP& nvp, const T&, int32_t) -> decltype(Serialize(static_cast(nullptr), nvp.value())) { this->VisitSerializable(nvp); } // For std::vector. template void DoVisit(const NVP& nvp, const std::vector&, int32_t) { this->VisitVector(nvp); } // For std::array. template void DoVisit(const NVP& nvp, const std::array&, int32_t) { this->VisitArray(nvp.name(), N, nvp.value()->data()); } // For std::map. template void DoVisit(const NVP& nvp, const std::map&, int32_t) { this->VisitMap(nvp); } // For std::unordered_map. template void DoVisit(const NVP& nvp, const std::unordered_map&, int32_t) { this->VisitMap(nvp); } // For std::optional. template void DoVisit(const NVP& nvp, const std::optional&, int32_t) { this->VisitOptional(nvp); } // For std::variant. template void DoVisit(const NVP& nvp, const std::variant&, int32_t) { this->VisitVariant(nvp); } // For Eigen::Matrix or Eigen::Vector. template void DoVisit(const NVP& nvp, const Eigen::Matrix&, int32_t) { if constexpr (Cols == 1) { if constexpr (Rows >= 0) { this->VisitArray(nvp.name(), Rows, nvp.value()->data()); } else if constexpr (MaxRows >= 0) { this->VisitVector(nvp, MaxRows); } else { this->VisitVector(nvp); } } else { this->VisitMatrix(nvp.name(), nvp.value()); } } // If no other DoVisit matched, we'll treat the value as a scalar. template void DoVisit(const NVP& nvp, const T&, int64_t) { this->VisitScalar(nvp); } // -------------------------------------------------------------------------- // @name Implementations of Visit() once the shape is known template void VisitSerializable(const NVP& nvp) { const internal::Node* sub_node = GetSubNodeMapping(nvp.name()); if (sub_node == nullptr) { return; } YamlReadArchive sub_archive(sub_node, this); auto&& value = *nvp.value(); sub_archive.Accept(&value); } template void VisitScalar(const NVP& nvp) { const internal::Node* sub_node = GetSubNodeScalar(nvp.name()); if (sub_node == nullptr) { return; } ParseScalar(sub_node->GetScalar(), nvp.value()); } template void VisitOptional(const NVP& nvp) { // When visiting an optional, we want to match up the null-ness of the YAML // node with the nullopt-ness of the C++ data. Refer to the unit tests for // Optional for a full explanation. const internal::Node* sub_node = MaybeGetSubNode(nvp.name()); if (sub_node == nullptr) { if (!options_.allow_cpp_with_no_yaml) { *nvp.value() = std::nullopt; } return; } if (sub_node->GetTag() == internal::Node::kTagNull) { *nvp.value() = std::nullopt; return; } // Visit the unpacked optional as if it weren't wrapped in optional<>. using T = typename NVP::value_type::value_type; std::optional& storage = *nvp.value(); if (!storage) { storage = T{}; } this->Visit(drake::MakeNameValue(nvp.name(), &storage.value()), VisitShouldMemorizeType::kNo); } template void VisitVariant(const NVP& nvp) { const internal::Node* sub_node = MaybeGetSubNode(nvp.name()); if (sub_node == nullptr) { if (!options_.allow_cpp_with_no_yaml) { ReportError("is missing"); } return; } // Figure out which variant<...> type we have based on the node's tag. const std::string_view tag = sub_node->GetTag(); VariantHelper(tag, nvp.name(), nvp.value()); } // Steps through Types to extract 'size_t I' and 'typename T' for the Impl. template