From 8d5327d0aaad3050e62cfc4847e1bf0c6512f955 Mon Sep 17 00:00:00 2001 From: graehl Date: Mon, 8 Aug 2022 15:38:32 -0700 Subject: [PATCH] node: id hash_value UnshareSubtrees Deep+ShallowClone ModifyValues At the cost of exposing some existing detail, enable efficient unsharing of graph structure (DeepClone), and efficient ShallowClone. Modify[Key]Values allows in-place modification (or deletion) of node_data in a Map. (the Key is not modified but provided so the modification of the value may depend on the key) UnshareSubtrees for in-place DeepClone copying of shared subtrees. id and hash_value allow creating efficient input Node -> output Node transformations where the result of a previously transformed subtree may be reused (allowing linear time creation of output trees with shared subtrees instead of exponential by Rationale: although YAML::Node faithfully represents the input document and allows efficient traversal, using it as a large-scale configuration / AST means that it's easy to unintentionally create quadratic or worse algorithms modifying the tree. --- include/yaml-cpp/node/detail/impl.h | 8 ++ include/yaml-cpp/node/detail/node.h | 34 ++++- include/yaml-cpp/node/detail/node_data.h | 21 ++- include/yaml-cpp/node/detail/node_ref.h | 25 ++++ include/yaml-cpp/node/impl.h | 14 +- include/yaml-cpp/node/modify.h | 26 ++++ include/yaml-cpp/node/node.h | 62 ++++++++- src/node.cpp | 164 ++++++++++++++++++++++- src/node_data.cpp | 42 ++++++ test/node/node_test.cpp | 115 ++++++++++++++++ 10 files changed, 505 insertions(+), 6 deletions(-) create mode 100644 include/yaml-cpp/node/modify.h diff --git a/include/yaml-cpp/node/detail/impl.h b/include/yaml-cpp/node/detail/impl.h index b38038d..3aff296 100644 --- a/include/yaml-cpp/node/detail/impl.h +++ b/include/yaml-cpp/node/detail/impl.h @@ -221,6 +221,14 @@ inline void node_data::force_insert(const Key& key, const Value& value, insert_map_pair(k, v); } +inline void node_data::seq_push_back(node& rhs) { + m_sequence.push_back(&rhs); +} + +inline void node_data::map_force_insert(node& key, node& value) { + insert_map_pair(key, value); +} + template inline node& node_data::convert_to_node(const T& rhs, shared_memory_holder pMemory) { diff --git a/include/yaml-cpp/node/detail/node.h b/include/yaml-cpp/node/detail/node.h index acf60ff..08c7cba 100644 --- a/include/yaml-cpp/node/detail/node.h +++ b/include/yaml-cpp/node/detail/node.h @@ -28,7 +28,12 @@ class node { node(const node&) = delete; node& operator=(const node&) = delete; - bool is(const node& rhs) const { return m_pRef == rhs.m_pRef; } + // if set_data points us at the same data as another node then we want the + // same id. maybe set_data has single-owner semantics and this is unnecessary, + // but this is not clear (yet) + // alt: return m_pRef->id(); // (void*)this; + void *id() const YAML_CPP_NOEXCEPT { return m_pRef.get(); } + bool is(const node& rhs) const { return id() == rhs.id(); } const node_ref* ref() const { return m_pRef.get(); } bool is_defined() const { return m_pRef->is_defined(); } @@ -71,6 +76,14 @@ class node { m_pRef->set_data(*rhs.m_pRef); } + void copy_contents(const node& rhs) { + if (&rhs == this) return; + m_pRef = std::make_shared(); + if (rhs.is_defined()) + mark_defined(); + m_pRef->copy_contents(*rhs.m_pRef); + } + void set_mark(const Mark& mark) { m_pRef->set_mark(mark); } void set_type(NodeType::value type) { @@ -110,17 +123,36 @@ class node { } node_iterator end() { return m_pRef->end(); } + void modify(modify_values const& f) { + m_pRef->modify(f); + } + + void map_modify(modify_key_values const& f) { + m_pRef->map_modify(f); + } + // sequence void push_back(node& input, shared_memory_holder pMemory) { m_pRef->push_back(input, pMemory); input.add_dependency(*this); m_index = m_amount.fetch_add(1); } + // \pre IsSequence(), rhs held in our memory + void seq_push_back(node& rhs) { + m_pRef->seq_push_back(rhs); + } + // map void insert(node& key, node& value, shared_memory_holder pMemory) { m_pRef->insert(key, value, pMemory); key.add_dependency(*this); value.add_dependency(*this); } + // \pre IsMap(), key, value held in our memory + void map_force_insert(node& key, node& value) { + m_pRef->map_force_insert(key, value); + key.add_dependency(*this); + value.add_dependency(*this); + } // indexing template diff --git a/include/yaml-cpp/node/detail/node_data.h b/include/yaml-cpp/node/detail/node_data.h index 07cf81a..f5e43c5 100644 --- a/include/yaml-cpp/node/detail/node_data.h +++ b/include/yaml-cpp/node/detail/node_data.h @@ -30,7 +30,7 @@ namespace detail { class YAML_CPP_API node_data { public: node_data(); - node_data(const node_data&) = delete; + node_data(const node_data&) = default; node_data& operator=(const node_data&) = delete; void mark_defined(); @@ -61,6 +61,10 @@ class YAML_CPP_API node_data { // sequence void push_back(node& node, const shared_memory_holder& pMemory); + // \pre IsSequence, node in our memory + void seq_push_back(node& node); + + // map void insert(node& key, node& value, const shared_memory_holder& pMemory); // indexing @@ -80,6 +84,21 @@ class YAML_CPP_API node_data { void force_insert(const Key& key, const Value& value, shared_memory_holder pMemory); + // map. \pre IsMap() + void map_force_insert(node& key, node& value); + + void modify(modify_values const& f) + { + if (m_type == NodeType::Sequence) + seq_modify(f); + else if (m_type == NodeType::Map) + map_modify(f); + } + + void seq_modify(modify_values const& f); + void map_modify(modify_values const& f); + void map_modify(modify_key_values const& f); + public: static const std::string& empty_scalar(); diff --git a/include/yaml-cpp/node/detail/node_ref.h b/include/yaml-cpp/node/detail/node_ref.h index d8a94f8..a168832 100644 --- a/include/yaml-cpp/node/detail/node_ref.h +++ b/include/yaml-cpp/node/detail/node_ref.h @@ -27,8 +27,15 @@ class node_ref { const std::string& tag() const { return m_pData->tag(); } EmitterStyle::value style() const { return m_pData->style(); } + // needed if we allow copy_contents + void *id() const YAML_CPP_NOEXCEPT { return m_pData.get(); } void mark_defined() { m_pData->mark_defined(); } void set_data(const node_ref& rhs) { m_pData = rhs.m_pData; } + void copy_contents(const node_ref& rhs) { + if (&rhs == this) return; + node_data *r = rhs.m_pData.get(); + m_pData = std::make_shared(*r); + } void set_mark(const Mark& mark) { m_pData->set_mark(mark); } void set_type(NodeType::value type) { m_pData->set_type(type); } @@ -50,13 +57,31 @@ class node_ref { } node_iterator end() { return m_pData->end(); } + void modify(modify_values const& f) { + m_pData->modify(f); + } + + void map_modify(modify_key_values const& f) { + m_pData->map_modify(f); + } + // sequence void push_back(node& node, shared_memory_holder pMemory) { m_pData->push_back(node, pMemory); } + // \pre IsSequence, node in our memory + void seq_push_back(node& node) { + m_pData->seq_push_back(node); + } + + // map void insert(node& key, node& value, shared_memory_holder pMemory) { m_pData->insert(key, value, pMemory); } + // \pre IsMap, key value held in our memory + void map_force_insert(node& key, node& value) { + m_pData->map_force_insert(key, value); + } // indexing template diff --git a/include/yaml-cpp/node/impl.h b/include/yaml-cpp/node/impl.h index 312281f..31d5473 100644 --- a/include/yaml-cpp/node/impl.h +++ b/include/yaml-cpp/node/impl.h @@ -50,7 +50,7 @@ inline Node::Node(Zombie) inline Node::Node(Zombie, const std::string& key) : m_isValid(false), m_invalidKey(key), m_pMemory{}, m_pNode(nullptr) {} -inline Node::Node(detail::node& node, detail::shared_memory_holder pMemory) +inline Node::Node(detail::node& node, detail::shared_memory_holder const& pMemory) : m_isValid(true), m_invalidKey{}, m_pMemory(pMemory), m_pNode(&node) {} inline Node::~Node() = default; @@ -191,6 +191,8 @@ inline void Node::SetStyle(EmitterStyle::value style) { m_pNode->set_style(style); } +inline void* Node::id() const YAML_CPP_NOEXCEPT { return m_pNode ? m_pNode->id() : nullptr; } + // assignment inline bool Node::is(const Node& rhs) const { if (!m_isValid || !rhs.m_isValid) @@ -371,12 +373,22 @@ inline bool Node::remove(const Node& key) { return m_pNode->remove(*key.m_pNode, m_pMemory); } +inline void Node::seq_push_back(const Node& rhs) { + m_pMemory->merge(*rhs.m_pMemory); + m_pNode->seq_push_back(*rhs.m_pNode); +} + // map template inline void Node::force_insert(const Key& key, const Value& value) { EnsureNodeExists(); m_pNode->force_insert(key, value, m_pMemory); } +inline void Node::map_force_insert(Node const& key, Node const& value) { + m_pMemory->merge(*key.m_pMemory); + m_pMemory->merge(*value.m_pMemory); + m_pNode->map_force_insert(*key.m_pNode, *value.m_pNode); +} // free functions inline bool operator==(const Node& lhs, const Node& rhs) { return lhs.is(rhs); } diff --git a/include/yaml-cpp/node/modify.h b/include/yaml-cpp/node/modify.h new file mode 100644 index 0000000..de62042 --- /dev/null +++ b/include/yaml-cpp/node/modify.h @@ -0,0 +1,26 @@ +#ifndef YAMLCPP__NODE_MODIFY_H +#define YAMLCPP__NODE_MODIFY_H + +#if defined(_MSC_VER) || \ + (defined(__GNUC__) && (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || \ + (__GNUC__ >= 4)) // GCC supports "pragma once" correctly since 3.4 +#pragma once +#endif + +#include + +namespace YAML { +namespace detail { +class node; +} // namespace detail +} // namespace YAML + +namespace YAML { +/// \return 0 to remove key, identity to leave value unchanged, or new node to replace old value +typedef std::function modify_values; +/// as above but first arg is pointer to string key (or 0 if non-scalar) +typedef std::function modify_key_values; +} + + +#endif diff --git a/include/yaml-cpp/node/node.h b/include/yaml-cpp/node/node.h index c9e9a0a..3ca132a 100644 --- a/include/yaml-cpp/node/node.h +++ b/include/yaml-cpp/node/node.h @@ -14,8 +14,10 @@ #include "yaml-cpp/emitterstyle.h" #include "yaml-cpp/mark.h" #include "yaml-cpp/node/detail/iterator_fwd.h" +#include "yaml-cpp/node/modify.h" #include "yaml-cpp/node/ptr.h" #include "yaml-cpp/node/type.h" +#include "yaml-cpp/noexcept.h" namespace YAML { namespace detail { @@ -26,6 +28,11 @@ struct iterator_value; } // namespace YAML namespace YAML { +// Node ctor tags +struct UnshareSubtrees {}; +struct ShallowClone {}; +struct DeepClone {}; + class YAML_CPP_API Node { public: friend class NodeBuilder; @@ -47,8 +54,38 @@ class YAML_CPP_API Node { explicit Node(const T& rhs); explicit Node(const detail::iterator_value& rhs); Node(const Node& rhs); + // newly allocated shallow clone (copies data incl anchors/tags). subtrees may + // still have sharing + Node(const Node& rhs, ShallowClone); + // Node(Node(rhs, memory), ShallowClone()) + Node(detail::node& rhs, detail::shared_memory_holder const& memory, + ShallowClone); + // newly allocated deep (recursive) clone (recurisvely copies data but + // possibly not anchors/tags). no cycle detection (i.e. pathological input + // with itself as subtree will expand infinitely) + Node(const Node& rhs, DeepClone); + // Node(Node(rhs, memory), DeepClone()) + Node(detail::node& rhs, detail::shared_memory_holder const& memory, + DeepClone); + // rhs but with no shared subtrees (may be is(rhs) if none were shared) + Node(const Node& rhs, UnshareSubtrees); + Node(detail::node& rhs, detail::shared_memory_holder const& memory); ~Node(); + detail::node& node() const { return *m_pNode; } + detail::shared_memory_holder const& memory() const { return m_pMemory; } + + // \post no anchor-shared subtrees (all but one will be clones instead). + void UnshareSubtrees(); + // \post *this = Node(*this, ShallowClone) - copy data. + void OwnData() const; + // f takes and returns a detail::node * for each value in sequence or map; + // value is updated. does nothing for other types + void ModifyValues(modify_values const& f); + // \pre IsMap(). f(std::string const* key, detail::node* val)->detail::node* + // as above but with key (if Scalar) else 0 arg + void ModifyKeyValues(modify_key_values const& f); + YAML::Mark Mark() const; NodeType::value Type() const; bool IsDefined() const; @@ -76,7 +113,15 @@ class YAML_CPP_API Node { EmitterStyle::value Style() const; void SetStyle(EmitterStyle::value style); + // a.id() == b.id() <=> a.is(b). \pre valid + void* id() const YAML_CPP_NOEXCEPT; + friend inline std::size_t hash_value(Node const& n) YAML_CPP_NOEXCEPT { + return n.hash(); + } + std::size_t hash() const YAML_CPP_NOEXCEPT { return (std::size_t)id() >> 5; } + // assignment + // note: default/invalid nodes return false; undefined may return true bool is(const Node& rhs) const; template Node& operator=(const T& rhs); @@ -96,6 +141,8 @@ class YAML_CPP_API Node { template void push_back(const T& rhs); void push_back(const Node& rhs); + // \pre IsSequence + void seq_push_back(const Node& rhs); // indexing template @@ -112,12 +159,13 @@ class YAML_CPP_API Node { // map template void force_insert(const Key& key, const Value& value); + // \pre IsMap() + void map_force_insert(Node const& key, Node const& value); private: enum Zombie { ZombieNode }; explicit Node(Zombie); explicit Node(Zombie, const std::string&); - explicit Node(detail::node& node, detail::shared_memory_holder pMemory); void EnsureNodeExists() const; @@ -137,12 +185,22 @@ class YAML_CPP_API Node { mutable detail::node* m_pNode; }; +// lhs.is(rhs) YAML_CPP_API bool operator==(const Node& lhs, const Node& rhs); +// keeps anchors etc intact YAML_CPP_API Node Clone(const Node& node); template struct convert; -} +} // namespace YAML + +namespace std { +template <> +struct hash { + std::size_t operator()(YAML::Node const& n) { return n.hash(); } + std::size_t operator()(YAML::Node const* n) { return n->hash(); } +}; +} // namespace std #endif // NODE_NODE_H_62B23520_7C8E_11DE_8A39_0800200C9A66 diff --git a/src/node.cpp b/src/node.cpp index badc311..19aeb11 100644 --- a/src/node.cpp +++ b/src/node.cpp @@ -1,12 +1,174 @@ + #include "yaml-cpp/node/node.h" #include "nodebuilder.h" #include "nodeevents.h" +#include "yaml-cpp/node/convert.h" +#include "yaml-cpp/node/detail/impl.h" +#include "yaml-cpp/node/detail/memory.h" +#include "yaml-cpp/node/detail/node.h" +#include "yaml-cpp/node/impl.h" +#include +#include namespace YAML { -Node Clone(const Node& node) { +Node Clone(const Node &node) { NodeEvents events(node); NodeBuilder builder; events.Emit(builder); return builder.Root(); } + +Node::Node(const Node &rhs, ShallowClone) + : m_isValid(rhs.m_isValid), + m_invalidKey(), + m_pMemory(rhs.m_pMemory), + m_pNode(&m_pMemory->create_node()) { + if (!m_isValid) + return; + m_pNode->copy_contents(*rhs.m_pNode); +} + +Node::Node(detail::node &rhs, detail::shared_memory_holder const &memory, + ShallowClone) + : m_isValid(true), + m_invalidKey(), + m_pMemory(memory), + m_pNode(&m_pMemory->create_node()) { + m_pNode->copy_contents(rhs); +} + +Node::Node(const Node &rhs, DeepClone) + : m_isValid(rhs.m_isValid), + m_invalidKey(), + m_pMemory(rhs.m_pMemory), + m_pNode(&m_pMemory->create_node()) { + if (!m_isValid) + return; + NodeType::value t = rhs.Type(); + if (t == NodeType::Sequence) { + m_pNode->set_type(NodeType::Sequence); + for (auto const &sub : rhs) + m_pNode->seq_push_back(*Node(sub, DeepClone()).m_pNode); + } else if (t == NodeType::Map) { + m_pNode->set_type(NodeType::Map); + for (auto const &kv : rhs) + m_pNode->map_force_insert( + *Node(kv.first, ShallowClone()).m_pNode, + *Node(kv.second, DeepClone()).m_pNode); // held in same memory + } else + m_pNode->copy_contents(*rhs.m_pNode); +} + +Node::Node(detail::node &rhs, detail::shared_memory_holder const &memory, + DeepClone) + : m_isValid(true), + m_invalidKey(), + m_pMemory(memory), + m_pNode(&m_pMemory->create_node()) { + NodeType::value t = rhs.type(); + if (t == NodeType::Sequence) { + m_pNode->set_type(NodeType::Sequence); + for (auto const &sub : rhs) + m_pNode->seq_push_back( + *Node(*sub, m_pMemory, DeepClone()).m_pNode); // held in same memory + } else if (t == NodeType::Map) { + m_pNode->set_type(NodeType::Map); + for (auto const &kv : rhs) + m_pNode->map_force_insert( + *Node(*kv.first, m_pMemory, ShallowClone()).m_pNode, + *Node(*kv.second, m_pMemory, DeepClone()).m_pNode); + } else + m_pNode->copy_contents(rhs); +} + +Node::Node(const Node &rhs, YAML::UnshareSubtrees) + : m_isValid(rhs.m_isValid), + m_invalidKey(), + m_pMemory(rhs.m_pMemory), + m_pNode(&m_pMemory->create_node()) { + if (!m_isValid) + return; + m_pNode->copy_contents(*rhs.m_pNode); + this->UnshareSubtrees(); +} + +struct HashNodeId { + std::size_t operator()(void *nodeId) const noexcept { + return (std::size_t)nodeId >> 5; + } +}; + +typedef std::unordered_set SubtreeSeen; + +#define YAML_CPP_UNSHARE_BY_MODIFY 1 + +void Node::OwnData() const { + if (!m_isValid) + return; + auto p = m_pNode; + (m_pNode = &m_pMemory->create_node())->copy_contents(*p); +} + +#if YAML_CPP_UNSHARE_BY_MODIFY +static void unshare(detail::node &n, SubtreeSeen &seen, + detail::shared_memory_holder const &memory) { + n.modify([&seen, &memory](detail::node *s) { + if (seen.insert(s->id()).second) { + unshare(*s, seen, memory); + return s; + } else + return &Node(*s, memory, DeepClone()).node(); + }); +} +#else +static void unshare(Node &n, SubtreeSeen &seen) { + Node copied; + bool modified = false; + if (n.IsSequence()) { + for (Node sub : n) + if (!seen.insert(sub.id()).second) { + modified = true; + copied.push_back(Node(sub, DeepClone())); + } else { + unshare(sub, seen); + copied.push_back(sub); + } + } else if (n.IsMap()) + for (auto const &kv : n) { + Node sub = kv.second; + if (!seen.insert(sub.id()).second) { + modified = true; + copied[kv.first] = Node(sub, DeepClone()); + } else { + unshare(sub, seen); + copied[kv.first] = sub; + } + } + // TODO: ModifyValues or other faster impl + if (modified) + n = copied; +} +#endif + +void Node::UnshareSubtrees() { + SubtreeSeen seen; +#if YAML_CPP_UNSHARE_BY_MODIFY + unshare(*m_pNode, seen, m_pMemory); +#else + unshare(*this, seen); +#endif +} + +void Node::ModifyValues(modify_values const &f) { + if (!m_isValid) + return; + m_pNode->modify(f); +} + +void Node::ModifyKeyValues(modify_key_values const &f) { + if (!m_isValid) + return; + m_pNode->map_modify(f); +} + } // namespace YAML diff --git a/src/node_data.cpp b/src/node_data.cpp index 8f5422a..40922f4 100644 --- a/src/node_data.cpp +++ b/src/node_data.cpp @@ -71,6 +71,48 @@ void node_data::set_type(NodeType::value type) { } } +void node_data::seq_modify(modify_values const& f) { + assert(m_type == NodeType::Sequence); + node_seq::iterator i = m_sequence.begin(), e = m_sequence.end(), b = i, o = i; + for (; i != e; ++i) { + node* m = f(*i); + if (m) { + *o = m; + ++o; + } + } + m_sequence.resize(o - b); +} + +void node_data::map_modify(modify_values const& f) { + assert(m_type == NodeType::Map); + node_map::iterator i = m_map.begin(), e = m_map.end(), b = i, o = i; + for (; i != e; ++i) { + node* m = f(i->second); + if (m) { + o->first = i->first; + o->second = m; + ++o; + } + } + m_map.resize(o - b); +} + +void node_data::map_modify(modify_key_values const& f) { + assert(m_type == NodeType::Map); + node_map::iterator i = m_map.begin(), e = m_map.end(), b = i, o = i; + for (; i != e; ++i) { + node* k = i->first; + node* m = f(k->type() == NodeType::Scalar ? &k->scalar() : 0, i->second); + if (m) { + o->first = k; + o->second = m; + ++o; + } + } + m_map.resize(o - b); +} + void node_data::set_tag(const std::string& tag) { m_tag = tag; } void node_data::set_style(EmitterStyle::value style) { m_style = style; } diff --git a/test/node/node_test.cpp b/test/node/node_test.cpp index d4367c5..3ae4aa0 100644 --- a/test/node/node_test.cpp +++ b/test/node/node_test.cpp @@ -486,6 +486,121 @@ TEST(NodeTest, SimpleAlias) { EXPECT_EQ(2, node.size()); } +TEST(NodeTest, SimpleAliasShallowClone) { + Node cloned; + { + Node node; + node["foo"] = "value"; + node["bar"] = node["foo"]; + EXPECT_EQ("value", node["foo"].as()); + EXPECT_EQ("value", node["bar"].as()); + EXPECT_EQ(node["bar"], node["foo"]); + EXPECT_EQ(node["bar"].id(), node["foo"].id()); + EXPECT_EQ(2, node.size()); + EXPECT_EQ(node["foo"].Scalar(), "value"); + cloned = Node(node, ShallowClone()); + EXPECT_EQ(2, cloned.size()); + EXPECT_NE(cloned.id(), node.id()); + } + EXPECT_EQ(cloned["foo"].Scalar(), "value"); + EXPECT_EQ(cloned["bar"].id(), cloned["foo"].id()); + cloned["foo"] = Node(cloned["foo"], ShallowClone()); + EXPECT_EQ(cloned["foo"].Scalar(), "value"); + EXPECT_NE(cloned["bar"].id(), cloned["foo"].id()); +} + + +TEST(NodeTest, SimpleAliasUnshare) { + Node node; + node["foo"] = "value"; + node["bar"] = node["foo"]; + node.UnshareSubtrees(); + EXPECT_NE(node["bar"].id(), node["foo"].id()); + EXPECT_FALSE(node["bar"] == node["foo"]); +} + +TEST(NodeTest, SimpleAliasRemoveKey) { + Node node; + node["foo"] = "value"; + node["bar"] = node["foo"]; + EXPECT_EQ(node.size(), 2); + EXPECT_EQ(node["foo"].Scalar(), "value"); + node.ModifyKeyValues([](std::string const* key, detail::node *n) { return key && *key == "foo" ? 0 : n; }); + EXPECT_EQ(node.size(), 1); + EXPECT_FALSE(node["foo"].IsDefined()); +} + +TEST(NodeTest, SimpleAliasUnshareDeep) { + Node node; + node["foo"] = "value"; + node["bar"] = "2"; + node["deep"]["baralias"] = node["bar"]; + EXPECT_EQ(node["deep"]["baralias"].id(), node["bar"].id()); + EXPECT_EQ(node["deep"]["baralias"].Scalar(), "2"); + node.UnshareSubtrees(); + EXPECT_NE(node["deep"]["baralias"].id(), node["bar"].id()); + EXPECT_EQ(node["deep"]["baralias"].Scalar(), "2"); +} + +TEST(NodeTest, SimpleAliasUnshareDeeper) { + Node node; + node["foo"] = "value"; + node["bar"]["two"] = "2"; + node["deep"]["baralias"] = node["bar"]; + EXPECT_EQ(node["deep"]["baralias"].id(), node["bar"].id()); + EXPECT_EQ(node["foo"].Scalar(), "value"); + EXPECT_EQ(node["deep"]["baralias"]["two"].Scalar(), "2"); + EXPECT_EQ(node["deep"]["baralias"]["two"].id(), node["bar"]["two"].id()); + node.UnshareSubtrees(); + EXPECT_NE(node["deep"]["baralias"].id(), node["bar"].id()); + EXPECT_EQ(node["foo"].Scalar(), "value"); + EXPECT_NE(node["deep"]["baralias"]["two"].id(), node["bar"]["two"].id()); + EXPECT_EQ(node["deep"]["baralias"]["two"].Scalar(), "2"); +} + +TEST(NodeTest, SimpleAliasDeeperClone) { + Node node; + node["foo"] = "value"; + node["bar"]["two"] = "2"; + node["deep"]["baralias"] = node["bar"]; + EXPECT_EQ(node["deep"]["baralias"].id(), node["bar"].id()); + EXPECT_EQ(node["foo"].Scalar(), "value"); + EXPECT_EQ(node["deep"]["baralias"]["two"].Scalar(), "2"); + EXPECT_EQ(node["deep"]["baralias"]["two"].id(), node["bar"]["two"].id()); + Node tree(node, DeepClone()); + EXPECT_EQ(tree["foo"].Scalar(), "value"); + EXPECT_NE(tree["deep"]["baralias"].id(), tree["bar"].id()); + EXPECT_NE(tree["deep"]["baralias"]["two"].id(), tree["bar"]["two"].id()); + EXPECT_EQ(tree["deep"]["baralias"]["two"].Scalar(), "2"); +} + +TEST(NodeTest, SimpleAliasSeqClone) { + Node node; + node["foo"] = "value"; + node["bar"]["two"] = "2"; + node["deep"]["baralias"].push_back(node["bar"]); + node["deep"]["baralias"].push_back(node["bar"]); + EXPECT_TRUE(node["deep"]["baralias"].IsSequence()); + EXPECT_EQ(node["deep"]["baralias"].size(), 2); + EXPECT_EQ(node["foo"].Scalar(), "value"); + Node tree(node, DeepClone()); + EXPECT_TRUE(tree["deep"]["baralias"].IsSequence()); + EXPECT_EQ(tree["deep"]["baralias"].size(), 2); + EXPECT_EQ(tree["foo"].Scalar(), "value"); + for (auto const& s : tree["deep"]["baralias"]) + EXPECT_EQ(s["two"].Scalar(), "2"); +} + +TEST(NodeTest, SimpleAliasUnshareCtor) { + Node node; + node["foo"] = "value"; + node["bar"] = node["foo"]; + Node tree(node, UnshareSubtrees()); + EXPECT_NE(tree["bar"].id(), tree["foo"].id()); + EXPECT_FALSE(tree["bar"] == tree["foo"]); + EXPECT_TRUE(tree["bar"] == node["foo"] || tree["foo"] == node["foo"]); +} + TEST(NodeTest, AliasAsKey) { Node node; node["foo"] = "value";