This commit is contained in:
Jonathan Graehl 2025-01-28 04:52:50 +00:00 committed by GitHub
commit d31f6b82bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 505 additions and 6 deletions

View File

@ -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) {

View File

@ -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>

View File

@ -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();

View File

@ -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>

View File

@ -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;
@ -193,6 +193,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)
@ -373,12 +375,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); }

View 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

View File

@ -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

View File

@ -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

View File

@ -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; }

View File

@ -545,6 +545,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";