#pragma once #include #include #include #include #include #include #include #include #include "drake/common/drake_copyable.h" #include "drake/common/fmt_ostream.h" // Note that even though this file contains "class Node", the file is named // "yaml_node.h" not "node.h" to avoid conflict with "yaml-cpp/node/node.h". namespace drake { namespace yaml { namespace internal { /* The three possible kinds of YAML nodes. See https://yaml.org/spec/1.2.2/#nodes for the definition of a node. Note that even though our links to the YAML specification point to YAML version 1.2.2, we don't actually care about the version number in particular; this class is not tied to a specific version. */ enum class NodeType { // See https://yaml.org/spec/1.2.2/#scalar for the definition. // See https://yaml.org/spec/1.2.2/#scalars for examples. // // Note that even though Drake most often uses "scalar" to refer to a // mathematical scalar type such as `double`, here we use "scalar" in the // sense of YAML. kScalar, // See https://yaml.org/spec/1.2.2/#sequence for the definition. // See https://yaml.org/spec/1.2.2/#collections for Example 2.1. kSequence, // See https://yaml.org/spec/1.2.2/#mapping for the definition. // See https://yaml.org/spec/1.2.2/#collections for Example 2.2. // // Note that even though YAML in general allows the keys of a mapping to be // any type of node, in our implementation we limit keys to be only strings, // for better compatibility with other serialization formats such as JSON. kMapping, }; /* Denotes one of the "JSON Schema" tags. See https://yaml.org/spec/1.2.2/#json-schema. */ enum class JsonSchemaTag { // https://yaml.org/spec/1.2.2/#null kNull, // https://yaml.org/spec/1.2.2/#boolean kBool, // https://yaml.org/spec/1.2.2/#integer kInt, // https://yaml.org/spec/1.2.2/#floating-point kFloat, }; /* Data type that represents a YAML node. A Node can hold one of three possible kinds of value at runtime: - Scalar - Sequence[Node] - Mapping[string, Node] Refer to https://yaml.org/spec/1.2.2/#nodes for details. This class implements the https://yaml.org/spec/1.2.2/#321-representation-graph concept, with two caveats for better compatibility with JSON serialization: - graph cycles are not allowed; - mapping keys must only be scalar strings. Each node may also have a tag. By default (i.e., at construction time), the tag will be empty. Use GetTag() and SetTag() to query and adjust it. Refer to https://yaml.org/spec/1.2.2/#tags for details. */ class Node final { public: DRAKE_DEFAULT_COPY_AND_MOVE_AND_ASSIGN(Node) /* Returns a Scalar node with the given value. Note that even though Drake most often uses "scalar" to refer to a mathematical scalar type such as `double`, here we use "scalar" in the sense of YAML. */ static Node MakeScalar(std::string value = {}); /* Returns an empty Sequence node. */ static Node MakeSequence(); /* Returns an empty Mapping node. */ static Node MakeMapping(); /* Returns a null Scalar node. The returned node's tag is kTagNull and value is "null". Refer to https://yaml.org/spec/1.2.2/#null for details. */ static Node MakeNull(); /* Returns type of stored value. */ NodeType GetType() const; /* Returns a description of the type of stored value, suitable for use in error messages, e.g., "Mapping". */ std::string_view GetTypeString() const; /* Returns a description of the given type, suitable for use in error messages, e.g., "Mapping". */ static std::string_view GetTypeString(NodeType); /* Returns true iff this Node's type is Scalar. */ bool IsScalar() const; /* Returns true iff this Node's type is Sequence. */ bool IsSequence() const; /* Returns true iff this Node's type is Mapping. */ bool IsMapping() const; /* Compares two nodes for equality. */ friend bool operator==(const Node&, const Node&); /* Gets this node's YAML tag. See https://yaml.org/spec/1.2.2/#tags. By default (i.e., at construction time), the tag will be empty. */ std::string_view GetTag() const; /* Sets this node's YAML tag to one of the "JSON Schema" tags. See https://yaml.org/spec/1.2.2/#json-schema. */ void SetTag(JsonSchemaTag); /* Sets this node's YAML tag. See https://yaml.org/spec/1.2.2/#tags. The tag is not checked for well-formedness nor consistency with the node's type nor value. The caller is responsible for providing a valid tag. */ void SetTag(std::string); // https://yaml.org/spec/1.2.2/#null static constexpr std::string_view kTagNull{"tag:yaml.org,2002:null"}; // https://yaml.org/spec/1.2.2/#boolean static constexpr std::string_view kTagBool{"tag:yaml.org,2002:bool"}; // https://yaml.org/spec/1.2.2/#integer static constexpr std::string_view kTagInt{"tag:yaml.org,2002:int"}; // https://yaml.org/spec/1.2.2/#floating-point static constexpr std::string_view kTagFloat{"tag:yaml.org,2002:float"}; // https://yaml.org/spec/1.2.2/#generic-string static constexpr std::string_view kTagStr{"tag:yaml.org,2002:str"}; /* Sets the filename where this Node was read from. A nullopt indicates that the filename is not known. */ void SetFilename(std::optional filename); /* Gets the filename where this Node was read from. A nullopt indicates that the filename is not known. */ const std::optional& GetFilename() const; /* An indication of where in a file or string this Node was read from. The indexing is 1-based (the first character is line 1 column 1). */ struct Mark { int line{}; int column{}; friend bool operator==(const Mark&, const Mark&); }; /* Sets the line:column offset in the file or string where this Node was read from. A nullopt indicates that the Node's position is unknown. */ void SetMark(std::optional mark); /* Gets the line:column offset in the file or string where this Node was read from. A nullopt indicates that the Node's position is unknown. */ const std::optional& GetMark() const; // @name Scalar-only Functions // These functions may only be called when IsScalar() is true; // otherwise, they will throw an exception. // // Note that there is no SetScalar function provided; users should call // the Node::operator= function, instead. //@{ /* Gets this node's Scalar data. */ const std::string& GetScalar() const; //@} // @name Sequence-only Functions // These functions may only be called when IsSequence() is true; // otherwise, they will throw an exception. // // Note that there is no SetSequence function provided to bulk-overwrite the // sequence; users should call the Node::operator= function, instead. //@{ /* Gets this node's Sequence data. */ const std::vector& GetSequence() const; /* Appends a new node to the back of this Sequence. Any iterators based on GetSequence() are invalidated. */ void Add(Node); //@} // @name Mapping-only Functions // These functions may only be called when IsMapping() is true; // otherwise, they will throw an exception. // // Note that there is no SetMapping function provided to bulk-overwrite the // mapping; users should call the Node::operator= function, instead. //@{ /* Gets this node's Mapping data. */ const std::map& GetMapping() const; /* Add a new node to this Mapping. Any iterators based on GetMapping() remain valid. @throws std::exception the given key was already in this mapping. */ void Add(std::string key, Node value); /* Gets an existing node from this Mapping. Any iterators based on GetMapping() remain valid. @throws std::exception the given key does not exist. */ Node& At(std::string_view key); /* Removes an existing node from this Mapping. Any iterators based on GetMapping() that referred to this key are invalidated. @throws std::exception the given key does not exist. */ void Remove(std::string_view key); //@} /* Calls back into the given Visitor using operator(), with an argument type (see below) based on this Node's type. */ template void Visit(Visitor&& visitor) const { return std::visit(std::forward(visitor), data_); } /* The argument type for Visit on a Scalar node .*/ struct ScalarData final { std::string scalar; friend bool operator==(const ScalarData&, const ScalarData&); }; /* The argument type for Visit on a Sequence node .*/ struct SequenceData final { std::vector sequence; friend bool operator==(const SequenceData&, const SequenceData&); }; /* The argument type for Visit on a Mapping node .*/ struct MappingData final { // Even though YAML mappings are notionally unordered, we use an ordered // map here to ensure program determinism. std::map mapping; friend bool operator==(const MappingData&, const MappingData&); }; /* Displays the given node using flow style. Intended only for debugging, not serialization. */ friend std::ostream& operator<<(std::ostream&, const Node&); private: /* No-op for use only by the public "Make..." functions. */ Node(); using Variant = std::variant; Variant data_; // The YAML tag is not required, but can be set to either a well-known enum or // a bespoke string. The representation here is not canonical -- it's possible // to set a string value that is equivalent to an enum's implied string. std::variant tag_; std::optional mark_; std::optional filename_; }; } // namespace internal } // namespace yaml } // namespace drake #ifndef DRAKE_DOXYGEN_CXX // TODO(jwnimmer-tri) Add a real formatter and deprecate the operator<<. namespace fmt { template <> struct formatter : drake::ostream_formatter {}; } // namespace fmt #endif