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.
This commit is contained in:
parent
c73ee34704
commit
8d5327d0aa
@ -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 <typename T>
|
||||
inline node& node_data::convert_to_node(const T& rhs,
|
||||
shared_memory_holder pMemory) {
|
||||
|
||||
@ -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<node_ref>();
|
||||
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 <typename Key>
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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<node_data>(*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 <typename Key>
|
||||
|
||||
@ -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 <typename Key, typename Value>
|
||||
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); }
|
||||
|
||||
26
include/yaml-cpp/node/modify.h
Normal file
26
include/yaml-cpp/node/modify.h
Normal file
@ -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 <functional>
|
||||
|
||||
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<detail::node *(detail::node *)> modify_values;
|
||||
/// as above but first arg is pointer to string key (or 0 if non-scalar)
|
||||
typedef std::function<detail::node *(std::string const*, detail::node *)> modify_key_values;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@ -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 <typename T>
|
||||
Node& operator=(const T& rhs);
|
||||
@ -96,6 +141,8 @@ class YAML_CPP_API Node {
|
||||
template <typename T>
|
||||
void push_back(const T& rhs);
|
||||
void push_back(const Node& rhs);
|
||||
// \pre IsSequence
|
||||
void seq_push_back(const Node& rhs);
|
||||
|
||||
// indexing
|
||||
template <typename Key>
|
||||
@ -112,12 +159,13 @@ class YAML_CPP_API Node {
|
||||
// map
|
||||
template <typename Key, typename Value>
|
||||
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 <typename T>
|
||||
struct convert;
|
||||
}
|
||||
} // namespace YAML
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<YAML::Node> {
|
||||
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
|
||||
|
||||
164
src/node.cpp
164
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 <cassert>
|
||||
#include <unordered_set>
|
||||
|
||||
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<void *, HashNodeId> 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
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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<std::string>());
|
||||
EXPECT_EQ("value", node["bar"].as<std::string>());
|
||||
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";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user