From 452e19b24f95b64cca63e5eda2500d3b8b5a6d18 Mon Sep 17 00:00:00 2001 From: Joao Paulo Magalhaes Date: Tue, 19 Mar 2024 16:45:14 +0100 Subject: [PATCH 01/15] fix erroneous use of RYML_USE_ASSERT: which caused asserts to be enabled in release builds thanks to @jdrouhard re #389 --- src/c4/yml/common.hpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/c4/yml/common.hpp b/src/c4/yml/common.hpp index f74de9dd8..2b5ce02d9 100644 --- a/src/c4/yml/common.hpp +++ b/src/c4/yml/common.hpp @@ -14,9 +14,11 @@ #if RYML_USE_ASSERT # define RYML_ASSERT(cond) RYML_CHECK(cond) # define RYML_ASSERT_MSG(cond, msg) RYML_CHECK_MSG(cond, msg) +# define _RYML_CB_ASSERT(cb, cond) _RYML_CB_CHECK((cb), (cond)) #else # define RYML_ASSERT(cond) # define RYML_ASSERT_MSG(cond, msg) +# define _RYML_CB_ASSERT(cb, cond) #endif @@ -210,11 +212,6 @@ do \ (cb).m_error(msg, sizeof(msg), c4::yml::Location(__FILE__, 0, __LINE__, 0), (cb).m_user_data); \ } \ } while(0) -#ifdef RYML_USE_ASSERT -#define _RYML_CB_ASSERT(cb, cond) _RYML_CB_CHECK((cb), (cond)) -#else -#define _RYML_CB_ASSERT(cb, cond) do {} while(0) -#endif #define _RYML_CB_ALLOC_HINT(cb, T, num, hint) (T*) (cb).m_allocate((num) * sizeof(T), (hint), (cb).m_user_data) #define _RYML_CB_ALLOC(cb, T, num) _RYML_CB_ALLOC_HINT((cb), (T), (num), nullptr) #define _RYML_CB_FREE(cb, buf, T, num) \ From 15ca52dbd26484ad78516d4e8a3ae38e7d7d79ab Mon Sep 17 00:00:00 2001 From: Joao Paulo Magalhaes Date: Tue, 19 Mar 2024 16:59:35 +0100 Subject: [PATCH 02/15] node: disable noexcept when assertions are enabled re #389 --- src/c4/yml/common.hpp | 2 + src/c4/yml/node.hpp | 210 +++++++++++++++++++++--------------------- 2 files changed, 107 insertions(+), 105 deletions(-) diff --git a/src/c4/yml/common.hpp b/src/c4/yml/common.hpp index 2b5ce02d9..968146c4d 100644 --- a/src/c4/yml/common.hpp +++ b/src/c4/yml/common.hpp @@ -15,10 +15,12 @@ # define RYML_ASSERT(cond) RYML_CHECK(cond) # define RYML_ASSERT_MSG(cond, msg) RYML_CHECK_MSG(cond, msg) # define _RYML_CB_ASSERT(cb, cond) _RYML_CB_CHECK((cb), (cond)) +# define RYML_NOEXCEPT #else # define RYML_ASSERT(cond) # define RYML_ASSERT_MSG(cond, msg) # define _RYML_CB_ASSERT(cb, cond) +# define RYML_NOEXCEPT noexcept #endif diff --git a/src/c4/yml/node.hpp b/src/c4/yml/node.hpp index d97aa7f42..a56943f71 100644 --- a/src/c4/yml/node.hpp +++ b/src/c4/yml/node.hpp @@ -170,29 +170,29 @@ struct RoNodeMethods /** @{ */ /** returns the data or null when the id is NONE */ - C4_ALWAYS_INLINE C4_PURE NodeData const* get() const noexcept { RYML_ASSERT(tree_ != nullptr); return tree_->get(id_); } + C4_ALWAYS_INLINE C4_PURE NodeData const* get() const RYML_NOEXCEPT { RYML_ASSERT(tree_ != nullptr); return tree_->get(id_); } /** returns the data or null when the id is NONE */ template - C4_ALWAYS_INLINE C4_PURE auto get() noexcept -> _C4_IF_MUTABLE(NodeData*) { RYML_ASSERT(tree_ != nullptr); return tree__->get(id__); } + C4_ALWAYS_INLINE C4_PURE auto get() RYML_NOEXCEPT -> _C4_IF_MUTABLE(NodeData*) { RYML_ASSERT(tree_ != nullptr); return tree__->get(id__); } - C4_ALWAYS_INLINE C4_PURE NodeType type() const noexcept { _C4RV(); return tree_->type(id_); } - C4_ALWAYS_INLINE C4_PURE const char* type_str() const noexcept { return tree_->type_str(id_); } + C4_ALWAYS_INLINE C4_PURE NodeType type() const RYML_NOEXCEPT { _C4RV(); return tree_->type(id_); } + C4_ALWAYS_INLINE C4_PURE const char* type_str() const RYML_NOEXCEPT { _C4RV(); return tree_->type_str(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr key() const noexcept { _C4RV(); return tree_->key(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr key_tag() const noexcept { _C4RV(); return tree_->key_tag(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr key_ref() const noexcept { _C4RV(); return tree_->key_ref(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr key_anchor() const noexcept { _C4RV(); return tree_->key_anchor(id_); } + C4_ALWAYS_INLINE C4_PURE csubstr key() const RYML_NOEXCEPT { _C4RV(); return tree_->key(id_); } + C4_ALWAYS_INLINE C4_PURE csubstr key_tag() const RYML_NOEXCEPT { _C4RV(); return tree_->key_tag(id_); } + C4_ALWAYS_INLINE C4_PURE csubstr key_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->key_ref(id_); } + C4_ALWAYS_INLINE C4_PURE csubstr key_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->key_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr val() const noexcept { _C4RV(); return tree_->val(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr val_tag() const noexcept { _C4RV(); return tree_->val_tag(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr val_ref() const noexcept { _C4RV(); return tree_->val_ref(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr val_anchor() const noexcept { _C4RV(); return tree_->val_anchor(id_); } + C4_ALWAYS_INLINE C4_PURE csubstr val() const RYML_NOEXCEPT { _C4RV(); return tree_->val(id_); } + C4_ALWAYS_INLINE C4_PURE csubstr val_tag() const RYML_NOEXCEPT { _C4RV(); return tree_->val_tag(id_); } + C4_ALWAYS_INLINE C4_PURE csubstr val_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->val_ref(id_); } + C4_ALWAYS_INLINE C4_PURE csubstr val_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->val_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE NodeScalar const& keysc() const noexcept { _C4RV(); return tree_->keysc(id_); } - C4_ALWAYS_INLINE C4_PURE NodeScalar const& valsc() const noexcept { _C4RV(); return tree_->valsc(id_); } + C4_ALWAYS_INLINE C4_PURE NodeScalar const& keysc() const RYML_NOEXCEPT { _C4RV(); return tree_->keysc(id_); } + C4_ALWAYS_INLINE C4_PURE NodeScalar const& valsc() const RYML_NOEXCEPT { _C4RV(); return tree_->valsc(id_); } - C4_ALWAYS_INLINE C4_PURE bool key_is_null() const noexcept { _C4RV(); return tree_->key_is_null(id_); } - C4_ALWAYS_INLINE C4_PURE bool val_is_null() const noexcept { _C4RV(); return tree_->val_is_null(id_); } + C4_ALWAYS_INLINE C4_PURE bool key_is_null() const RYML_NOEXCEPT { _C4RV(); return tree_->key_is_null(id_); } + C4_ALWAYS_INLINE C4_PURE bool val_is_null() const RYML_NOEXCEPT { _C4RV(); return tree_->val_is_null(id_); } /** @} */ @@ -201,33 +201,33 @@ struct RoNodeMethods /** @name node property predicates */ /** @{ */ - C4_ALWAYS_INLINE C4_PURE bool empty() const noexcept { _C4RV(); return tree_->empty(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_stream() const noexcept { _C4RV(); return tree_->is_stream(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_doc() const noexcept { _C4RV(); return tree_->is_doc(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_container() const noexcept { _C4RV(); return tree_->is_container(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_map() const noexcept { _C4RV(); return tree_->is_map(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_seq() const noexcept { _C4RV(); return tree_->is_seq(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_val() const noexcept { _C4RV(); return tree_->has_val(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_key() const noexcept { _C4RV(); return tree_->has_key(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_val() const noexcept { _C4RV(); return tree_->is_val(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_keyval() const noexcept { _C4RV(); return tree_->is_keyval(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_key_tag() const noexcept { _C4RV(); return tree_->has_key_tag(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_val_tag() const noexcept { _C4RV(); return tree_->has_val_tag(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_key_anchor() const noexcept { _C4RV(); return tree_->has_key_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_key_anchor() const noexcept { _C4RV(); return tree_->is_key_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_val_anchor() const noexcept { _C4RV(); return tree_->has_val_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_val_anchor() const noexcept { _C4RV(); return tree_->is_val_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_anchor() const noexcept { _C4RV(); return tree_->has_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_anchor() const noexcept { _C4RV(); return tree_->is_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_key_ref() const noexcept { _C4RV(); return tree_->is_key_ref(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_val_ref() const noexcept { _C4RV(); return tree_->is_val_ref(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_ref() const noexcept { _C4RV(); return tree_->is_ref(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_anchor_or_ref() const noexcept { _C4RV(); return tree_->is_anchor_or_ref(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_key_quoted() const noexcept { _C4RV(); return tree_->is_key_quoted(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_val_quoted() const noexcept { _C4RV(); return tree_->is_val_quoted(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_quoted() const noexcept { _C4RV(); return tree_->is_quoted(id_); } - C4_ALWAYS_INLINE C4_PURE bool parent_is_seq() const noexcept { _C4RV(); return tree_->parent_is_seq(id_); } - C4_ALWAYS_INLINE C4_PURE bool parent_is_map() const noexcept { _C4RV(); return tree_->parent_is_map(id_); } + C4_ALWAYS_INLINE C4_PURE bool empty() const RYML_NOEXCEPT { _C4RV(); return tree_->empty(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_stream() const RYML_NOEXCEPT { _C4RV(); return tree_->is_stream(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_doc() const RYML_NOEXCEPT { _C4RV(); return tree_->is_doc(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_container() const RYML_NOEXCEPT { _C4RV(); return tree_->is_container(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_map() const RYML_NOEXCEPT { _C4RV(); return tree_->is_map(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_seq() const RYML_NOEXCEPT { _C4RV(); return tree_->is_seq(id_); } + C4_ALWAYS_INLINE C4_PURE bool has_val() const RYML_NOEXCEPT { _C4RV(); return tree_->has_val(id_); } + C4_ALWAYS_INLINE C4_PURE bool has_key() const RYML_NOEXCEPT { _C4RV(); return tree_->has_key(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_val() const RYML_NOEXCEPT { _C4RV(); return tree_->is_val(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_keyval() const RYML_NOEXCEPT { _C4RV(); return tree_->is_keyval(id_); } + C4_ALWAYS_INLINE C4_PURE bool has_key_tag() const RYML_NOEXCEPT { _C4RV(); return tree_->has_key_tag(id_); } + C4_ALWAYS_INLINE C4_PURE bool has_val_tag() const RYML_NOEXCEPT { _C4RV(); return tree_->has_val_tag(id_); } + C4_ALWAYS_INLINE C4_PURE bool has_key_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->has_key_anchor(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_key_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->is_key_anchor(id_); } + C4_ALWAYS_INLINE C4_PURE bool has_val_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->has_val_anchor(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_val_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->is_val_anchor(id_); } + C4_ALWAYS_INLINE C4_PURE bool has_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->has_anchor(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->is_anchor(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_key_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->is_key_ref(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_val_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->is_val_ref(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->is_ref(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_anchor_or_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->is_anchor_or_ref(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_key_quoted() const RYML_NOEXCEPT { _C4RV(); return tree_->is_key_quoted(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_val_quoted() const RYML_NOEXCEPT { _C4RV(); return tree_->is_val_quoted(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_quoted() const RYML_NOEXCEPT { _C4RV(); return tree_->is_quoted(id_); } + C4_ALWAYS_INLINE C4_PURE bool parent_is_seq() const RYML_NOEXCEPT { _C4RV(); return tree_->parent_is_seq(id_); } + C4_ALWAYS_INLINE C4_PURE bool parent_is_map() const RYML_NOEXCEPT { _C4RV(); return tree_->parent_is_map(id_); } /** @} */ @@ -236,19 +236,19 @@ struct RoNodeMethods /** @name hierarchy predicates */ /** @{ */ - C4_ALWAYS_INLINE C4_PURE bool is_root() const noexcept { _C4RV(); return tree_->is_root(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_parent() const noexcept { _C4RV(); return tree_->has_parent(id_); } + C4_ALWAYS_INLINE C4_PURE bool is_root() const RYML_NOEXCEPT { _C4RV(); return tree_->is_root(id_); } + C4_ALWAYS_INLINE C4_PURE bool has_parent() const RYML_NOEXCEPT { _C4RV(); return tree_->has_parent(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_child(ConstImpl const& ch) const noexcept { _C4RV(); return tree_->has_child(id_, ch.m_id); } - C4_ALWAYS_INLINE C4_PURE bool has_child(csubstr name) const noexcept { _C4RV(); return tree_->has_child(id_, name); } - C4_ALWAYS_INLINE C4_PURE bool has_children() const noexcept { _C4RV(); return tree_->has_children(id_); } + C4_ALWAYS_INLINE C4_PURE bool has_child(ConstImpl const& ch) const RYML_NOEXCEPT { _C4RV(); return tree_->has_child(id_, ch.m_id); } + C4_ALWAYS_INLINE C4_PURE bool has_child(csubstr name) const RYML_NOEXCEPT { _C4RV(); return tree_->has_child(id_, name); } + C4_ALWAYS_INLINE C4_PURE bool has_children() const RYML_NOEXCEPT { _C4RV(); return tree_->has_children(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_sibling(ConstImpl const& n) const noexcept { _C4RV(); return tree_->has_sibling(id_, n.m_id); } - C4_ALWAYS_INLINE C4_PURE bool has_sibling(csubstr name) const noexcept { _C4RV(); return tree_->has_sibling(id_, name); } + C4_ALWAYS_INLINE C4_PURE bool has_sibling(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); return tree_->has_sibling(id_, n.m_id); } + C4_ALWAYS_INLINE C4_PURE bool has_sibling(csubstr name) const RYML_NOEXCEPT { _C4RV(); return tree_->has_sibling(id_, name); } /** counts with this */ - C4_ALWAYS_INLINE C4_PURE bool has_siblings() const noexcept { _C4RV(); return tree_->has_siblings(id_); } + C4_ALWAYS_INLINE C4_PURE bool has_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->has_siblings(id_); } /** does not count with this */ - C4_ALWAYS_INLINE C4_PURE bool has_other_siblings() const noexcept { _C4RV(); return tree_->has_other_siblings(id_); } + C4_ALWAYS_INLINE C4_PURE bool has_other_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->has_other_siblings(id_); } /** @} */ @@ -259,68 +259,68 @@ struct RoNodeMethods template - C4_ALWAYS_INLINE C4_PURE auto doc(size_t num) noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->doc(num)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl doc(size_t num) const noexcept { _C4RV(); return {tree_, tree_->doc(num)}; } + C4_ALWAYS_INLINE C4_PURE auto doc(size_t num) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->doc(num)}; } + C4_ALWAYS_INLINE C4_PURE ConstImpl doc(size_t num) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->doc(num)}; } template - C4_ALWAYS_INLINE C4_PURE auto parent() noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->parent(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl parent() const noexcept { _C4RV(); return {tree_, tree_->parent(id_)}; } + C4_ALWAYS_INLINE C4_PURE auto parent() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->parent(id__)}; } + C4_ALWAYS_INLINE C4_PURE ConstImpl parent() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->parent(id_)}; } /** O(#num_children) */ - C4_ALWAYS_INLINE C4_PURE size_t child_pos(ConstImpl const& n) const noexcept { _C4RV(); return tree_->child_pos(id_, n.m_id); } - C4_ALWAYS_INLINE C4_PURE size_t num_children() const noexcept { _C4RV(); return tree_->num_children(id_); } + C4_ALWAYS_INLINE C4_PURE size_t child_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); return tree_->child_pos(id_, n.m_id); } + C4_ALWAYS_INLINE C4_PURE size_t num_children() const RYML_NOEXCEPT { _C4RV(); return tree_->num_children(id_); } template - C4_ALWAYS_INLINE C4_PURE auto first_child() noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->first_child(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl first_child() const noexcept { _C4RV(); return {tree_, tree_->first_child(id_)}; } + C4_ALWAYS_INLINE C4_PURE auto first_child() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->first_child(id__)}; } + C4_ALWAYS_INLINE C4_PURE ConstImpl first_child() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->first_child(id_)}; } template - C4_ALWAYS_INLINE C4_PURE auto last_child() noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->last_child(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl last_child () const noexcept { _C4RV(); return {tree_, tree_->last_child (id_)}; } + C4_ALWAYS_INLINE C4_PURE auto last_child() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->last_child(id__)}; } + C4_ALWAYS_INLINE C4_PURE ConstImpl last_child () const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->last_child (id_)}; } template - C4_ALWAYS_INLINE C4_PURE auto child(size_t pos) noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->child(id__, pos)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl child(size_t pos) const noexcept { _C4RV(); return {tree_, tree_->child(id_, pos)}; } + C4_ALWAYS_INLINE C4_PURE auto child(size_t pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->child(id__, pos)}; } + C4_ALWAYS_INLINE C4_PURE ConstImpl child(size_t pos) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->child(id_, pos)}; } template - C4_ALWAYS_INLINE C4_PURE auto find_child(csubstr name) noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->find_child(id__, name)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl find_child(csubstr name) const noexcept { _C4RV(); return {tree_, tree_->find_child(id_, name)}; } + C4_ALWAYS_INLINE C4_PURE auto find_child(csubstr name) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->find_child(id__, name)}; } + C4_ALWAYS_INLINE C4_PURE ConstImpl find_child(csubstr name) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->find_child(id_, name)}; } /** O(#num_siblings) */ - C4_ALWAYS_INLINE C4_PURE size_t num_siblings() const noexcept { _C4RV(); return tree_->num_siblings(id_); } - C4_ALWAYS_INLINE C4_PURE size_t num_other_siblings() const noexcept { _C4RV(); return tree_->num_other_siblings(id_); } - C4_ALWAYS_INLINE C4_PURE size_t sibling_pos(ConstImpl const& n) const noexcept { _C4RV(); return tree_->child_pos(tree_->parent(id_), n.m_id); } + C4_ALWAYS_INLINE C4_PURE size_t num_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->num_siblings(id_); } + C4_ALWAYS_INLINE C4_PURE size_t num_other_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->num_other_siblings(id_); } + C4_ALWAYS_INLINE C4_PURE size_t sibling_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); return tree_->child_pos(tree_->parent(id_), n.m_id); } template - C4_ALWAYS_INLINE C4_PURE auto prev_sibling() noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->prev_sibling(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl prev_sibling() const noexcept { _C4RV(); return {tree_, tree_->prev_sibling(id_)}; } + C4_ALWAYS_INLINE C4_PURE auto prev_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->prev_sibling(id__)}; } + C4_ALWAYS_INLINE C4_PURE ConstImpl prev_sibling() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->prev_sibling(id_)}; } template - C4_ALWAYS_INLINE C4_PURE auto next_sibling() noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->next_sibling(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl next_sibling() const noexcept { _C4RV(); return {tree_, tree_->next_sibling(id_)}; } + C4_ALWAYS_INLINE C4_PURE auto next_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->next_sibling(id__)}; } + C4_ALWAYS_INLINE C4_PURE ConstImpl next_sibling() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->next_sibling(id_)}; } template - C4_ALWAYS_INLINE C4_PURE auto first_sibling() noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->first_sibling(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl first_sibling() const noexcept { _C4RV(); return {tree_, tree_->first_sibling(id_)}; } + C4_ALWAYS_INLINE C4_PURE auto first_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->first_sibling(id__)}; } + C4_ALWAYS_INLINE C4_PURE ConstImpl first_sibling() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->first_sibling(id_)}; } template - C4_ALWAYS_INLINE C4_PURE auto last_sibling() noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->last_sibling(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl last_sibling () const noexcept { _C4RV(); return {tree_, tree_->last_sibling(id_)}; } + C4_ALWAYS_INLINE C4_PURE auto last_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->last_sibling(id__)}; } + C4_ALWAYS_INLINE C4_PURE ConstImpl last_sibling () const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->last_sibling(id_)}; } template - C4_ALWAYS_INLINE C4_PURE auto sibling(size_t pos) noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->sibling(id__, pos)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl sibling(size_t pos) const noexcept { _C4RV(); return {tree_, tree_->sibling(id_, pos)}; } + C4_ALWAYS_INLINE C4_PURE auto sibling(size_t pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->sibling(id__, pos)}; } + C4_ALWAYS_INLINE C4_PURE ConstImpl sibling(size_t pos) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->sibling(id_, pos)}; } template - C4_ALWAYS_INLINE C4_PURE auto find_sibling(csubstr name) noexcept -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->find_sibling(id__, name)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl find_sibling(csubstr name) const noexcept { _C4RV(); return {tree_, tree_->find_sibling(id_, name)}; } + C4_ALWAYS_INLINE C4_PURE auto find_sibling(csubstr name) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->find_sibling(id__, name)}; } + C4_ALWAYS_INLINE C4_PURE ConstImpl find_sibling(csubstr name) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->find_sibling(id_, name)}; } /** O(num_children) */ - C4_ALWAYS_INLINE C4_PURE ConstImpl operator[] (csubstr k) const noexcept + C4_ALWAYS_INLINE C4_PURE ConstImpl operator[] (csubstr k) const RYML_NOEXCEPT { _C4RV(); size_t ch = tree_->find_child(id_, k); @@ -329,7 +329,7 @@ struct RoNodeMethods } /** Find child by key. O(num_children). returns a seed node if no such child is found. */ template - C4_ALWAYS_INLINE C4_PURE auto operator[] (csubstr k) noexcept -> _C4_IF_MUTABLE(Impl) + C4_ALWAYS_INLINE C4_PURE auto operator[] (csubstr k) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); size_t ch = tree__->find_child(id__, k); @@ -337,7 +337,7 @@ struct RoNodeMethods } /** O(num_children) */ - C4_ALWAYS_INLINE C4_PURE ConstImpl operator[] (size_t pos) const noexcept + C4_ALWAYS_INLINE C4_PURE ConstImpl operator[] (size_t pos) const RYML_NOEXCEPT { _C4RV(); size_t ch = tree_->child(id_, pos); @@ -347,7 +347,7 @@ struct RoNodeMethods /** Find child by position. O(pos). returns a seed node if no such child is found. */ template - C4_ALWAYS_INLINE C4_PURE auto operator[] (size_t pos) noexcept -> _C4_IF_MUTABLE(Impl) + C4_ALWAYS_INLINE C4_PURE auto operator[] (size_t pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); size_t ch = tree__->child(id__, pos); @@ -460,26 +460,26 @@ struct RoNodeMethods using const_children_view = detail::children_view_; template - C4_ALWAYS_INLINE C4_PURE auto begin() noexcept -> _C4_IF_MUTABLE(iterator) { _C4RV(); return iterator(tree__, tree__->first_child(id__)); } - C4_ALWAYS_INLINE C4_PURE const_iterator begin() const noexcept { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } - C4_ALWAYS_INLINE C4_PURE const_iterator cbegin() const noexcept { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } + C4_ALWAYS_INLINE C4_PURE auto begin() RYML_NOEXCEPT -> _C4_IF_MUTABLE(iterator) { _C4RV(); return iterator(tree__, tree__->first_child(id__)); } + C4_ALWAYS_INLINE C4_PURE const_iterator begin() const RYML_NOEXCEPT { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } + C4_ALWAYS_INLINE C4_PURE const_iterator cbegin() const RYML_NOEXCEPT { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } template - C4_ALWAYS_INLINE C4_PURE auto end() noexcept -> _C4_IF_MUTABLE(iterator) { _C4RV(); return iterator(tree__, NONE); } - C4_ALWAYS_INLINE C4_PURE const_iterator end() const noexcept { _C4RV(); return const_iterator(tree_, NONE); } - C4_ALWAYS_INLINE C4_PURE const_iterator cend() const noexcept { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } + C4_ALWAYS_INLINE C4_PURE auto end() RYML_NOEXCEPT -> _C4_IF_MUTABLE(iterator) { _C4RV(); return iterator(tree__, NONE); } + C4_ALWAYS_INLINE C4_PURE const_iterator end() const RYML_NOEXCEPT { _C4RV(); return const_iterator(tree_, NONE); } + C4_ALWAYS_INLINE C4_PURE const_iterator cend() const RYML_NOEXCEPT { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } /** get an iterable view over children */ template - C4_ALWAYS_INLINE C4_PURE auto children() noexcept -> _C4_IF_MUTABLE(children_view) { _C4RV(); return children_view(begin(), end()); } + C4_ALWAYS_INLINE C4_PURE auto children() RYML_NOEXCEPT -> _C4_IF_MUTABLE(children_view) { _C4RV(); return children_view(begin(), end()); } /** get an iterable view over children */ - C4_ALWAYS_INLINE C4_PURE const_children_view children() const noexcept { _C4RV(); return const_children_view(begin(), end()); } + C4_ALWAYS_INLINE C4_PURE const_children_view children() const RYML_NOEXCEPT { _C4RV(); return const_children_view(begin(), end()); } /** get an iterable view over children */ - C4_ALWAYS_INLINE C4_PURE const_children_view cchildren() const noexcept { _C4RV(); return const_children_view(begin(), end()); } + C4_ALWAYS_INLINE C4_PURE const_children_view cchildren() const RYML_NOEXCEPT { _C4RV(); return const_children_view(begin(), end()); } /** get an iterable view over all siblings (including the calling node) */ template - C4_ALWAYS_INLINE C4_PURE auto siblings() noexcept -> _C4_IF_MUTABLE(children_view) + C4_ALWAYS_INLINE C4_PURE auto siblings() RYML_NOEXCEPT -> _C4_IF_MUTABLE(children_view) { _C4RV(); NodeData const *nd = tree__->get(id__); @@ -489,7 +489,7 @@ struct RoNodeMethods children_view(end(), end()); } /** get an iterable view over all siblings (including the calling node) */ - C4_ALWAYS_INLINE C4_PURE const_children_view siblings() const noexcept + C4_ALWAYS_INLINE C4_PURE const_children_view siblings() const RYML_NOEXCEPT { _C4RV(); NodeData const *nd = tree_->get(id_); @@ -499,17 +499,17 @@ struct RoNodeMethods const_children_view(end(), end()); } /** get an iterable view over all siblings (including the calling node) */ - C4_ALWAYS_INLINE C4_PURE const_children_view csiblings() const noexcept { return siblings(); } + C4_ALWAYS_INLINE C4_PURE const_children_view csiblings() const RYML_NOEXCEPT { return siblings(); } /** visit every child node calling fn(node) */ template - C4_ALWAYS_INLINE bool visit(Visitor fn, size_t indentation_level=0, bool skip_root=true) const noexcept + C4_ALWAYS_INLINE bool visit(Visitor fn, size_t indentation_level=0, bool skip_root=true) const RYML_NOEXCEPT { return detail::_visit(*(ConstImpl const*)this, fn, indentation_level, skip_root); } /** visit every child node calling fn(node) */ template - auto visit(Visitor fn, size_t indentation_level=0, bool skip_root=true) noexcept + auto visit(Visitor fn, size_t indentation_level=0, bool skip_root=true) RYML_NOEXCEPT -> _C4_IF_MUTABLE(bool) { return detail::_visit(*(Impl*)this, fn, indentation_level, skip_root); @@ -517,13 +517,13 @@ struct RoNodeMethods /** visit every child node calling fn(node, level) */ template - C4_ALWAYS_INLINE bool visit_stacked(Visitor fn, size_t indentation_level=0, bool skip_root=true) const noexcept + C4_ALWAYS_INLINE bool visit_stacked(Visitor fn, size_t indentation_level=0, bool skip_root=true) const RYML_NOEXCEPT { return detail::_visit_stacked(*(ConstImpl const*)this, fn, indentation_level, skip_root); } /** visit every child node calling fn(node, level) */ template - auto visit_stacked(Visitor fn, size_t indentation_level=0, bool skip_root=true) noexcept + auto visit_stacked(Visitor fn, size_t indentation_level=0, bool skip_root=true) RYML_NOEXCEPT -> _C4_IF_MUTABLE(bool) { return detail::_visit_stacked(*(Impl*)this, fn, indentation_level, skip_root); @@ -626,14 +626,14 @@ class RYML_EXPORT ConstNodeRef : public detail::RoNodeMethodsoperator==(that); } + C4_ALWAYS_INLINE C4_PURE bool operator== (ConstNodeRef const& that) const RYML_NOEXCEPT { RYML_ASSERT(that.m_tree == m_tree); return m_id == that.m_id; } + C4_ALWAYS_INLINE C4_PURE bool operator!= (ConstNodeRef const& that) const RYML_NOEXCEPT { RYML_ASSERT(that.m_tree == m_tree); return ! this->operator==(that); } C4_ALWAYS_INLINE C4_PURE bool operator== (std::nullptr_t) const noexcept { return m_tree == nullptr || m_id == NONE; } C4_ALWAYS_INLINE C4_PURE bool operator!= (std::nullptr_t) const noexcept { return ! this->operator== (nullptr); } - C4_ALWAYS_INLINE C4_PURE bool operator== (csubstr val) const noexcept { RYML_ASSERT(has_val()); return m_tree->val(m_id) == val; } - C4_ALWAYS_INLINE C4_PURE bool operator!= (csubstr val) const noexcept { RYML_ASSERT(has_val()); return m_tree->val(m_id) != val; } + C4_ALWAYS_INLINE C4_PURE bool operator== (csubstr val) const RYML_NOEXCEPT { RYML_ASSERT(has_val()); return m_tree->val(m_id) == val; } + C4_ALWAYS_INLINE C4_PURE bool operator!= (csubstr val) const RYML_NOEXCEPT { RYML_ASSERT(has_val()); return m_tree->val(m_id) != val; } /** @} */ From 2f83b56197bd71981ab351f12c43ead0e42bf2f3 Mon Sep 17 00:00:00 2001 From: Joao Paulo Magalhaes Date: Tue, 19 Mar 2024 17:28:39 +0100 Subject: [PATCH 03/15] node: do not use C4_PURE when assertions are enabled re #389 --- src/c4/yml/node.hpp | 256 ++++++++++++++++++++++++-------------------- 1 file changed, 140 insertions(+), 116 deletions(-) diff --git a/src/c4/yml/node.hpp b/src/c4/yml/node.hpp index a56943f71..dcb814015 100644 --- a/src/c4/yml/node.hpp +++ b/src/c4/yml/node.hpp @@ -170,29 +170,29 @@ struct RoNodeMethods /** @{ */ /** returns the data or null when the id is NONE */ - C4_ALWAYS_INLINE C4_PURE NodeData const* get() const RYML_NOEXCEPT { RYML_ASSERT(tree_ != nullptr); return tree_->get(id_); } + C4_ALWAYS_INLINE NodeData const* get() const RYML_NOEXCEPT { RYML_ASSERT(tree_ != nullptr); return tree_->get(id_); } /** returns the data or null when the id is NONE */ template - C4_ALWAYS_INLINE C4_PURE auto get() RYML_NOEXCEPT -> _C4_IF_MUTABLE(NodeData*) { RYML_ASSERT(tree_ != nullptr); return tree__->get(id__); } + C4_ALWAYS_INLINE auto get() RYML_NOEXCEPT -> _C4_IF_MUTABLE(NodeData*) { RYML_ASSERT(tree_ != nullptr); return tree__->get(id__); } - C4_ALWAYS_INLINE C4_PURE NodeType type() const RYML_NOEXCEPT { _C4RV(); return tree_->type(id_); } - C4_ALWAYS_INLINE C4_PURE const char* type_str() const RYML_NOEXCEPT { _C4RV(); return tree_->type_str(id_); } + C4_ALWAYS_INLINE NodeType type() const RYML_NOEXCEPT { _C4RV(); return tree_->type(id_); } + C4_ALWAYS_INLINE const char* type_str() const RYML_NOEXCEPT { _C4RV(); return tree_->type_str(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr key() const RYML_NOEXCEPT { _C4RV(); return tree_->key(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr key_tag() const RYML_NOEXCEPT { _C4RV(); return tree_->key_tag(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr key_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->key_ref(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr key_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->key_anchor(id_); } + C4_ALWAYS_INLINE csubstr key() const RYML_NOEXCEPT { _C4RV(); return tree_->key(id_); } + C4_ALWAYS_INLINE csubstr key_tag() const RYML_NOEXCEPT { _C4RV(); return tree_->key_tag(id_); } + C4_ALWAYS_INLINE csubstr key_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->key_ref(id_); } + C4_ALWAYS_INLINE csubstr key_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->key_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr val() const RYML_NOEXCEPT { _C4RV(); return tree_->val(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr val_tag() const RYML_NOEXCEPT { _C4RV(); return tree_->val_tag(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr val_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->val_ref(id_); } - C4_ALWAYS_INLINE C4_PURE csubstr val_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->val_anchor(id_); } + C4_ALWAYS_INLINE csubstr val() const RYML_NOEXCEPT { _C4RV(); return tree_->val(id_); } + C4_ALWAYS_INLINE csubstr val_tag() const RYML_NOEXCEPT { _C4RV(); return tree_->val_tag(id_); } + C4_ALWAYS_INLINE csubstr val_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->val_ref(id_); } + C4_ALWAYS_INLINE csubstr val_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->val_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE NodeScalar const& keysc() const RYML_NOEXCEPT { _C4RV(); return tree_->keysc(id_); } - C4_ALWAYS_INLINE C4_PURE NodeScalar const& valsc() const RYML_NOEXCEPT { _C4RV(); return tree_->valsc(id_); } + C4_ALWAYS_INLINE NodeScalar const& keysc() const RYML_NOEXCEPT { _C4RV(); return tree_->keysc(id_); } + C4_ALWAYS_INLINE NodeScalar const& valsc() const RYML_NOEXCEPT { _C4RV(); return tree_->valsc(id_); } - C4_ALWAYS_INLINE C4_PURE bool key_is_null() const RYML_NOEXCEPT { _C4RV(); return tree_->key_is_null(id_); } - C4_ALWAYS_INLINE C4_PURE bool val_is_null() const RYML_NOEXCEPT { _C4RV(); return tree_->val_is_null(id_); } + C4_ALWAYS_INLINE bool key_is_null() const RYML_NOEXCEPT { _C4RV(); return tree_->key_is_null(id_); } + C4_ALWAYS_INLINE bool val_is_null() const RYML_NOEXCEPT { _C4RV(); return tree_->val_is_null(id_); } /** @} */ @@ -201,33 +201,33 @@ struct RoNodeMethods /** @name node property predicates */ /** @{ */ - C4_ALWAYS_INLINE C4_PURE bool empty() const RYML_NOEXCEPT { _C4RV(); return tree_->empty(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_stream() const RYML_NOEXCEPT { _C4RV(); return tree_->is_stream(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_doc() const RYML_NOEXCEPT { _C4RV(); return tree_->is_doc(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_container() const RYML_NOEXCEPT { _C4RV(); return tree_->is_container(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_map() const RYML_NOEXCEPT { _C4RV(); return tree_->is_map(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_seq() const RYML_NOEXCEPT { _C4RV(); return tree_->is_seq(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_val() const RYML_NOEXCEPT { _C4RV(); return tree_->has_val(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_key() const RYML_NOEXCEPT { _C4RV(); return tree_->has_key(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_val() const RYML_NOEXCEPT { _C4RV(); return tree_->is_val(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_keyval() const RYML_NOEXCEPT { _C4RV(); return tree_->is_keyval(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_key_tag() const RYML_NOEXCEPT { _C4RV(); return tree_->has_key_tag(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_val_tag() const RYML_NOEXCEPT { _C4RV(); return tree_->has_val_tag(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_key_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->has_key_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_key_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->is_key_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_val_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->has_val_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_val_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->is_val_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->has_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->is_anchor(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_key_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->is_key_ref(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_val_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->is_val_ref(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->is_ref(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_anchor_or_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->is_anchor_or_ref(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_key_quoted() const RYML_NOEXCEPT { _C4RV(); return tree_->is_key_quoted(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_val_quoted() const RYML_NOEXCEPT { _C4RV(); return tree_->is_val_quoted(id_); } - C4_ALWAYS_INLINE C4_PURE bool is_quoted() const RYML_NOEXCEPT { _C4RV(); return tree_->is_quoted(id_); } - C4_ALWAYS_INLINE C4_PURE bool parent_is_seq() const RYML_NOEXCEPT { _C4RV(); return tree_->parent_is_seq(id_); } - C4_ALWAYS_INLINE C4_PURE bool parent_is_map() const RYML_NOEXCEPT { _C4RV(); return tree_->parent_is_map(id_); } + C4_ALWAYS_INLINE bool empty() const RYML_NOEXCEPT { _C4RV(); return tree_->empty(id_); } + C4_ALWAYS_INLINE bool is_stream() const RYML_NOEXCEPT { _C4RV(); return tree_->is_stream(id_); } + C4_ALWAYS_INLINE bool is_doc() const RYML_NOEXCEPT { _C4RV(); return tree_->is_doc(id_); } + C4_ALWAYS_INLINE bool is_container() const RYML_NOEXCEPT { _C4RV(); return tree_->is_container(id_); } + C4_ALWAYS_INLINE bool is_map() const RYML_NOEXCEPT { _C4RV(); return tree_->is_map(id_); } + C4_ALWAYS_INLINE bool is_seq() const RYML_NOEXCEPT { _C4RV(); return tree_->is_seq(id_); } + C4_ALWAYS_INLINE bool has_val() const RYML_NOEXCEPT { _C4RV(); return tree_->has_val(id_); } + C4_ALWAYS_INLINE bool has_key() const RYML_NOEXCEPT { _C4RV(); return tree_->has_key(id_); } + C4_ALWAYS_INLINE bool is_val() const RYML_NOEXCEPT { _C4RV(); return tree_->is_val(id_); } + C4_ALWAYS_INLINE bool is_keyval() const RYML_NOEXCEPT { _C4RV(); return tree_->is_keyval(id_); } + C4_ALWAYS_INLINE bool has_key_tag() const RYML_NOEXCEPT { _C4RV(); return tree_->has_key_tag(id_); } + C4_ALWAYS_INLINE bool has_val_tag() const RYML_NOEXCEPT { _C4RV(); return tree_->has_val_tag(id_); } + C4_ALWAYS_INLINE bool has_key_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->has_key_anchor(id_); } + C4_ALWAYS_INLINE bool is_key_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->is_key_anchor(id_); } + C4_ALWAYS_INLINE bool has_val_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->has_val_anchor(id_); } + C4_ALWAYS_INLINE bool is_val_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->is_val_anchor(id_); } + C4_ALWAYS_INLINE bool has_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->has_anchor(id_); } + C4_ALWAYS_INLINE bool is_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->is_anchor(id_); } + C4_ALWAYS_INLINE bool is_key_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->is_key_ref(id_); } + C4_ALWAYS_INLINE bool is_val_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->is_val_ref(id_); } + C4_ALWAYS_INLINE bool is_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->is_ref(id_); } + C4_ALWAYS_INLINE bool is_anchor_or_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->is_anchor_or_ref(id_); } + C4_ALWAYS_INLINE bool is_key_quoted() const RYML_NOEXCEPT { _C4RV(); return tree_->is_key_quoted(id_); } + C4_ALWAYS_INLINE bool is_val_quoted() const RYML_NOEXCEPT { _C4RV(); return tree_->is_val_quoted(id_); } + C4_ALWAYS_INLINE bool is_quoted() const RYML_NOEXCEPT { _C4RV(); return tree_->is_quoted(id_); } + C4_ALWAYS_INLINE bool parent_is_seq() const RYML_NOEXCEPT { _C4RV(); return tree_->parent_is_seq(id_); } + C4_ALWAYS_INLINE bool parent_is_map() const RYML_NOEXCEPT { _C4RV(); return tree_->parent_is_map(id_); } /** @} */ @@ -236,19 +236,19 @@ struct RoNodeMethods /** @name hierarchy predicates */ /** @{ */ - C4_ALWAYS_INLINE C4_PURE bool is_root() const RYML_NOEXCEPT { _C4RV(); return tree_->is_root(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_parent() const RYML_NOEXCEPT { _C4RV(); return tree_->has_parent(id_); } + C4_ALWAYS_INLINE bool is_root() const RYML_NOEXCEPT { _C4RV(); return tree_->is_root(id_); } + C4_ALWAYS_INLINE bool has_parent() const RYML_NOEXCEPT { _C4RV(); return tree_->has_parent(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_child(ConstImpl const& ch) const RYML_NOEXCEPT { _C4RV(); return tree_->has_child(id_, ch.m_id); } - C4_ALWAYS_INLINE C4_PURE bool has_child(csubstr name) const RYML_NOEXCEPT { _C4RV(); return tree_->has_child(id_, name); } - C4_ALWAYS_INLINE C4_PURE bool has_children() const RYML_NOEXCEPT { _C4RV(); return tree_->has_children(id_); } + C4_ALWAYS_INLINE bool has_child(ConstImpl const& ch) const RYML_NOEXCEPT { _C4RV(); return tree_->has_child(id_, ch.m_id); } + C4_ALWAYS_INLINE bool has_child(csubstr name) const RYML_NOEXCEPT { _C4RV(); return tree_->has_child(id_, name); } + C4_ALWAYS_INLINE bool has_children() const RYML_NOEXCEPT { _C4RV(); return tree_->has_children(id_); } - C4_ALWAYS_INLINE C4_PURE bool has_sibling(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); return tree_->has_sibling(id_, n.m_id); } - C4_ALWAYS_INLINE C4_PURE bool has_sibling(csubstr name) const RYML_NOEXCEPT { _C4RV(); return tree_->has_sibling(id_, name); } + C4_ALWAYS_INLINE bool has_sibling(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); return tree_->has_sibling(id_, n.m_id); } + C4_ALWAYS_INLINE bool has_sibling(csubstr name) const RYML_NOEXCEPT { _C4RV(); return tree_->has_sibling(id_, name); } /** counts with this */ - C4_ALWAYS_INLINE C4_PURE bool has_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->has_siblings(id_); } + C4_ALWAYS_INLINE bool has_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->has_siblings(id_); } /** does not count with this */ - C4_ALWAYS_INLINE C4_PURE bool has_other_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->has_other_siblings(id_); } + C4_ALWAYS_INLINE bool has_other_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->has_other_siblings(id_); } /** @} */ @@ -259,99 +259,123 @@ struct RoNodeMethods template - C4_ALWAYS_INLINE C4_PURE auto doc(size_t num) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->doc(num)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl doc(size_t num) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->doc(num)}; } + C4_ALWAYS_INLINE auto doc(size_t num) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->doc(num)}; } + C4_ALWAYS_INLINE ConstImpl doc(size_t num) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->doc(num)}; } template - C4_ALWAYS_INLINE C4_PURE auto parent() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->parent(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl parent() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->parent(id_)}; } + C4_ALWAYS_INLINE auto parent() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->parent(id__)}; } + C4_ALWAYS_INLINE ConstImpl parent() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->parent(id_)}; } /** O(#num_children) */ - C4_ALWAYS_INLINE C4_PURE size_t child_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); return tree_->child_pos(id_, n.m_id); } - C4_ALWAYS_INLINE C4_PURE size_t num_children() const RYML_NOEXCEPT { _C4RV(); return tree_->num_children(id_); } + C4_ALWAYS_INLINE size_t child_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); return tree_->child_pos(id_, n.m_id); } + C4_ALWAYS_INLINE size_t num_children() const RYML_NOEXCEPT { _C4RV(); return tree_->num_children(id_); } template - C4_ALWAYS_INLINE C4_PURE auto first_child() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->first_child(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl first_child() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->first_child(id_)}; } + C4_ALWAYS_INLINE auto first_child() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->first_child(id__)}; } + C4_ALWAYS_INLINE ConstImpl first_child() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->first_child(id_)}; } template - C4_ALWAYS_INLINE C4_PURE auto last_child() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->last_child(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl last_child () const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->last_child (id_)}; } + C4_ALWAYS_INLINE auto last_child() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->last_child(id__)}; } + C4_ALWAYS_INLINE ConstImpl last_child () const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->last_child (id_)}; } template - C4_ALWAYS_INLINE C4_PURE auto child(size_t pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->child(id__, pos)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl child(size_t pos) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->child(id_, pos)}; } + C4_ALWAYS_INLINE auto child(size_t pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->child(id__, pos)}; } + C4_ALWAYS_INLINE ConstImpl child(size_t pos) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->child(id_, pos)}; } template - C4_ALWAYS_INLINE C4_PURE auto find_child(csubstr name) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->find_child(id__, name)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl find_child(csubstr name) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->find_child(id_, name)}; } + C4_ALWAYS_INLINE auto find_child(csubstr name) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->find_child(id__, name)}; } + C4_ALWAYS_INLINE ConstImpl find_child(csubstr name) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->find_child(id_, name)}; } /** O(#num_siblings) */ - C4_ALWAYS_INLINE C4_PURE size_t num_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->num_siblings(id_); } - C4_ALWAYS_INLINE C4_PURE size_t num_other_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->num_other_siblings(id_); } - C4_ALWAYS_INLINE C4_PURE size_t sibling_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); return tree_->child_pos(tree_->parent(id_), n.m_id); } + C4_ALWAYS_INLINE size_t num_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->num_siblings(id_); } + C4_ALWAYS_INLINE size_t num_other_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->num_other_siblings(id_); } + C4_ALWAYS_INLINE size_t sibling_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); return tree_->child_pos(tree_->parent(id_), n.m_id); } template - C4_ALWAYS_INLINE C4_PURE auto prev_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->prev_sibling(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl prev_sibling() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->prev_sibling(id_)}; } + C4_ALWAYS_INLINE auto prev_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->prev_sibling(id__)}; } + C4_ALWAYS_INLINE ConstImpl prev_sibling() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->prev_sibling(id_)}; } template - C4_ALWAYS_INLINE C4_PURE auto next_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->next_sibling(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl next_sibling() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->next_sibling(id_)}; } + C4_ALWAYS_INLINE auto next_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->next_sibling(id__)}; } + C4_ALWAYS_INLINE ConstImpl next_sibling() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->next_sibling(id_)}; } template - C4_ALWAYS_INLINE C4_PURE auto first_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->first_sibling(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl first_sibling() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->first_sibling(id_)}; } + C4_ALWAYS_INLINE auto first_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->first_sibling(id__)}; } + C4_ALWAYS_INLINE ConstImpl first_sibling() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->first_sibling(id_)}; } template - C4_ALWAYS_INLINE C4_PURE auto last_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->last_sibling(id__)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl last_sibling () const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->last_sibling(id_)}; } + C4_ALWAYS_INLINE auto last_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->last_sibling(id__)}; } + C4_ALWAYS_INLINE ConstImpl last_sibling () const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->last_sibling(id_)}; } template - C4_ALWAYS_INLINE C4_PURE auto sibling(size_t pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->sibling(id__, pos)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl sibling(size_t pos) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->sibling(id_, pos)}; } + C4_ALWAYS_INLINE auto sibling(size_t pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->sibling(id__, pos)}; } + C4_ALWAYS_INLINE ConstImpl sibling(size_t pos) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->sibling(id_, pos)}; } template - C4_ALWAYS_INLINE C4_PURE auto find_sibling(csubstr name) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->find_sibling(id__, name)}; } - C4_ALWAYS_INLINE C4_PURE ConstImpl find_sibling(csubstr name) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->find_sibling(id_, name)}; } - - - /** O(num_children) */ - C4_ALWAYS_INLINE C4_PURE ConstImpl operator[] (csubstr k) const RYML_NOEXCEPT + C4_ALWAYS_INLINE auto find_sibling(csubstr name) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->find_sibling(id__, name)}; } + C4_ALWAYS_INLINE ConstImpl find_sibling(csubstr name) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->find_sibling(id_, name)}; } + + /** Find child by key; complexity is O(num_children). Returns the + * requested node, or a seed node if no such child is found. Using + * the returned seed node to write into the tree is UB. It is the + * responsibility of the caller to verify that the returned node + * is valid before using subsequently using it. + * + * @see https://github.com/biojppm/rapidyaml/issues/389 */ + template + C4_ALWAYS_INLINE auto operator[] (csubstr key) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); - size_t ch = tree_->find_child(id_, k); - _RYML_CB_ASSERT(tree_->m_callbacks, ch != NONE); - return {tree_, ch}; + size_t ch = tree__->find_child(id__, key); + return ch != NONE ? Impl(tree__, ch) : Impl(tree__, id__, key); } - /** Find child by key. O(num_children). returns a seed node if no such child is found. */ + + /** Find child by position; complexity is O(pos). Returns the + * requested node, or a seed node if no such child is found. Using + * the returned seed node to write into the tree is UB. It is the + * responsibility of the caller to verify that the returned node + * is valid before using subsequently using it. + * + * @see https://github.com/biojppm/rapidyaml/issues/389 */ template - C4_ALWAYS_INLINE C4_PURE auto operator[] (csubstr k) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) + C4_ALWAYS_INLINE auto operator[] (size_t pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); - size_t ch = tree__->find_child(id__, k); - return ch != NONE ? Impl(tree__, ch) : NodeRef(tree__, id__, k); + size_t ch = tree__->child(id__, pos); + return ch != NONE ? Impl(tree__, ch) : Impl(tree__, id__, pos); } - /** O(num_children) */ - C4_ALWAYS_INLINE C4_PURE ConstImpl operator[] (size_t pos) const RYML_NOEXCEPT + /** Find a child by key; complexity is O(num_children). Behaves + * similar to the non-const overload, but further asserts that the + * returned node is valid. This assertion is performed only if + * @ref RYML_USE_ASSERT is set to true. As with the non-const + * overload, it is UB to use the return value if it is not valid. + * + * @see https://github.com/biojppm/rapidyaml/issues/389 */ + C4_ALWAYS_INLINE ConstImpl operator[] (csubstr key) const RYML_NOEXCEPT { _C4RV(); - size_t ch = tree_->child(id_, pos); + size_t ch = tree_->find_child(id_, key); _RYML_CB_ASSERT(tree_->m_callbacks, ch != NONE); return {tree_, ch}; } - /** Find child by position. O(pos). returns a seed node if no such child is found. */ - template - C4_ALWAYS_INLINE C4_PURE auto operator[] (size_t pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) + /** Find a child by position; complexity is O(pos). Behaves + * similar to the non-const overload, but further asserts that the + * returned node is valid. This assertion is performed only if + * @ref RYML_USE_ASSERT is set to true. As with the non-const + * overload, it is UB to use the return value if it is not valid. + * + * @see https://github.com/biojppm/rapidyaml/issues/389 */ + C4_ALWAYS_INLINE ConstImpl operator[] (size_t pos) const RYML_NOEXCEPT { _C4RV(); - size_t ch = tree__->child(id__, pos); - return ch != NONE ? Impl(tree__, ch) : NodeRef(tree__, id__, pos); + size_t ch = tree_->child(id_, pos); + _RYML_CB_ASSERT(tree_->m_callbacks, ch != NONE); + return {tree_, ch}; } /** @} */ @@ -460,26 +484,26 @@ struct RoNodeMethods using const_children_view = detail::children_view_; template - C4_ALWAYS_INLINE C4_PURE auto begin() RYML_NOEXCEPT -> _C4_IF_MUTABLE(iterator) { _C4RV(); return iterator(tree__, tree__->first_child(id__)); } - C4_ALWAYS_INLINE C4_PURE const_iterator begin() const RYML_NOEXCEPT { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } - C4_ALWAYS_INLINE C4_PURE const_iterator cbegin() const RYML_NOEXCEPT { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } + C4_ALWAYS_INLINE auto begin() RYML_NOEXCEPT -> _C4_IF_MUTABLE(iterator) { _C4RV(); return iterator(tree__, tree__->first_child(id__)); } + C4_ALWAYS_INLINE const_iterator begin() const RYML_NOEXCEPT { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } + C4_ALWAYS_INLINE const_iterator cbegin() const RYML_NOEXCEPT { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } template - C4_ALWAYS_INLINE C4_PURE auto end() RYML_NOEXCEPT -> _C4_IF_MUTABLE(iterator) { _C4RV(); return iterator(tree__, NONE); } - C4_ALWAYS_INLINE C4_PURE const_iterator end() const RYML_NOEXCEPT { _C4RV(); return const_iterator(tree_, NONE); } - C4_ALWAYS_INLINE C4_PURE const_iterator cend() const RYML_NOEXCEPT { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } + C4_ALWAYS_INLINE auto end() RYML_NOEXCEPT -> _C4_IF_MUTABLE(iterator) { _C4RV(); return iterator(tree__, NONE); } + C4_ALWAYS_INLINE const_iterator end() const RYML_NOEXCEPT { _C4RV(); return const_iterator(tree_, NONE); } + C4_ALWAYS_INLINE const_iterator cend() const RYML_NOEXCEPT { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } /** get an iterable view over children */ template - C4_ALWAYS_INLINE C4_PURE auto children() RYML_NOEXCEPT -> _C4_IF_MUTABLE(children_view) { _C4RV(); return children_view(begin(), end()); } + C4_ALWAYS_INLINE auto children() RYML_NOEXCEPT -> _C4_IF_MUTABLE(children_view) { _C4RV(); return children_view(begin(), end()); } /** get an iterable view over children */ - C4_ALWAYS_INLINE C4_PURE const_children_view children() const RYML_NOEXCEPT { _C4RV(); return const_children_view(begin(), end()); } + C4_ALWAYS_INLINE const_children_view children() const RYML_NOEXCEPT { _C4RV(); return const_children_view(begin(), end()); } /** get an iterable view over children */ - C4_ALWAYS_INLINE C4_PURE const_children_view cchildren() const RYML_NOEXCEPT { _C4RV(); return const_children_view(begin(), end()); } + C4_ALWAYS_INLINE const_children_view cchildren() const RYML_NOEXCEPT { _C4RV(); return const_children_view(begin(), end()); } /** get an iterable view over all siblings (including the calling node) */ template - C4_ALWAYS_INLINE C4_PURE auto siblings() RYML_NOEXCEPT -> _C4_IF_MUTABLE(children_view) + C4_ALWAYS_INLINE auto siblings() RYML_NOEXCEPT -> _C4_IF_MUTABLE(children_view) { _C4RV(); NodeData const *nd = tree__->get(id__); @@ -489,7 +513,7 @@ struct RoNodeMethods children_view(end(), end()); } /** get an iterable view over all siblings (including the calling node) */ - C4_ALWAYS_INLINE C4_PURE const_children_view siblings() const RYML_NOEXCEPT + C4_ALWAYS_INLINE const_children_view siblings() const RYML_NOEXCEPT { _C4RV(); NodeData const *nd = tree_->get(id_); @@ -499,7 +523,7 @@ struct RoNodeMethods const_children_view(end(), end()); } /** get an iterable view over all siblings (including the calling node) */ - C4_ALWAYS_INLINE C4_PURE const_children_view csiblings() const RYML_NOEXCEPT { return siblings(); } + C4_ALWAYS_INLINE const_children_view csiblings() const RYML_NOEXCEPT { return siblings(); } /** visit every child node calling fn(node) */ template @@ -626,14 +650,14 @@ class RYML_EXPORT ConstNodeRef : public detail::RoNodeMethodsoperator==(that); } + C4_ALWAYS_INLINE bool operator== (ConstNodeRef const& that) const RYML_NOEXCEPT { RYML_ASSERT(that.m_tree == m_tree); return m_id == that.m_id; } + C4_ALWAYS_INLINE bool operator!= (ConstNodeRef const& that) const RYML_NOEXCEPT { RYML_ASSERT(that.m_tree == m_tree); return ! this->operator==(that); } C4_ALWAYS_INLINE C4_PURE bool operator== (std::nullptr_t) const noexcept { return m_tree == nullptr || m_id == NONE; } C4_ALWAYS_INLINE C4_PURE bool operator!= (std::nullptr_t) const noexcept { return ! this->operator== (nullptr); } - C4_ALWAYS_INLINE C4_PURE bool operator== (csubstr val) const RYML_NOEXCEPT { RYML_ASSERT(has_val()); return m_tree->val(m_id) == val; } - C4_ALWAYS_INLINE C4_PURE bool operator!= (csubstr val) const RYML_NOEXCEPT { RYML_ASSERT(has_val()); return m_tree->val(m_id) != val; } + C4_ALWAYS_INLINE bool operator== (csubstr val) const RYML_NOEXCEPT { RYML_ASSERT(has_val()); return m_tree->val(m_id) == val; } + C4_ALWAYS_INLINE bool operator!= (csubstr val) const RYML_NOEXCEPT { RYML_ASSERT(has_val()); return m_tree->val(m_id) != val; } /** @} */ From 9d86ec38f65cfaaa3565bfa57780d57447d7e81a Mon Sep 17 00:00:00 2001 From: Joao Paulo Magalhaes Date: Tue, 19 Mar 2024 17:31:56 +0100 Subject: [PATCH 04/15] use of RYML_DEBUG_BREAK is now opt-in via RYML_DBG re #362 --- src/c4/yml/common.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/c4/yml/common.hpp b/src/c4/yml/common.hpp index 968146c4d..01d3d1f33 100644 --- a/src/c4/yml/common.hpp +++ b/src/c4/yml/common.hpp @@ -24,7 +24,7 @@ #endif -#if defined(NDEBUG) || defined(C4_NO_DEBUG_BREAK) +#if defined(NDEBUG) || defined(C4_NO_DEBUG_BREAK) || (!defined(RYML_DBG)) # define RYML_DEBUG_BREAK() #else # define RYML_DEBUG_BREAK() \ From c3624a5a15cead1d4aa3f08ebaff592672093d02 Mon Sep 17 00:00:00 2001 From: Joao Paulo Magalhaes Date: Thu, 28 Mar 2024 00:09:25 +0000 Subject: [PATCH 05/15] (Const)NodeRef: report errors through the closest callback --- CMakeLists.txt | 14 +- changelog/current.md | 41 +++++- samples/quickstart.cpp | 135 ++++++++++++------ src/c4/yml/common.cpp | 7 + src/c4/yml/common.hpp | 8 +- src/c4/yml/node.hpp | 288 +++++++++++++++++++++++++------------- src/c4/yml/parse.cpp | 2 +- src/c4/yml/parse.hpp | 6 +- src/c4/yml/std/vector.hpp | 2 +- src/c4/yml/tree.cpp | 27 ++-- src/c4/yml/tree.hpp | 32 ++--- test/callbacks_tester.hpp | 2 +- test/test_case.cpp | 14 ++ test/test_case.hpp | 2 + test/test_noderef.cpp | 269 +++++++++++++++++++++++++++++++++++ test/test_tree.cpp | 203 +++++++++++++++++++++++++++ 16 files changed, 870 insertions(+), 182 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 11c52e064..888a45d15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,11 @@ c4_project(VERSION 0.5.0 STANDALONE option(RYML_WITH_TAB_TOKENS "Enable parsing of tabs after ':' and '-'. This is costly and disabled by default." OFF) option(RYML_DEFAULT_CALLBACKS "Enable ryml's default implementation of callbacks: allocate(), free(), error()" ON) -option(RYML_BUILD_TOOLS "build tools" OFF) +if(RYML_DEFAULT_CALLBACKS) + option(RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS "Throw exceptions instead of calling abort in the default error handler provided by ryml" OFF) +endif() +option(RYML_USE_ASSERT "Enable assertions regardless of build type. Default is only when NDEBUG is not defined (which is in release builds). This causes a slowdown of the code." OFF) +option(RYML_BUILD_TOOLS "Build tools" OFF) option(RYML_BUILD_API "Enable API generation (python, etc)" OFF) option(RYML_DBG "Enable (very verbose) ryml debug prints." OFF) @@ -66,12 +70,20 @@ endif() if(NOT RYML_DEFAULT_CALLBACKS) target_compile_definitions(ryml PRIVATE RYML_NO_DEFAULT_CALLBACKS) +else() + if(RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS) + target_compile_definitions(ryml PRIVATE RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS) + endif() endif() if(RYML_DBG) target_compile_definitions(ryml PRIVATE RYML_DBG) endif() +if(RYML_USE_ASSERT) + target_compile_definitions(ryml PUBLIC RYML_USE_ASSERT=1) +endif() + #------------------------------------------------------- diff --git a/changelog/current.md b/changelog/current.md index d464d10d9..89f848474 100644 --- a/changelog/current.md +++ b/changelog/current.md @@ -1,7 +1,46 @@ ### Fixes -- Fix [#390](https://github.com/biojppm/rapidyaml/pull/390) - `csubstr::first_real_span()` failed on number with one digit in the exponent. +- Fix major error handling problem reported in [#389](https://github.com/biojppm/rapidyaml/issues/389) ([PR#411](https://github.com/biojppm/rapidyaml/pull/411)): + - The `NodeRef` and `ConstNodeRef` classes had many methods marked `noexcept` that were doing assertions which could throw exceptions, causing an abort instead of a throw whenever the assertion called an exception-throwing error callback. + - Also, this problem was compounded by exceptions being enabled in every build type -- despite the intention to have them only in debug builds. There was a problem in the preprocessor code to enable assertions which led to assertions being enabled in release builds even when `RYML_USE_ASSERT` was defined to 0. Thanks to @jdrouhard for reporting this. + - Although the code is and was extensively tested, the testing was addressing mostly the happy path. In the fix, I added tests to ensure that the error behavior is as intended. + - Together with this changeset, a major revision was carried out of the asserting/checking status of each function in the node classes. In most cases, assertions were added to cases that were missing them. So **beware** - user code that was invalid will now assert or error out. Also, assertions and checks are now directed as much as possible to the callbacks of the closest scope, ie if a tree has custom callbacks, errors should go through those callbacks. + - Also, the intended assertion behavior is now in place: *no assertions in release builds*. **Beware** as well - user code which was relying on this will now silently succeed and return garbage in release builds. See the next points, which may help: + - Added new methods to the node class: + ```c++ + /** Distinguish between a valid seed vs a valid non-seed ref. */ + bool readable() const { return valid() && !is_seed(); } + + /** Get a child by name, with error checking; complexity is + * O(num_children). + * + * Behaves as operator[](csubstr) const, but always raises an + * error (even when RYML_USE_ASSERT is set to false) when the + * returned node does not exist, or when this node is not + * readable, or when it is not a map. This behaviour is similar to + * std::vector::at(), but the error consists in calling the error + * callback instead of directly raising an exception. */ + ConstNodeRef ConstNodeRef::at(csubstr key) const; + ConstNodeRef NodeRef::at(csubstr key) const; + + /** Get a child by position, with error checking; complexity is + * O(pos). + * + * Behaves as operator[](size_t) const, but always raises an error + * (even when RYML_USE_ASSERT is set to false) when the returned + * node does not exist, or when this node is not readable, or when + * it is not a map. This behaviour is similar to + * std::vector::at(), but the error consists in calling the error + * callback instead of directly raising an exception. */ + ConstNodeRef ConstNodeRef::at(size_t pos) const; + ConstNodeRef NodeRef::at(size_t pos) const; + ``` + - Added macros and respective cmake options to control error handling: + - `RYML_USE_ASSERT` - enable assertions regardless of build type. This is disabled by default. + - `RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS` - defines the same macro, which will make the default error handler provided by ryml throw exceptions instead of calling `std::abort()`. This is disabled by default. + - Also, `RYML_DEBUG_BREAK()` is now enabled only if `RYML_DBG` is defined, as reported in [#362](https://github.com/biojppm/rapidyaml/issues/362). +- Fix [#390](https://github.com/biojppm/rapidyaml/pull/390) - `csubstr::first_real_span()` failed on scientific numbers with one digit in the exponent. - Fix [#361](https://github.com/biojppm/rapidyaml/pull/361) - parse error on map scalars containing `:` and starting on the next line: ```yaml --- diff --git a/samples/quickstart.cpp b/samples/quickstart.cpp index ce036bb8b..0701b2387 100644 --- a/samples/quickstart.cpp +++ b/samples/quickstart.cpp @@ -167,21 +167,31 @@ struct CheckPredicate { /** a brief tour over most features */ void sample_quick_overview() { - // Parse YAML code in place, potentially mutating the buffer. - // It is also possible to: - // - parse a read-only buffer using parse_in_arena() - // - reuse an existing tree (advised) - // - reuse an existing parser (advised) + // Parse YAML code in place, potentially mutating the buffer: char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}"; ryml::Tree tree = ryml::parse_in_place(yml_buf); - // Note: it will always be significantly faster to use mutable - // buffers and reuse tree+parser. + // The resulting tree contains only views to the parsed string. If + // the string was parsed in place, then the string must outlive + // the tree! This works in this case because `yml_buf` and `tree` + // live on the same scope, so have the same lifetime. + + // It is also possible to: + // + // - parse a read-only buffer using parse_in_arena(). This + // copies the YAML buffer to the tree's arena, and spares the + // headache of the string's lifetime. + // + // - reuse an existing tree (advised) + // + // - reuse an existing parser (advised) + // + // Note: it will always be significantly faster to parse in place + // and reuse tree+parser. // // Below you will find samples that show how to achieve reuse; but // please note that for brevity and clarity, many of the examples - // here are parsing immutable buffers, and not reusing tree or - // parser. + // here are parsing in the arena, and not reusing tree or parser. //------------------------------------------------------------------ @@ -199,10 +209,11 @@ void sample_quick_overview() // The node API is a lightweight abstraction sitting on top of the // index API, but offering a much more convenient interaction: - ryml::ConstNodeRef root = tree.rootref(); + ryml::ConstNodeRef root = tree.rootref(); // a const node reference ryml::ConstNodeRef bar = tree["bar"]; CHECK(root.is_map()); CHECK(bar.is_seq()); + // A node ref is a lightweight handle to the tree and associated id: CHECK(root.tree() == &tree); // a node ref points at its tree, WITHOUT refcount CHECK(root.id() == root_id); // a node ref's id is the index of the node @@ -211,10 +222,9 @@ void sample_quick_overview() // The node API translates very cleanly to the index API, so most // of the code examples below are using the node API. - // One significant point of the node API is that it holds a raw - // pointer to the tree. Care must be taken to ensure the lifetimes - // match, so that a node will never access the tree after the tree - // went out of scope. + // WARNING. A node ref holds a raw pointer to the tree. Care must + // be taken to ensure the lifetimes match, so that a node will + // never access the tree after the goes out of scope. //------------------------------------------------------------------ @@ -222,16 +232,16 @@ void sample_quick_overview() // ConstNodeRef::operator[] does a lookup, is O(num_children[node]). CHECK(tree["foo"].is_keyval()); - CHECK(tree["foo"].key() == "foo"); - CHECK(tree["foo"].val() == "1"); + CHECK(tree["foo"].val() == "1"); // get the val of a node (must be leaf node, otherwise it is a container and has no val) + CHECK(tree["foo"].key() == "foo"); // get the key of a node (must be child of a map, otherwise it has no key) CHECK(tree["bar"].is_seq()); CHECK(tree["bar"].has_key()); CHECK(tree["bar"].key() == "bar"); - // maps use string keys, seqs use integral keys: + // maps use string keys, seqs use index keys: CHECK(tree["bar"][0].val() == "2"); CHECK(tree["bar"][1].val() == "3"); CHECK(tree["john"].val() == "doe"); - // An integral key is the position of the child within its parent, + // An index key is the position of the child within its parent, // so even maps can also use int keys, if the key position is // known. CHECK(tree[0].id() == tree["foo"].id()); @@ -239,8 +249,8 @@ void sample_quick_overview() CHECK(tree[2].id() == tree["john"].id()); // Tree::operator[](int) searches a ***root*** child by its position. CHECK(tree[0].id() == tree["foo"].id()); // 0: first child of root - CHECK(tree[1].id() == tree["bar"].id()); // 1: first child of root - CHECK(tree[2].id() == tree["john"].id()); // 2: first child of root + CHECK(tree[1].id() == tree["bar"].id()); // 1: second child of root + CHECK(tree[2].id() == tree["john"].id()); // 2: third child of root // NodeRef::operator[](int) searches a ***node*** child by its position: CHECK(bar[0].val() == "2"); // 0 means first child of bar CHECK(bar[1].val() == "3"); // 1 means second child of bar @@ -259,11 +269,12 @@ void sample_quick_overview() CHECK(root["bar"].id() == root[1].id()); CHECK(root["john"].id() == root[2].id()); - // IMPORTANT. The ryml tree uses indexed linked lists for storing - // children, so the complexity of `Tree::operator[csubstr]` and - // `Tree::operator[size_t]` is linear on the number of root - // children. If you use `Tree::operator[]` with a large tree where - // the root has many children, you will see a performance hit. + // IMPORTANT. The ryml tree uses an index-based linked list for + // storing children, so the complexity of + // `Tree::operator[csubstr]` and `Tree::operator[size_t]` is O(n), + // linear on the number of root children. If you use + // `Tree::operator[]` with a large tree where the root has many + // children, you will see a performance hit. // // To avoid this hit, you can create your own accelerator // structure. For example, before doing a lookup, do a single @@ -283,6 +294,7 @@ void sample_quick_overview() // depending on the data, that number may be very different from // one to another. + //------------------------------------------------------------------ // Hierarchy: @@ -342,12 +354,48 @@ void sample_quick_overview() //------------------------------------------------------------------ // Gotchas: - CHECK(!tree["bar"].has_val()); // seq is a container, so no val - CHECK(!tree["bar"][0].has_key()); // belongs to a seq, so no key - CHECK(!tree["bar"][1].has_key()); // belongs to a seq, so no key - //CHECK(tree["bar"].val() == BOOM!); // ... so attempting to get a val is undefined behavior - //CHECK(tree["bar"][0].key() == BOOM!); // ... so attempting to get a key is undefined behavior - //CHECK(tree["bar"][1].key() == BOOM!); // ... so attempting to get a key is undefined behavior + + // ryml uses assertions to prevent you from trying to obtain + // things that do not exist. For example: + + { + ryml::ConstNodeRef seq_node = tree["bar"]; + ryml::ConstNodeRef val_node = seq_node[0]; + + CHECK(seq_node.is_seq()); // seq is a container + CHECK(!seq_node.has_val()); // ... so it has no val + //CHECK(seq_node.val() == BOOM!); // ... so attempting to get a val is undefined behavior + + CHECK(val_node.parent() == seq_node); // belongs to a seq + CHECK(!val_node.has_key()); // ... so it has no key + //CHECK(val_node.key() == BOOM!); // ... so attempting to get a key is undefined behavior + + CHECK(val_node.is_val()); // this node is a val + //CHECK(val_node.first_child() == BOOM!); // ... so attempting to get a child is undefined behavior + + // assertions are also present in methods that /may/ read the val: + CHECK(seq_node.is_seq()); // seq is a container + //CHECK(seq_node.val_is_null() BOOM!); // so cannot get the val to check + } + + + // By default, assertions are enabled unless the NDEBUG macro is + // defined (which happens in release builds). + // + // This adheres to the pay-only-for-what-you-use philosophy: if + // you are sure that your intent is correct, why would you need to + // pay the runtime cost for the assertions? + // + // The downside, of course, is that when you are not sure, release + // builds may be doing something crazy. + // + // So you can override this behavior and enable/disable + // assertions, by defining the macro RYML_USE_ASSERT to a proper + // value (see c4/yml/common.hpp). + // + // Also, to be clear, this does not apply to parse errors + // happening when the YAML is parsed. Checking for these errors is + // always enabled and cannot be switched off. //------------------------------------------------------------------ @@ -370,7 +418,7 @@ void sample_quick_overview() //------------------------------------------------------------------ - // Modifying existing nodes: operator<< vs operator= + // Modifying existing nodes: operator= vs operator<< // As implied by its name, ConstNodeRef is a reference to a const // node. It can be used to read from the node, but not write to it @@ -379,20 +427,21 @@ void sample_quick_overview() ryml::NodeRef wroot = tree.rootref(); // operator= assigns an existing string to the receiving node. - // This pointer will be in effect until the tree goes out of scope - // so BEWARE to only assign from strings outliving the tree. + // The contents are NOT copied, and this pointer will be in effect + // until the tree goes out of scope! So BEWARE to only assign from + // strings outliving the tree. wroot["foo"] = "says you"; wroot["bar"][0] = "-2"; wroot["bar"][1] = "-3"; wroot["john"] = "ron"; // Now the tree is _pointing_ at the memory of the strings above. - // That is OK because those are static strings and will outlive - // the tree. + // In this case it is OK because those are static strings and will + // outlive the tree. CHECK(root["foo"].val() == "says you"); CHECK(root["bar"][0].val() == "-2"); CHECK(root["bar"][1].val() == "-3"); CHECK(root["john"].val() == "ron"); - // WATCHOUT: do not assign from temporary objects: + // But WATCHOUT: do not assign from temporary objects: // { // std::string crash("will dangle"); // root["john"] = ryml::to_csubstr(crash); @@ -4077,6 +4126,10 @@ d: 3 // However, it is important to note that the error callback must never // return to the caller! Otherwise, an infinite loop or program crash // may occur. +// +// The default error handler calls std::abort(). You can use the cmake +// option and the macro RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS to have +// the default error handler throw a std::runtime_error instead. struct ErrorHandlerExample @@ -4137,11 +4190,13 @@ void sample_error_handler() // crash. ryml::set_callbacks(errh.callbacks()); errh.check_effect(/*committed*/true); - errh.check_error_occurs([]{ + bool had_parse_error = true; + errh.check_error_occurs([&had_parse_error]{ + had_parse_error = true; ryml::Tree tree = ryml::parse_in_arena("errorhandler.yml", "[a: b\n}"); - std::cout << tree; + had_parse_error = false; // this line is not executed }); - + CHECK(had_parse_error); ryml::set_callbacks(errh.defaults); // restore defaults. errh.check_effect(/*committed*/false); } diff --git a/src/c4/yml/common.cpp b/src/c4/yml/common.cpp index 1a86e86ae..00f14a03f 100644 --- a/src/c4/yml/common.cpp +++ b/src/c4/yml/common.cpp @@ -3,6 +3,9 @@ #ifndef RYML_NO_DEFAULT_CALLBACKS # include # include +# ifdef RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS +# include +# endif #endif // RYML_NO_DEFAULT_CALLBACKS namespace c4 { @@ -39,7 +42,11 @@ void report_error_impl(const char* msg, size_t length, Location loc, FILE *f) void error_impl(const char* msg, size_t length, Location loc, void * /*user_data*/) { report_error_impl(msg, length, loc, nullptr); +#ifdef RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS + throw std::runtime_error(std::string(msg, length)); +#else ::abort(); +#endif } void* allocate_impl(size_t length, void * /*hint*/, void * /*user_data*/) diff --git a/src/c4/yml/common.hpp b/src/c4/yml/common.hpp index 01d3d1f33..11284c7a3 100644 --- a/src/c4/yml/common.hpp +++ b/src/c4/yml/common.hpp @@ -7,6 +7,8 @@ #ifndef RYML_USE_ASSERT +/** define this macro with a boolean value to force enable/disable + assertions. This causes a slowdown of the code.*/ # define RYML_USE_ASSERT C4_USE_ASSERT #endif @@ -24,9 +26,7 @@ #endif -#if defined(NDEBUG) || defined(C4_NO_DEBUG_BREAK) || (!defined(RYML_DBG)) -# define RYML_DEBUG_BREAK() -#else +#if defined(RYML_DBG) && !defined(NDEBUG) && !defined(C4_NO_DEBUG_BREAK) # define RYML_DEBUG_BREAK() \ { \ if(c4::get_error_flags() & c4::ON_ERROR_DEBUGBREAK) \ @@ -34,6 +34,8 @@ C4_DEBUG_BREAK(); \ } \ } +#else +# define RYML_DEBUG_BREAK() #endif diff --git a/src/c4/yml/node.hpp b/src/c4/yml/node.hpp index dcb814015..011086e1b 100644 --- a/src/c4/yml/node.hpp +++ b/src/c4/yml/node.hpp @@ -158,10 +158,12 @@ struct RoNodeMethods #define id_ ((ConstImpl const* C4_RESTRICT)this)->m_id #define tree__ ((Impl const* C4_RESTRICT)this)->m_tree #define id__ ((Impl const* C4_RESTRICT)this)->m_id - // require valid + // require readable: this is a precondition for reading from the + // tree using this object. #define _C4RV() \ RYML_ASSERT(tree_ != nullptr); \ - _RYML_CB_ASSERT(tree_->m_callbacks, id_ != NONE) + _RYML_CB_ASSERT(tree_->m_callbacks, id_ != NONE); \ + _RYML_CB_ASSERT(tree_->m_callbacks, (!(((Impl const* C4_RESTRICT)this)->is_seed()))); #define _C4_IF_MUTABLE(ty) typename std::enable_if::value, ty>::type public: @@ -170,10 +172,10 @@ struct RoNodeMethods /** @{ */ /** returns the data or null when the id is NONE */ - C4_ALWAYS_INLINE NodeData const* get() const RYML_NOEXCEPT { RYML_ASSERT(tree_ != nullptr); return tree_->get(id_); } + C4_ALWAYS_INLINE NodeData const* get() const RYML_NOEXCEPT { return ((Impl const*)this)->readable() ? tree_->get(id_) : nullptr; } /** returns the data or null when the id is NONE */ template - C4_ALWAYS_INLINE auto get() RYML_NOEXCEPT -> _C4_IF_MUTABLE(NodeData*) { RYML_ASSERT(tree_ != nullptr); return tree__->get(id__); } + C4_ALWAYS_INLINE auto get() RYML_NOEXCEPT -> _C4_IF_MUTABLE(NodeData*) { return ((Impl const*)this)->readable() ? tree__->get(id__) : nullptr; } C4_ALWAYS_INLINE NodeType type() const RYML_NOEXCEPT { _C4RV(); return tree_->type(id_); } C4_ALWAYS_INLINE const char* type_str() const RYML_NOEXCEPT { _C4RV(); return tree_->type_str(id_); } @@ -239,17 +241,20 @@ struct RoNodeMethods C4_ALWAYS_INLINE bool is_root() const RYML_NOEXCEPT { _C4RV(); return tree_->is_root(id_); } C4_ALWAYS_INLINE bool has_parent() const RYML_NOEXCEPT { _C4RV(); return tree_->has_parent(id_); } - C4_ALWAYS_INLINE bool has_child(ConstImpl const& ch) const RYML_NOEXCEPT { _C4RV(); return tree_->has_child(id_, ch.m_id); } + C4_ALWAYS_INLINE bool has_child(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); return n.readable() ? tree_->has_child(id_, n.m_id) : false; } + C4_ALWAYS_INLINE bool has_child(size_t node) const RYML_NOEXCEPT { _C4RV(); return tree_->has_child(id_, node); } C4_ALWAYS_INLINE bool has_child(csubstr name) const RYML_NOEXCEPT { _C4RV(); return tree_->has_child(id_, name); } C4_ALWAYS_INLINE bool has_children() const RYML_NOEXCEPT { _C4RV(); return tree_->has_children(id_); } - C4_ALWAYS_INLINE bool has_sibling(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); return tree_->has_sibling(id_, n.m_id); } + C4_ALWAYS_INLINE bool has_sibling(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); return n.readable() ? tree_->has_sibling(id_, n.m_id) : false; } + C4_ALWAYS_INLINE bool has_sibling(size_t node) const RYML_NOEXCEPT { _C4RV(); return tree_->has_sibling(id_, node); } C4_ALWAYS_INLINE bool has_sibling(csubstr name) const RYML_NOEXCEPT { _C4RV(); return tree_->has_sibling(id_, name); } - /** counts with this */ - C4_ALWAYS_INLINE bool has_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->has_siblings(id_); } /** does not count with this */ C4_ALWAYS_INLINE bool has_other_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->has_other_siblings(id_); } + /** counts with this */ + RYML_DEPRECATED("use has_other_siblings()") bool has_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->has_siblings(id_); } + /** @} */ public: @@ -257,21 +262,16 @@ struct RoNodeMethods /** @name hierarchy getters */ /** @{ */ - + /** succeeds even when the node may have invalid or seed id */ template - C4_ALWAYS_INLINE auto doc(size_t num) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->doc(num)}; } - C4_ALWAYS_INLINE ConstImpl doc(size_t num) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->doc(num)}; } - + C4_ALWAYS_INLINE auto doc(size_t num) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { RYML_ASSERT(tree_); return {tree__, tree__->doc(num)}; } + /** succeeds even when the node may have invalid or seed id */ + C4_ALWAYS_INLINE ConstImpl doc(size_t num) const RYML_NOEXCEPT { RYML_ASSERT(tree_); return {tree_, tree_->doc(num)}; } template C4_ALWAYS_INLINE auto parent() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->parent(id__)}; } C4_ALWAYS_INLINE ConstImpl parent() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->parent(id_)}; } - - /** O(#num_children) */ - C4_ALWAYS_INLINE size_t child_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); return tree_->child_pos(id_, n.m_id); } - C4_ALWAYS_INLINE size_t num_children() const RYML_NOEXCEPT { _C4RV(); return tree_->num_children(id_); } - template C4_ALWAYS_INLINE auto first_child() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->first_child(id__)}; } C4_ALWAYS_INLINE ConstImpl first_child() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->first_child(id_)}; } @@ -288,12 +288,6 @@ struct RoNodeMethods C4_ALWAYS_INLINE auto find_child(csubstr name) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->find_child(id__, name)}; } C4_ALWAYS_INLINE ConstImpl find_child(csubstr name) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->find_child(id_, name)}; } - - /** O(#num_siblings) */ - C4_ALWAYS_INLINE size_t num_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->num_siblings(id_); } - C4_ALWAYS_INLINE size_t num_other_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->num_other_siblings(id_); } - C4_ALWAYS_INLINE size_t sibling_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); return tree_->child_pos(tree_->parent(id_), n.m_id); } - template C4_ALWAYS_INLINE auto prev_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->prev_sibling(id__)}; } C4_ALWAYS_INLINE ConstImpl prev_sibling() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->prev_sibling(id_)}; } @@ -318,11 +312,32 @@ struct RoNodeMethods C4_ALWAYS_INLINE auto find_sibling(csubstr name) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->find_sibling(id__, name)}; } C4_ALWAYS_INLINE ConstImpl find_sibling(csubstr name) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->find_sibling(id_, name)}; } - /** Find child by key; complexity is O(num_children). Returns the - * requested node, or a seed node if no such child is found. Using - * the returned seed node to write into the tree is UB. It is the - * responsibility of the caller to verify that the returned node - * is valid before using subsequently using it. + /** O(#num_children) */ + C4_ALWAYS_INLINE size_t child_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); _RYML_CB_ASSERT(tree_->m_callbacks, n.readable()); return tree_->child_pos(id_, n.m_id); } + + /** O(#num_children) */ + C4_ALWAYS_INLINE size_t num_children() const RYML_NOEXCEPT { _C4RV(); return tree_->num_children(id_); } + + /** O(#num_siblings) */ + C4_ALWAYS_INLINE size_t num_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->num_siblings(id_); } + C4_ALWAYS_INLINE size_t num_other_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->num_other_siblings(id_); } + C4_ALWAYS_INLINE size_t sibling_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); _RYML_CB_ASSERT(tree_->callbacks(), n.readable()); return tree_->child_pos(tree_->parent(id_), n.m_id); } + + /** Find child by key; complexity is O(num_children). + * + * Returns the requested node, or an object in seed state if no + * such child is found (see @ref NodeRef for an explanation of + * what is seed state). When the object is in seed state, using it + * to read from the tree is UB. The seed node can be used to write + * to the tree provided that its create() method is called prior + * to writing, which happens in most modifying methods in + * NodeRef. It is the caller's responsibility to verify that the + * returned node is readable before subsequently using it. + * + * @warning the calling object must be readable. This precondition + * is asserted. This assertion is performed only if @ref + * RYML_USE_ASSERT is set to true. As with the non-const overload, + * it is UB to call this method if the node is not readable. * * @see https://github.com/biojppm/rapidyaml/issues/389 */ template @@ -333,11 +348,21 @@ struct RoNodeMethods return ch != NONE ? Impl(tree__, ch) : Impl(tree__, id__, key); } - /** Find child by position; complexity is O(pos). Returns the - * requested node, or a seed node if no such child is found. Using - * the returned seed node to write into the tree is UB. It is the - * responsibility of the caller to verify that the returned node - * is valid before using subsequently using it. + /** Find child by position; complexity is O(pos). + * + * Returns the requested node, or an object in seed state if no + * such child is found (see @ref NodeRef for an explanation of + * what is seed state). When the object is in seed state, using it + * to read from the tree is UB. The seed node can be used to write + * to the tree provided that its create() method is called prior + * to writing, which happens in most modifying methods in + * NodeRef. It is the caller's responsibility to verify that the + * returned node is readable before subsequently using it. + * + * @warning the calling object must be readable. This precondition + * is asserted. This assertion is performed only if @ref + * RYML_USE_ASSERT is set to true. As with the non-const overload, + * it is UB to call this method if the node is not readable. * * @see https://github.com/biojppm/rapidyaml/issues/389 */ template @@ -348,11 +373,13 @@ struct RoNodeMethods return ch != NONE ? Impl(tree__, ch) : Impl(tree__, id__, pos); } - /** Find a child by key; complexity is O(num_children). Behaves - * similar to the non-const overload, but further asserts that the - * returned node is valid. This assertion is performed only if - * @ref RYML_USE_ASSERT is set to true. As with the non-const - * overload, it is UB to use the return value if it is not valid. + /** Find a child by key; complexity is O(num_children). + * + * Behaves similar to the non-const overload, but further asserts + * that the returned node is readable (because it can never be in + * a seed state). This assertion is performed only if @ref + * RYML_USE_ASSERT is set to true. As with the non-const overload, + * it is UB to use the return value if it is not valid. * * @see https://github.com/biojppm/rapidyaml/issues/389 */ C4_ALWAYS_INLINE ConstImpl operator[] (csubstr key) const RYML_NOEXCEPT @@ -363,11 +390,13 @@ struct RoNodeMethods return {tree_, ch}; } - /** Find a child by position; complexity is O(pos). Behaves - * similar to the non-const overload, but further asserts that the - * returned node is valid. This assertion is performed only if - * @ref RYML_USE_ASSERT is set to true. As with the non-const - * overload, it is UB to use the return value if it is not valid. + /** Find a child by position; complexity is O(pos). + * + * Behaves similar to the non-const overload, but further asserts + * that the returned node is readable (because it can never be in + * a seed state). This assertion is performed only if @ref + * RYML_USE_ASSERT is set to true. As with the non-const overload, + * it is UB to use the return value if it is not valid. * * @see https://github.com/biojppm/rapidyaml/issues/389 */ C4_ALWAYS_INLINE ConstImpl operator[] (size_t pos) const RYML_NOEXCEPT @@ -378,6 +407,44 @@ struct RoNodeMethods return {tree_, ch}; } + /** Get a child by name, with error checking; complexity is + * O(num_children). + * + * Behaves as operator[](csubstr) const, but always raises an + * error (even when RYML_USE_ASSERT is set to false) when the + * returned node does not exist, or when this node is not + * readable, or when it is not a map. This behaviour is similar to + * std::vector::at(), but the error consists in calling the error + * callback instead of directly raising an exception. */ + ConstImpl at(csubstr key) const + { + RYML_CHECK(tree_ != nullptr); + _RYML_CB_CHECK(tree_->m_callbacks, ((Impl const*)this)->readable()); + _RYML_CB_CHECK(tree_->m_callbacks, ((Impl const*)this)->is_map()); + size_t ch = tree_->find_child(id_, key); + _RYML_CB_CHECK(tree_->m_callbacks, ch != NONE); + return {tree_, ch}; + } + + /** Get a child by position, with error checking; complexity is + * O(pos). + * + * Behaves as operator[](size_t) const, but always raises an error + * (even when RYML_USE_ASSERT is set to false) when the returned + * node does not exist, or when this node is not readable, or when + * it is not a map. This behaviour is similar to + * std::vector::at(), but the error consists in calling the error + * callback instead of directly raising an exception. */ + ConstImpl at(size_t pos) const + { + RYML_CHECK(tree_ != nullptr); + _RYML_CB_CHECK(tree_->m_callbacks, ((Impl const*)this)->readable()); + _RYML_CB_CHECK(tree_->m_callbacks, ((Impl const*)this)->is_container()); + size_t ch = tree_->child(id_, pos); + _RYML_CB_ASSERT(tree_->m_callbacks, ch != NONE); + return {tree_, ch}; + } + /** @} */ public: @@ -438,7 +505,8 @@ struct RoNodeMethods template bool get_if(csubstr name, T *var) const { - auto ch = find_child(name); + _C4RV(); + ConstImpl ch = find_child(name); if(!ch.valid()) return false; ch >> *var; @@ -448,7 +516,8 @@ struct RoNodeMethods template bool get_if(csubstr name, T *var, T const& fallback) const { - auto ch = find_child(name); + _C4RV(); + ConstImpl ch = find_child(name); if(ch.valid()) { ch >> *var; @@ -527,8 +596,9 @@ struct RoNodeMethods /** visit every child node calling fn(node) */ template - C4_ALWAYS_INLINE bool visit(Visitor fn, size_t indentation_level=0, bool skip_root=true) const RYML_NOEXCEPT + bool visit(Visitor fn, size_t indentation_level=0, bool skip_root=true) const RYML_NOEXCEPT { + _C4RV(); return detail::_visit(*(ConstImpl const*)this, fn, indentation_level, skip_root); } /** visit every child node calling fn(node) */ @@ -536,13 +606,15 @@ struct RoNodeMethods auto visit(Visitor fn, size_t indentation_level=0, bool skip_root=true) RYML_NOEXCEPT -> _C4_IF_MUTABLE(bool) { + _C4RV(); return detail::_visit(*(Impl*)this, fn, indentation_level, skip_root); } /** visit every child node calling fn(node, level) */ template - C4_ALWAYS_INLINE bool visit_stacked(Visitor fn, size_t indentation_level=0, bool skip_root=true) const RYML_NOEXCEPT + bool visit_stacked(Visitor fn, size_t indentation_level=0, bool skip_root=true) const RYML_NOEXCEPT { + _C4RV(); return detail::_visit_stacked(*(ConstImpl const*)this, fn, indentation_level, skip_root); } /** visit every child node calling fn(node, level) */ @@ -550,6 +622,7 @@ struct RoNodeMethods auto visit_stacked(Visitor fn, size_t indentation_level=0, bool skip_root=true) RYML_NOEXCEPT -> _C4_IF_MUTABLE(bool) { + _C4RV(); return detail::_visit_stacked(*(Impl*)this, fn, indentation_level, skip_root); } @@ -632,6 +705,11 @@ class RYML_EXPORT ConstNodeRef : public detail::RoNodeMethodsoperator==(that); } + C4_ALWAYS_INLINE bool operator== (ConstNodeRef const& that) const RYML_NOEXCEPT { return that.m_tree == m_tree && m_id == that.m_id; } + C4_ALWAYS_INLINE bool operator!= (ConstNodeRef const& that) const RYML_NOEXCEPT { return ! this->operator== (that); } C4_ALWAYS_INLINE C4_PURE bool operator== (std::nullptr_t) const noexcept { return m_tree == nullptr || m_id == NONE; } C4_ALWAYS_INLINE C4_PURE bool operator!= (std::nullptr_t) const noexcept { return ! this->operator== (nullptr); } - C4_ALWAYS_INLINE bool operator== (csubstr val) const RYML_NOEXCEPT { RYML_ASSERT(has_val()); return m_tree->val(m_id) == val; } - C4_ALWAYS_INLINE bool operator!= (csubstr val) const RYML_NOEXCEPT { RYML_ASSERT(has_val()); return m_tree->val(m_id) != val; } + C4_ALWAYS_INLINE bool operator== (csubstr val) const RYML_NOEXCEPT { RYML_ASSERT(m_tree); _RYML_CB_ASSERT(m_tree->m_callbacks, m_id != NONE); return m_tree->val(m_id) == val; } + C4_ALWAYS_INLINE bool operator!= (csubstr val) const RYML_NOEXCEPT { RYML_ASSERT(m_tree); _RYML_CB_ASSERT(m_tree->m_callbacks, m_id != NONE); return m_tree->val(m_id) != val; } /** @} */ @@ -669,7 +747,8 @@ class RYML_EXPORT ConstNodeRef : public detail::RoNodeMethods { public: @@ -701,6 +780,10 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods #define _C4RV() \ RYML_ASSERT(m_tree != nullptr); \ _RYML_CB_ASSERT(m_tree->m_callbacks, m_id != NONE && !is_seed()) + // require id: a helper macro, undefined at the end + #define _C4RID() \ + RYML_ASSERT(m_tree != nullptr); \ + _RYML_CB_ASSERT(m_tree->m_callbacks, m_id != NONE) public: @@ -737,8 +820,9 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods inline bool valid() const { return m_tree != nullptr && m_id != NONE; } inline bool is_seed() const { return m_seed.str != nullptr || m_seed.len != NONE; } + inline bool readable() const { return valid() && !is_seed(); } - inline void _clear_seed() { /*do this manually or an assert is triggered*/ m_seed.str = nullptr; m_seed.len = NONE; } + inline void _clear_seed() { /*do the following manually or an assert is triggered: */ m_seed.str = nullptr; m_seed.len = NONE; } /** @} */ @@ -747,19 +831,31 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods /** @name comparisons */ /** @{ */ - inline bool operator== (NodeRef const& that) const { _C4RV(); RYML_ASSERT(that.valid() && !that.is_seed()); RYML_ASSERT(that.m_tree == m_tree); return m_id == that.m_id; } + inline bool operator== (NodeRef const& that) const + { + if(m_tree == that.m_tree && m_id == that.m_id) + { + if(is_seed() == that.is_seed()) + { + if(is_seed()) + { + return (m_seed.len == that.m_seed.len) && (m_seed.str == that.m_seed.str || m_seed == that.m_seed); + } + return true; + } + } + return false; + } inline bool operator!= (NodeRef const& that) const { return ! this->operator==(that); } - inline bool operator== (ConstNodeRef const& that) const { _C4RV(); RYML_ASSERT(that.valid()); RYML_ASSERT(that.m_tree == m_tree); return m_id == that.m_id; } + inline bool operator== (ConstNodeRef const& that) const { return m_tree == that.m_tree && m_id == that.m_id && !is_seed(); } inline bool operator!= (ConstNodeRef const& that) const { return ! this->operator==(that); } inline bool operator== (std::nullptr_t) const { return m_tree == nullptr || m_id == NONE || is_seed(); } - inline bool operator!= (std::nullptr_t) const { return m_tree != nullptr && m_id != NONE && !is_seed(); } + inline bool operator!= (std::nullptr_t) const { return ! this->operator==(nullptr); } - inline bool operator== (csubstr val) const { _C4RV(); RYML_ASSERT(has_val()); return m_tree->val(m_id) == val; } - inline bool operator!= (csubstr val) const { _C4RV(); RYML_ASSERT(has_val()); return m_tree->val(m_id) != val; } - - //inline operator bool () const { return m_tree == nullptr || m_id == NONE || is_seed(); } + inline bool operator== (csubstr val) const { _C4RV(); _RYML_CB_ASSERT(m_tree->m_callbacks, has_val()); return m_tree->val(m_id) == val; } + inline bool operator!= (csubstr val) const { _C4RV(); _RYML_CB_ASSERT(m_tree->m_callbacks, has_val()); return m_tree->val(m_id) != val; } /** @} */ @@ -780,22 +876,24 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods /** @name node modifiers */ /** @{ */ + void create() { _apply_seed(); } + void change_type(NodeType t) { _C4RV(); m_tree->change_type(m_id, t); } - void set_type(NodeType t) { _C4RV(); m_tree->_set_flags(m_id, t); } - void set_key(csubstr key) { _C4RV(); m_tree->_set_key(m_id, key); } - void set_val(csubstr val) { _C4RV(); m_tree->_set_val(m_id, val); } - void set_key_tag(csubstr key_tag) { _C4RV(); m_tree->set_key_tag(m_id, key_tag); } - void set_val_tag(csubstr val_tag) { _C4RV(); m_tree->set_val_tag(m_id, val_tag); } - void set_key_anchor(csubstr key_anchor) { _C4RV(); m_tree->set_key_anchor(m_id, key_anchor); } - void set_val_anchor(csubstr val_anchor) { _C4RV(); m_tree->set_val_anchor(m_id, val_anchor); } - void set_key_ref(csubstr key_ref) { _C4RV(); m_tree->set_key_ref(m_id, key_ref); } - void set_val_ref(csubstr val_ref) { _C4RV(); m_tree->set_val_ref(m_id, val_ref); } + void set_type(NodeType t) { _apply_seed(); m_tree->_set_flags(m_id, t); } + void set_key(csubstr key) { _apply_seed(); m_tree->_set_key(m_id, key); } + void set_val(csubstr val) { _apply_seed(); m_tree->_set_val(m_id, val); } + void set_key_tag(csubstr key_tag) { _apply_seed(); m_tree->set_key_tag(m_id, key_tag); } + void set_val_tag(csubstr val_tag) { _apply_seed(); m_tree->set_val_tag(m_id, val_tag); } + void set_key_anchor(csubstr key_anchor) { _apply_seed(); m_tree->set_key_anchor(m_id, key_anchor); } + void set_val_anchor(csubstr val_anchor) { _apply_seed(); m_tree->set_val_anchor(m_id, val_anchor); } + void set_key_ref(csubstr key_ref) { _apply_seed(); m_tree->set_key_ref(m_id, key_ref); } + void set_val_ref(csubstr val_ref) { _apply_seed(); m_tree->set_val_ref(m_id, val_ref); } template size_t set_key_serialized(T const& C4_RESTRICT k) { - _C4RV(); + _apply_seed(); csubstr s = m_tree->to_arena(k); m_tree->_set_key(m_id, s); return s.len; @@ -803,23 +901,25 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods template size_t set_val_serialized(T const& C4_RESTRICT v) { - _C4RV(); + _apply_seed(); csubstr s = m_tree->to_arena(v); m_tree->_set_val(m_id, s); return s.len; } size_t set_val_serialized(std::nullptr_t) { - _C4RV(); + _apply_seed(); m_tree->_set_val(m_id, csubstr{}); return 0; } - /** encode a blob as base64, then assign the result to the node's key - * @return the size of base64-encoded blob */ + /** encode a blob as base64 into the tree's arena, then assign the + * result to the node's key @return the size of base64-encoded + * blob */ size_t set_key_serialized(fmt::const_base64_wrapper w); - /** encode a blob as base64, then assign the result to the node's val - * @return the size of base64-encoded blob */ + /** encode a blob as base64 into the tree's arena, then assign the + * result to the node's val @return the size of base64-encoded + * blob */ size_t set_val_serialized(fmt::const_base64_wrapper w); public: @@ -853,8 +953,6 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods m_tree->remove_children(m_id); } - void create() { _apply_seed(); } - inline void operator= (NodeType_e t) { _apply_seed(); @@ -911,7 +1009,7 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods template inline csubstr to_arena(T const& C4_RESTRICT s) { - _C4RV(); + RYML_ASSERT(m_tree); // no need for valid or readable return m_tree->to_arena(s); } @@ -922,7 +1020,7 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods // operator<< for writing a substr to a stream) _apply_seed(); write(this, s); - RYML_ASSERT(val() == s); + _RYML_CB_ASSERT(m_tree->m_callbacks, val() == s); return *this; } @@ -970,9 +1068,9 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods void _apply_seed() { + _C4RID(); if(m_seed.str) // we have a seed key: use it to create the new child { - //RYML_ASSERT(i.key.scalar.empty() || m_key == i.key.scalar || m_key.empty()); m_id = m_tree->append_child(m_id); m_tree->_set_key(m_id, m_seed); m_seed.str = nullptr; @@ -980,14 +1078,14 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods } else if(m_seed.len != NONE) // we have a seed index: create a child at that position { - RYML_ASSERT(m_tree->num_children(m_id) == m_seed.len); + _RYML_CB_ASSERT(m_tree->m_callbacks, m_tree->num_children(m_id) == m_seed.len); m_id = m_tree->append_child(m_id); m_seed.str = nullptr; m_seed.len = NONE; } else { - RYML_ASSERT(valid()); + _RYML_CB_ASSERT(m_tree->m_callbacks, valid()); } } @@ -1014,7 +1112,7 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods inline NodeRef insert_child(NodeRef after) { _C4RV(); - RYML_ASSERT(after.m_tree == m_tree); + _RYML_CB_ASSERT(m_tree->m_callbacks, after.m_tree == m_tree); NodeRef r(m_tree, m_tree->insert_child(m_id, after.m_id)); return r; } @@ -1022,7 +1120,7 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods inline NodeRef insert_child(NodeInit const& i, NodeRef after) { _C4RV(); - RYML_ASSERT(after.m_tree == m_tree); + _RYML_CB_ASSERT(m_tree->m_callbacks, after.m_tree == m_tree); NodeRef r(m_tree, m_tree->insert_child(m_id, after.m_id)); r._apply(i); return r; @@ -1063,7 +1161,7 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods inline NodeRef insert_sibling(ConstNodeRef const& after) { _C4RV(); - RYML_ASSERT(after.m_tree == m_tree); + _RYML_CB_ASSERT(m_tree->m_callbacks, after.m_tree == m_tree); NodeRef r(m_tree, m_tree->insert_sibling(m_id, after.m_id)); return r; } @@ -1071,7 +1169,7 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods inline NodeRef insert_sibling(NodeInit const& i, ConstNodeRef const& after) { _C4RV(); - RYML_ASSERT(after.m_tree == m_tree); + _RYML_CB_ASSERT(m_tree->m_callbacks, after.m_tree == m_tree); NodeRef r(m_tree, m_tree->insert_sibling(m_id, after.m_id)); r._apply(i); return r; @@ -1112,8 +1210,8 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods inline void remove_child(NodeRef & child) { _C4RV(); - RYML_ASSERT(has_child(child)); - RYML_ASSERT(child.parent().id() == id()); + _RYML_CB_ASSERT(m_tree->m_callbacks, has_child(child)); + _RYML_CB_ASSERT(m_tree->m_callbacks, child.parent().id() == id()); m_tree->remove(child.id()); child.clear(); } @@ -1122,9 +1220,9 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods inline void remove_child(size_t pos) { _C4RV(); - RYML_ASSERT(pos >= 0 && pos < num_children()); + _RYML_CB_ASSERT(m_tree->m_callbacks, pos >= 0 && pos < num_children()); size_t child = m_tree->child(m_id, pos); - RYML_ASSERT(child != NONE); + _RYML_CB_ASSERT(m_tree->m_callbacks, child != NONE); m_tree->remove(child); } @@ -1133,7 +1231,7 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods { _C4RV(); size_t child = m_tree->find_child(m_id, key); - RYML_ASSERT(child != NONE); + _RYML_CB_ASSERT(m_tree->m_callbacks, child != NONE); m_tree->remove(child); } @@ -1174,7 +1272,7 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods inline NodeRef duplicate(ConstNodeRef const& after) const { _C4RV(); - RYML_ASSERT(m_tree == after.m_tree || after.m_id == NONE); + _RYML_CB_ASSERT(m_tree->m_callbacks, m_tree == after.m_tree || after.m_id == NONE); size_t dup = m_tree->duplicate(m_id, m_tree->parent(m_id), after.m_id); NodeRef r(m_tree, dup); return r; @@ -1188,7 +1286,7 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods inline NodeRef duplicate(NodeRef const& parent, ConstNodeRef const& after) const { _C4RV(); - RYML_ASSERT(parent.m_tree == after.m_tree || after.m_id == NONE); + _RYML_CB_ASSERT(m_tree->m_callbacks, parent.m_tree == after.m_tree || after.m_id == NONE); if(parent.m_tree == m_tree) { size_t dup = m_tree->duplicate(m_id, parent.m_id, after.m_id); @@ -1206,7 +1304,7 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods inline void duplicate_children(NodeRef const& parent, ConstNodeRef const& after) const { _C4RV(); - RYML_ASSERT(parent.m_tree == after.m_tree); + _RYML_CB_ASSERT(m_tree->m_callbacks, parent.m_tree == after.m_tree); if(parent.m_tree == m_tree) { m_tree->duplicate_children(m_id, parent.m_id, after.m_id); diff --git a/src/c4/yml/parse.cpp b/src/c4/yml/parse.cpp index 84f6abcad..5e52a9697 100644 --- a/src/c4/yml/parse.cpp +++ b/src/c4/yml/parse.cpp @@ -4527,7 +4527,7 @@ bool Parser::_filter_nl(substr r, size_t *C4_RESTRICT i, size_t *C4_RESTRICT pos #define _c4dbgfnl(...) #endif - const char curr = r[*i]; + const char curr = r[*i];(void)curr; bool replaced = false; _RYML_CB_ASSERT(m_stack.m_callbacks, indentation != npos); diff --git a/src/c4/yml/parse.hpp b/src/c4/yml/parse.hpp index 659edf7e0..93a652d27 100644 --- a/src/c4/yml/parse.hpp +++ b/src/c4/yml/parse.hpp @@ -388,9 +388,9 @@ class RYML_EXPORT Parser csubstr _consume_scalar(); void _move_scalar_from_top(); - inline NodeData* _append_val_null(const char *str) { _RYML_CB_ASSERT(m_stack.m_callbacks, str >= m_buf.begin() && str <= m_buf.end()); return _append_val({nullptr, size_t(0)}); } - inline NodeData* _append_key_val_null(const char *str) { _RYML_CB_ASSERT(m_stack.m_callbacks, str >= m_buf.begin() && str <= m_buf.end()); return _append_key_val({nullptr, size_t(0)}); } - inline void _store_scalar_null(const char *str) { _RYML_CB_ASSERT(m_stack.m_callbacks, str >= m_buf.begin() && str <= m_buf.end()); _store_scalar({nullptr, size_t(0)}, false); } + inline NodeData* _append_val_null(const char *str) { _RYML_CB_ASSERT(m_stack.m_callbacks, str >= m_buf.begin() && str <= m_buf.end()); (void)str; return _append_val({nullptr, size_t(0)}); } + inline NodeData* _append_key_val_null(const char *str) { _RYML_CB_ASSERT(m_stack.m_callbacks, str >= m_buf.begin() && str <= m_buf.end()); (void)str; return _append_key_val({nullptr, size_t(0)}); } + inline void _store_scalar_null(const char *str) { _RYML_CB_ASSERT(m_stack.m_callbacks, str >= m_buf.begin() && str <= m_buf.end()); (void)str; _store_scalar({nullptr, size_t(0)}, false); } void _set_indentation(size_t behind); void _save_indentation(size_t behind=0); diff --git a/src/c4/yml/std/vector.hpp b/src/c4/yml/std/vector.hpp index 1b6a4610a..49963ee93 100644 --- a/src/c4/yml/std/vector.hpp +++ b/src/c4/yml/std/vector.hpp @@ -38,7 +38,7 @@ bool read(c4::yml::ConstNodeRef const& n, std::vector *vec) { vec->resize(n.num_children()); size_t pos = 0; - bool tmp; + bool tmp = false; for(auto const ch : n) { ch >> tmp; diff --git a/src/c4/yml/tree.cpp b/src/c4/yml/tree.cpp index b3819868e..2930dffdc 100644 --- a/src/c4/yml/tree.cpp +++ b/src/c4/yml/tree.cpp @@ -246,10 +246,6 @@ ConstNodeRef Tree::rootref() const return ConstNodeRef(this, root_id()); } -ConstNodeRef Tree::crootref() -{ - return ConstNodeRef(this, root_id()); -} ConstNodeRef Tree::crootref() const { return ConstNodeRef(this, root_id()); @@ -257,23 +253,17 @@ ConstNodeRef Tree::crootref() const NodeRef Tree::ref(size_t id) { - _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_size); + _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_cap); return NodeRef(this, id); } ConstNodeRef Tree::ref(size_t id) const { - _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_size); - return ConstNodeRef(this, id); -} - -ConstNodeRef Tree::cref(size_t id) -{ - _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_size); + _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_cap); return ConstNodeRef(this, id); } ConstNodeRef Tree::cref(size_t id) const { - _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_size); + _RYML_CB_ASSERT(m_callbacks, id != NONE && id >= 0 && id < m_cap); return ConstNodeRef(this, id); } @@ -331,12 +321,12 @@ Tree::~Tree() } -Tree::Tree(Tree const& that) noexcept : Tree(that.m_callbacks) +Tree::Tree(Tree const& that) : Tree(that.m_callbacks) { _copy(that); } -Tree& Tree::operator= (Tree const& that) noexcept +Tree& Tree::operator= (Tree const& that) { _free(); m_callbacks = that.m_callbacks; @@ -344,12 +334,12 @@ Tree& Tree::operator= (Tree const& that) noexcept return *this; } -Tree::Tree(Tree && that) noexcept : Tree(that.m_callbacks) +Tree::Tree(Tree && that) : Tree(that.m_callbacks) { _move(that); } -Tree& Tree::operator= (Tree && that) noexcept +Tree& Tree::operator= (Tree && that) { _free(); m_callbacks = that.m_callbacks; @@ -1584,6 +1574,7 @@ size_t Tree::child(size_t node, size_t pos) const size_t Tree::child_pos(size_t node, size_t ch) const { + _RYML_CB_ASSERT(m_callbacks, node != NONE); size_t count = 0; for(size_t i = first_child(node); i != NONE; i = next_sibling(i)) { @@ -1821,7 +1812,7 @@ csubstr _transform_tag(Tree *t, csubstr tag, size_t node) size_t required_size = t->resolve_tag(substr{}, tag, node); if(!required_size) return tag; - const char *prev_arena = t->arena().str; + const char *prev_arena = t->arena().str; (void)prev_arena; substr buf = t->alloc_arena(required_size); _RYML_CB_ASSERT(t->m_callbacks, t->arena().str == prev_arena); size_t actual_size = t->resolve_tag(buf, tag, node); diff --git a/src/c4/yml/tree.hpp b/src/c4/yml/tree.hpp index c99a20726..da7e9a046 100644 --- a/src/c4/yml/tree.hpp +++ b/src/c4/yml/tree.hpp @@ -453,11 +453,11 @@ class RYML_EXPORT Tree ~Tree(); - Tree(Tree const& that) noexcept; - Tree(Tree && that) noexcept; + Tree(Tree const& that); + Tree(Tree && that); - Tree& operator= (Tree const& that) noexcept; - Tree& operator= (Tree && that) noexcept; + Tree& operator= (Tree const& that); + Tree& operator= (Tree && that); /** @} */ @@ -491,7 +491,7 @@ class RYML_EXPORT Tree /** @{ */ //! get the index of a node belonging to this tree. - //! @p n can be nullptr, in which case a + //! @p n can be nullptr, in which case NONE is returned size_t id(NodeData const* n) const { if( ! n) @@ -538,19 +538,22 @@ class RYML_EXPORT Tree //! Get a NodeRef of a node by id ConstNodeRef ref(size_t id) const; //! Get a NodeRef of a node by id - ConstNodeRef cref(size_t id); - //! Get a NodeRef of a node by id ConstNodeRef cref(size_t id) const; //! Get the root as a NodeRef NodeRef rootref(); - //! Get the root as a NodeRef + //! Get the root as a ConstNodeRef ConstNodeRef rootref() const; - //! Get the root as a NodeRef - ConstNodeRef crootref(); - //! Get the root as a NodeRef + //! Get the root as a ConstNodeRef ConstNodeRef crootref() const; + //! get the i-th document of the stream + //! @note @i is NOT the node id, but the doc position within the stream + NodeRef docref(size_t i); + //! get the i-th document of the stream + //! @note @i is NOT the node id, but the doc position within the stream + ConstNodeRef docref(size_t i) const; + //! find a root child by name, return it as a NodeRef //! @note requires the root to be a map. NodeRef operator[] (csubstr key); @@ -565,13 +568,6 @@ class RYML_EXPORT Tree //! @note @i is NOT the node id, but the child's position ConstNodeRef operator[] (size_t i) const; - //! get the i-th document of the stream - //! @note @i is NOT the node id, but the doc position within the stream - NodeRef docref(size_t i); - //! get the i-th document of the stream - //! @note @i is NOT the node id, but the doc position within the stream - ConstNodeRef docref(size_t i) const; - /** @} */ public: diff --git a/test/callbacks_tester.hpp b/test/callbacks_tester.hpp index 13be9e40b..89f183728 100644 --- a/test/callbacks_tester.hpp +++ b/test/callbacks_tester.hpp @@ -21,7 +21,7 @@ struct CallbacksTester size_t num_allocs, alloc_size; size_t num_deallocs, dealloc_size; - CallbacksTester(const char *id_="notset", size_t sz=10u * 1024u) // 10KB + CallbacksTester(const char *id_="notset", size_t sz=30u * 1024u) // 30KB : memory_pool(sz) , id(id_) , num_allocs() diff --git a/test/test_case.cpp b/test/test_case.cpp index 538bfd334..bdde31463 100644 --- a/test/test_case.cpp +++ b/test/test_case.cpp @@ -215,6 +215,20 @@ ExpectError::~ExpectError() set_callbacks(m_tree_prev); } +void ExpectError::check_success(Tree *tree, std::function fn) +{ + auto context = ExpectError(tree, {}); + try + { + fn(); + } + catch(ExpectedError const&) + { + ; + } + EXPECT_FALSE(context.m_got_an_error); +} + void ExpectError::do_check(Tree *tree, std::function fn, Location expected_location) { auto context = ExpectError(tree, expected_location); diff --git a/test/test_case.hpp b/test/test_case.hpp index a5c08917b..7b4a3a487 100644 --- a/test/test_case.hpp +++ b/test/test_case.hpp @@ -173,6 +173,8 @@ struct ExpectError static void do_check(Tree *tree, std::function fn, Location expected={}); static void check_assertion( std::function fn, Location expected={}) { check_assertion(nullptr, fn, expected); } static void check_assertion(Tree *tree, std::function fn, Location expected={}); + static void check_success(std::function fn) { check_success(nullptr, fn); } + static void check_success(Tree *tree, std::function fn); }; diff --git a/test/test_noderef.cpp b/test/test_noderef.cpp index 2745c71f2..d41082758 100644 --- a/test/test_noderef.cpp +++ b/test/test_noderef.cpp @@ -149,6 +149,275 @@ TEST(NodeRef, general) EXPECT_EQ(root["b"]["aaa"].val(), "0"); } +TEST(NodeRef, valid_vs_seed_vs_readable) +{ + static_assert(!ConstNodeRef::is_seed(), "ConstNodeRef must never be a seed"); + Tree tree = parse_in_arena("foo: bar"); + NodeRef foo = tree["foo"]; + ConstNodeRef const_foo = tree["foo"]; + EXPECT_TRUE(foo.valid()); + EXPECT_FALSE(foo.is_seed()); + EXPECT_TRUE(foo.readable()); + EXPECT_TRUE(const_foo.valid()); + EXPECT_FALSE(const_foo.is_seed()); + EXPECT_TRUE(const_foo.readable()); + NodeRef none; + EXPECT_FALSE(none.valid()); + EXPECT_FALSE(none.is_seed()); + EXPECT_FALSE(none.readable()); + ConstNodeRef const_none; + EXPECT_FALSE(const_none.valid()); + EXPECT_FALSE(const_none.is_seed()); + EXPECT_FALSE(const_none.readable()); + none = tree["none"]; + EXPECT_TRUE(none.valid()); + EXPECT_TRUE(none.is_seed()); + EXPECT_FALSE(none.readable()); + ASSERT_FALSE(tree.rootref().has_child(none)); + const_none = tree["none"]; + EXPECT_FALSE(const_none.valid()); + EXPECT_FALSE(const_none.is_seed()); + EXPECT_TRUE(none.is_seed()); + EXPECT_FALSE(none.readable()); +} + +#define _TEST_FAIL_READ(method_expr) \ + { \ + SCOPED_TRACE(#method_expr); \ + std::cout << __FILE__ << ":" << __LINE__ << ": " << #method_expr << "\n"; \ + if(tree) \ + ExpectError::check_assertion(tree, [&]{ return method_expr; }); \ + else \ + ExpectError::check_assertion([&]{ return method_expr; }); \ + } +#define _TEST_SUCCEED_READ(method_expr) \ + { \ + SCOPED_TRACE(#method_expr); \ + std::cout << __FILE__ << ":" << __LINE__ << ": " << #method_expr << "\n"; \ + if(tree) \ + ExpectError::check_success(tree, [&]{ return method_expr; }); \ + else \ + ExpectError::check_success([&]{ return method_expr; }); \ + } +template +void test_fail_read(Tree *tree, NodeT node) +{ + _TEST_SUCCEED_READ(node.get()) + _TEST_FAIL_READ(node.type()) + _TEST_FAIL_READ(node.type_str()) + _TEST_FAIL_READ(node.key()) + _TEST_FAIL_READ(node.key_tag()) + _TEST_FAIL_READ(node.key_anchor()) + _TEST_FAIL_READ(node.key_ref()) + _TEST_FAIL_READ(node.key_is_null()) + _TEST_FAIL_READ(node.keysc()) + _TEST_FAIL_READ(node.val()) + _TEST_FAIL_READ(node.val_tag()) + _TEST_FAIL_READ(node.val_anchor()) + _TEST_FAIL_READ(node.val_ref()) + _TEST_FAIL_READ(node.val_is_null()) + _TEST_FAIL_READ(node.valsc()) + _TEST_FAIL_READ(node.is_map()) + _TEST_FAIL_READ(node.empty()) + _TEST_FAIL_READ(node.is_stream()) + _TEST_FAIL_READ(node.is_doc()) + _TEST_FAIL_READ(node.is_container()) + _TEST_FAIL_READ(node.is_map()) + _TEST_FAIL_READ(node.is_seq()) + _TEST_FAIL_READ(node.has_val()) + _TEST_FAIL_READ(node.has_key()) + _TEST_FAIL_READ(node.is_keyval()) + _TEST_FAIL_READ(node.has_key_tag()) + _TEST_FAIL_READ(node.has_val_tag()) + _TEST_FAIL_READ(node.has_key_anchor()) + _TEST_FAIL_READ(node.has_val_anchor()) + _TEST_FAIL_READ(node.is_val_anchor()) + _TEST_FAIL_READ(node.has_anchor()) + _TEST_FAIL_READ(node.is_anchor()) + _TEST_FAIL_READ(node.is_key_ref()) + _TEST_FAIL_READ(node.is_val_ref()) + _TEST_FAIL_READ(node.is_ref()) + _TEST_FAIL_READ(node.is_anchor_or_ref()) + _TEST_FAIL_READ(node.is_key_quoted()) + _TEST_FAIL_READ(node.is_val_quoted()) + _TEST_FAIL_READ(node.parent_is_seq()) + _TEST_FAIL_READ(node.parent_is_map()) + _TEST_FAIL_READ(node.is_root()) + _TEST_FAIL_READ(node.has_parent()) + _TEST_FAIL_READ(node.has_child(0)) + _TEST_FAIL_READ(node.has_child("key")) + _TEST_FAIL_READ(node.has_children()) + _TEST_FAIL_READ(node.has_sibling("key")) + _TEST_FAIL_READ(node.has_other_siblings()) + _TEST_FAIL_READ(node.doc(0)) + _TEST_FAIL_READ(node.parent()) + _TEST_FAIL_READ(node.num_children()) + _TEST_FAIL_READ(node.first_child()) + _TEST_FAIL_READ(node.last_child()) + _TEST_FAIL_READ(node.child(0)) + _TEST_FAIL_READ(node.find_child("key")) + _TEST_FAIL_READ(node.prev_sibling()) + _TEST_FAIL_READ(node.next_sibling()) + _TEST_FAIL_READ(node.first_sibling()) + _TEST_FAIL_READ(node.last_sibling()) + _TEST_FAIL_READ(node.sibling(0)) + _TEST_FAIL_READ(node.find_sibling("key")) + _TEST_FAIL_READ(node.num_children()) + _TEST_FAIL_READ(node.num_siblings()) + _TEST_FAIL_READ(node.num_other_siblings()) + _TEST_FAIL_READ(node["key"]) + _TEST_FAIL_READ(node[0]) + _TEST_FAIL_READ(node.at("key")) + _TEST_FAIL_READ(node.at(0)) + int val; + _TEST_FAIL_READ(node >> val) + _TEST_FAIL_READ(node >> key(val)) + _TEST_FAIL_READ(node >> fmt::base64(val)) + _TEST_FAIL_READ(node >> key(fmt::base64(val))) + _TEST_FAIL_READ(node.deserialize_key(fmt::base64(val))) + _TEST_FAIL_READ(node.deserialize_val(fmt::base64(val))) + _TEST_FAIL_READ(node.get_if("key", &val)); + _TEST_FAIL_READ(node.get_if("key", &val, 0)); + const NodeT const_node = node; + _TEST_FAIL_READ(node.begin()); + _TEST_FAIL_READ(node.cbegin()); + _TEST_FAIL_READ(const_node.begin()); + _TEST_FAIL_READ(const_node.cbegin()); + _TEST_FAIL_READ(node.end()); + _TEST_FAIL_READ(node.end()); + _TEST_FAIL_READ(const_node.end()); + _TEST_FAIL_READ(const_node.end()); + _TEST_FAIL_READ(node.children()); + _TEST_FAIL_READ(node.children()); + _TEST_FAIL_READ(const_node.children()); + _TEST_FAIL_READ(const_node.children()); + _TEST_FAIL_READ(node.siblings()); + _TEST_FAIL_READ(node.siblings()); + _TEST_FAIL_READ(const_node.siblings()); + _TEST_FAIL_READ(const_node.siblings()); + //_TEST_FAIL_READ(node.visit([](NodeT &n, size_t level){ (void)n; (void)level; return false; })); + //_TEST_FAIL_READ(const_node.visit([](const NodeT &n, size_t level){ (void)n; (void)level; return false; })); + _TEST_SUCCEED_READ(const_node == node); + _TEST_SUCCEED_READ(const_node != node); + _TEST_SUCCEED_READ(const_node == nullptr); + _TEST_SUCCEED_READ(const_node != nullptr); + _TEST_FAIL_READ(const_node == "val"); + _TEST_FAIL_READ(const_node != "val"); + if(std::is_same::value) + { + ConstNodeRef other; + _TEST_SUCCEED_READ(node == other); + _TEST_SUCCEED_READ(node != node); + } +} +template +void test_fail_read_subject(Tree *tree, NodeT node, NodeT subject) +{ + if(node.readable()) + { + _TEST_SUCCEED_READ(node.has_child(subject)) + _TEST_SUCCEED_READ(node.has_sibling(subject)) + } + else + { + _TEST_FAIL_READ(node.has_child(subject)) + _TEST_FAIL_READ(node.has_sibling(subject)) + } + _TEST_FAIL_READ(node.child_pos(subject)) + _TEST_FAIL_READ(node.sibling_pos(subject)) +} +#undef _TEST_FAIL_READ +#undef _TEST_SUCCEED_READ + +TEST(NodeRef, cannot_read_from_invalid) +{ + SCOPED_TRACE("here"); + NodeRef none; + ASSERT_EQ(none.tree(), nullptr); + ASSERT_EQ(none.id(), NONE); + EXPECT_FALSE(none.valid()); + EXPECT_FALSE(none.is_seed()); + EXPECT_FALSE(none.readable()); + test_fail_read(nullptr, none); + test_fail_read_subject(nullptr, none, none); +} + +TEST(NodeRef, cannot_read_from_invalid_subject) +{ + SCOPED_TRACE("here"); + NodeRef none; + Tree tree = parse_in_arena("foo: bar"); + test_fail_read_subject(&tree, tree["foo"], none); +} + +TEST(ConstNodeRef, cannot_read_from_invalid) +{ + SCOPED_TRACE("here"); + ConstNodeRef const_none; + ASSERT_EQ(const_none.tree(), nullptr); + ASSERT_EQ(const_none.id(), NONE); + EXPECT_FALSE(const_none.valid()); + EXPECT_FALSE(const_none.is_seed()); + EXPECT_FALSE(const_none.readable()); + test_fail_read(nullptr, const_none); + test_fail_read_subject(nullptr, const_none, const_none); + Tree tree = parse_in_arena("foo: bar"); + ConstNodeRef foo = tree["foo"]; + test_fail_read_subject(&tree, foo, const_none); +} + +TEST(ConstNodeRef, cannot_read_from_invalid_subject) +{ + SCOPED_TRACE("here"); + Tree tree = parse_in_arena("foo: bar"); + ConstNodeRef foo = tree["foo"]; + ConstNodeRef const_none; + test_fail_read_subject(&tree, foo, const_none); +} + +TEST(NodeRef, cannot_read_from_seed) +{ + SCOPED_TRACE("here"); + Tree tree = parse_in_arena("foo: bar"); + NodeRef none = tree["none"]; + ASSERT_EQ(none.tree(), &tree); + ASSERT_EQ(none.id(), 0); + EXPECT_TRUE(none.valid()); + EXPECT_TRUE(none.is_seed()); + EXPECT_FALSE(none.readable()); + test_fail_read(&tree, none); + test_fail_read_subject(&tree, none, none); +} + +TEST(NodeRef, cannot_read_from_seed_subject) +{ + SCOPED_TRACE("here"); + Tree tree = parse_in_arena("foo: bar"); + NodeRef none = tree["none"]; + ASSERT_EQ(none.tree(), &tree); + ASSERT_EQ(none.id(), 0); + EXPECT_TRUE(none.valid()); + EXPECT_TRUE(none.is_seed()); + EXPECT_FALSE(none.readable()); + test_fail_read(&tree, none); + test_fail_read_subject(&tree, none, none); +} + +TEST(ConstNodeRef, cannot_read_from_seed_subject) +{ + SCOPED_TRACE("here"); + Tree tree = parse_in_arena("foo: bar"); + ConstNodeRef const_none = tree["none"]; + ASSERT_EQ(const_none.tree(), &tree); + ASSERT_EQ(const_none.id(), NONE); + EXPECT_FALSE(const_none.valid()); + EXPECT_FALSE(const_none.is_seed()); + EXPECT_FALSE(const_none.readable()); + test_fail_read(&tree, const_none); + ConstNodeRef foo = tree["foo"]; + test_fail_read_subject(&tree, foo, const_none); +} + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- diff --git a/test/test_tree.cpp b/test/test_tree.cpp index 18c6a1b5e..8f14eae2a 100644 --- a/test/test_tree.cpp +++ b/test/test_tree.cpp @@ -832,6 +832,23 @@ TEST(Tree, clear) //------------------------------------------- +template +void verify_assertion(Tree &tree, Function &&fn) +{ + ExpectError::check_assertion(&tree, [&]{ + (void)fn(tree); + }); +} +template +void verify_assertion(csubstr src, Function &&fn) +{ + Tree tree = parse_in_arena(src); + ExpectError::check_assertion(&tree, [&]{ + (void)fn(tree); + }); +} + + TEST(Tree, ref) { Tree t = parse_in_arena("[0, 1, 2, 3]"); @@ -845,6 +862,25 @@ TEST(Tree, ref) EXPECT_TRUE(t.ref(2).is_val()); EXPECT_TRUE(t.ref(3).is_val()); EXPECT_TRUE(t.ref(4).is_val()); + verify_assertion(t, [](Tree & tree){ return tree.ref(tree.capacity()); }); + verify_assertion(t, [](Tree & tree){ return tree.ref(NONE); }); +} + +TEST(Tree, cref) +{ + Tree t = parse_in_arena("[0, 1, 2, 3]"); + EXPECT_EQ(t.cref(0).id(), 0); + EXPECT_EQ(t.cref(1).id(), 1); + EXPECT_EQ(t.cref(2).id(), 2); + EXPECT_EQ(t.cref(3).id(), 3); + EXPECT_EQ(t.cref(4).id(), 4); + EXPECT_TRUE(t.cref(0).is_seq()); + EXPECT_TRUE(t.cref(1).is_val()); + EXPECT_TRUE(t.cref(2).is_val()); + EXPECT_TRUE(t.cref(3).is_val()); + EXPECT_TRUE(t.cref(4).is_val()); + verify_assertion(t, [](Tree & tree){ return tree.cref(tree.capacity()); }); + verify_assertion(t, [](Tree & tree){ return tree.cref(NONE); }); } TEST(Tree, ref_const) @@ -860,6 +896,8 @@ TEST(Tree, ref_const) EXPECT_TRUE(t.ref(2).is_val()); EXPECT_TRUE(t.ref(3).is_val()); EXPECT_TRUE(t.ref(4).is_val()); + verify_assertion("[0, 1, 2, 3]", [](Tree const& tree){ return tree.cref(tree.capacity()); }); + verify_assertion("[0, 1, 2, 3]", [](Tree const& tree){ return tree.cref(NONE); }); } @@ -901,6 +939,11 @@ TEST(Tree, operator_square_brackets) EXPECT_FALSE(cm[2] != "2"); EXPECT_FALSE(cm[3] != "3"); EXPECT_FALSE(cm[4] != "4"); + // + verify_assertion(t, [&](Tree const&){ return cm[m.capacity()]; }); + verify_assertion(t, [&](Tree const&){ return cm[NONE]; }); + verify_assertion(t, [&](Tree const&){ return cm[0][0]; }); + verify_assertion(t, [&](Tree const&){ return cm["a"]; }); } { Tree t = parse_in_arena("{a: 0, b: 1, c: 2, d: 3, e: 4}"); @@ -938,6 +981,9 @@ TEST(Tree, operator_square_brackets) EXPECT_FALSE(cm["c"] != "2"); EXPECT_FALSE(cm["d"] != "3"); EXPECT_FALSE(cm["e"] != "4"); + // + verify_assertion(t, [&](Tree const&){ return cm["f"]; }); + verify_assertion(t, [&](Tree const&){ return cm["g"]["h"]; }); } } @@ -1045,6 +1091,8 @@ TEST(NodeType, type_str) EXPECT_EQ(to_csubstr(NodeType(VALANCH).type_str()), "(unk)"); } + + TEST(NodeType, is_stream) { EXPECT_FALSE(NodeType(NOTYPE).is_stream()); @@ -1074,6 +1122,13 @@ foo: bar EXPECT_EQ(stream.is_stream(), stream.get()->m_type.is_stream()); EXPECT_EQ(doc.is_stream(), doc.get()->m_type.is_stream()); EXPECT_EQ(keyval.is_stream(), keyval.get()->m_type.is_stream()); + // + ASSERT_TRUE(t.docref(0)["none"].valid()); + ASSERT_TRUE(t.docref(0)["none"].is_seed()); + ASSERT_FALSE(t.docref(0)["none"].readable()); + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_stream(); }); + verify_assertion(t, [&](Tree const&){ return t.is_stream(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.is_stream(NONE); }); } TEST(NodeType, is_doc) @@ -1125,6 +1180,11 @@ a scalar EXPECT_EQ(mdoc.is_doc(), mdoc.get()->m_type.is_doc()); EXPECT_EQ(mkeyval.is_doc(), mkeyval.get()->m_type.is_doc()); EXPECT_EQ(mdocval.is_doc(), mdocval.get()->m_type.is_doc()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_doc(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).is_doc(); }); + verify_assertion(t, [&](Tree const&){ return t.is_doc(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.is_doc(NONE); }); } TEST(NodeType, is_container) @@ -1212,6 +1272,11 @@ a scalar EXPECT_EQ(mseq.is_container(), mseq.get()->m_type.is_container()); EXPECT_EQ(mval.is_container(), mval.get()->m_type.is_container()); EXPECT_EQ(mdocval.is_container(), mdocval.get()->m_type.is_container()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_container(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).is_container(); }); + verify_assertion(t, [&](Tree const&){ return t.is_container(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.is_container(NONE); }); } TEST(NodeType, is_map) @@ -1296,6 +1361,11 @@ a scalar EXPECT_EQ(mseq.is_map(), mseq.get()->m_type.is_map()); EXPECT_EQ(mval.is_map(), mval.get()->m_type.is_map()); EXPECT_EQ(mdocval.is_map(), mdocval.get()->m_type.is_map()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_map(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).is_map(); }); + verify_assertion(t, [&](Tree const&){ return t.is_map(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.is_map(NONE); }); } TEST(NodeType, is_seq) @@ -1380,6 +1450,11 @@ a scalar EXPECT_EQ(mseq.is_seq(), mseq.get()->m_type.is_seq()); EXPECT_EQ(mval.is_seq(), mval.get()->m_type.is_seq()); EXPECT_EQ(mdocval.is_seq(), mdocval.get()->m_type.is_seq()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_seq(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).is_seq(); }); + verify_assertion(t, [&](Tree const&){ return t.is_seq(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.is_seq(NONE); }); } TEST(NodeType, has_val) @@ -1464,6 +1539,11 @@ a scalar EXPECT_EQ(mseq.has_val(), mseq.get()->m_type.has_val()); EXPECT_EQ(mval.has_val(), mval.get()->m_type.has_val()); EXPECT_EQ(mdocval.has_val(), mdocval.get()->m_type.has_val()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].has_val(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).has_val(); }); + verify_assertion(t, [&](Tree const&){ return t.has_val(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.has_val(NONE); }); } TEST(NodeType, is_val) @@ -1548,6 +1628,11 @@ a scalar EXPECT_EQ(mseq.is_val(), mseq.get()->m_type.is_val()); EXPECT_EQ(mval.is_val(), mval.get()->m_type.is_val()); EXPECT_EQ(mdocval.is_val(), mdocval.get()->m_type.is_val()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_val(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(1)[1].is_val(); }); + verify_assertion(t, [&](Tree const&){ return t.is_val(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.is_val(NONE); }); } TEST(NodeType, has_key) @@ -1631,6 +1716,11 @@ a scalar EXPECT_EQ(mseq.has_key(), mseq.get()->m_type.has_key()); EXPECT_EQ(mval.has_key(), mval.get()->m_type.has_key()); EXPECT_EQ(mdocval.has_key(), mdocval.get()->m_type.has_key()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].has_key(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).has_key(); }); + verify_assertion(t, [&](Tree const&){ return t.has_key(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.has_key(NONE); }); } TEST(NodeType, is_keyval) @@ -1715,6 +1805,11 @@ a scalar EXPECT_EQ(mseq.is_keyval(), mseq.get()->m_type.is_keyval()); EXPECT_EQ(mval.is_keyval(), mval.get()->m_type.is_keyval()); EXPECT_EQ(mdocval.is_keyval(), mdocval.get()->m_type.is_keyval()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_keyval(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).is_keyval(); }); + verify_assertion(t, [&](Tree const&){ return t.is_keyval(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.is_keyval(NONE); }); } TEST(NodeType, has_key_tag) @@ -1813,6 +1908,11 @@ a scalar EXPECT_EQ(mval.has_key_tag(), mval.get()->m_type.has_key_tag()); EXPECT_EQ(mvalnotag.has_key_tag(), mvalnotag.get()->m_type.has_key_tag()); EXPECT_EQ(mdocval.has_key_tag(), mdocval.get()->m_type.has_key_tag()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].has_key_tag(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).has_key_tag(); }); + verify_assertion(t, [&](Tree const&){ return t.has_key_tag(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.has_key_tag(NONE); }); } TEST(NodeType, has_val_tag) @@ -1911,6 +2011,11 @@ a scalar EXPECT_EQ(mval.has_val_tag(), mval.get()->m_type.has_val_tag()); EXPECT_EQ(mvalnotag.has_val_tag(), mvalnotag.get()->m_type.has_val_tag()); EXPECT_EQ(mdocval.has_val_tag(), mdocval.get()->m_type.has_val_tag()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].has_val_tag(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).has_val_tag(); }); + verify_assertion(t, [&](Tree const&){ return t.has_val_tag(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.has_val_tag(NONE); }); } TEST(NodeType, has_key_anchor) @@ -1998,6 +2103,11 @@ TEST(Tree, has_key_anchor) EXPECT_EQ(mseq.has_key_anchor(), mseq.get()->m_type.has_key_anchor()); EXPECT_EQ(mval.has_key_anchor(), mval.get()->m_type.has_key_anchor()); EXPECT_EQ(mvalnoanchor.has_key_anchor(), mvalnoanchor.get()->m_type.has_key_anchor()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].has_key(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(1).has_key_anchor(); }); + verify_assertion(t, [&](Tree const&){ return t.has_key_anchor(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.has_key_anchor(NONE); }); } TEST(NodeType, is_key_anchor) @@ -2085,6 +2195,11 @@ TEST(Tree, is_key_anchor) EXPECT_EQ(mseq.is_key_anchor(), mseq.get()->m_type.is_key_anchor()); EXPECT_EQ(mval.is_key_anchor(), mval.get()->m_type.is_key_anchor()); EXPECT_EQ(mvalnoanchor.is_key_anchor(), mvalnoanchor.get()->m_type.is_key_anchor()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_key_anchor(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(1).is_key_anchor(); }); + verify_assertion(t, [&](Tree const&){ return t.is_key_anchor(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.is_key_anchor(NONE); }); } TEST(NodeType, has_val_anchor) @@ -2172,6 +2287,11 @@ seq: &seqanchor [&valanchor foo, bar] EXPECT_EQ(mseq.has_val_anchor(), mseq.get()->m_type.has_val_anchor()); EXPECT_EQ(mval.has_val_anchor(), mval.get()->m_type.has_val_anchor()); EXPECT_EQ(mvalnoanchor.has_val_anchor(), mvalnoanchor.get()->m_type.has_val_anchor()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].has_val_anchor(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(1).has_val_anchor(); }); + verify_assertion(t, [&](Tree const&){ return t.has_val_anchor(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.has_val_anchor(NONE); }); } TEST(NodeType, is_val_anchor) @@ -2259,6 +2379,11 @@ seq: &seqanchor [&valanchor foo, bar] EXPECT_EQ(mseq.is_val_anchor(), mseq.get()->m_type.is_val_anchor()); EXPECT_EQ(mval.is_val_anchor(), mval.get()->m_type.is_val_anchor()); EXPECT_EQ(mvalnoanchor.is_val_anchor(), mvalnoanchor.get()->m_type.is_val_anchor()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_val_anchor(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(1).is_val_anchor(); }); + verify_assertion(t, [&](Tree const&){ return t.is_val_anchor(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.is_val_anchor(NONE); }); } TEST(NodeType, has_anchor) @@ -2351,6 +2476,8 @@ map: &mapanchor {foo: &keyvalanchor bar, anchor: none} EXPECT_EQ(mseq.has_anchor(), mseq.get()->m_type.has_anchor()); EXPECT_EQ(mval.has_anchor(), mval.get()->m_type.has_anchor()); EXPECT_EQ(mvalnoanchor.has_anchor(), mvalnoanchor.get()->m_type.has_anchor()); + verify_assertion(t, [&](Tree const&){ return t.has_anchor(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.has_anchor(NONE); }); } TEST(NodeType, is_anchor) @@ -2443,6 +2570,11 @@ map: &mapanchor {foo: &keyvalanchor bar, anchor: none} EXPECT_EQ(mseq.is_anchor(), mseq.get()->m_type.is_anchor()); EXPECT_EQ(mval.is_anchor(), mval.get()->m_type.is_anchor()); EXPECT_EQ(mvalnoanchor.is_anchor(), mvalnoanchor.get()->m_type.is_anchor()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_anchor(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).is_anchor(); }); + verify_assertion(t, [&](Tree const&){ return t.is_anchor(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.is_anchor(NONE); }); } TEST(NodeType, is_key_ref) @@ -2512,6 +2644,11 @@ TEST(Tree, is_key_ref) EXPECT_EQ(mkeyval.is_key_ref(), mkeyval.get()->m_type.is_key_ref()); EXPECT_EQ(mseq.is_key_ref(), mseq.get()->m_type.is_key_ref()); EXPECT_EQ(mval.is_key_ref(), mval.get()->m_type.is_key_ref()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_key_ref(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).is_key_ref(); }); + verify_assertion(t, [&](Tree const&){ return t.is_key_ref(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.is_key_ref(NONE); }); } TEST(NodeType, is_val_ref) @@ -2581,6 +2718,11 @@ seq: [*valref, bar] EXPECT_EQ(mkeyval.is_val_ref(), mkeyval.get()->m_type.is_val_ref()); EXPECT_EQ(mseq.is_val_ref(), mseq.get()->m_type.is_val_ref()); EXPECT_EQ(mval.is_val_ref(), mval.get()->m_type.is_val_ref()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_val_ref(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).is_val_ref(); }); + verify_assertion(t, [&](Tree const&){ return t.is_val_ref(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.is_val_ref(NONE); }); } TEST(NodeType, is_ref) @@ -2654,6 +2796,11 @@ seq: [*valref, bar] EXPECT_EQ(mkeyval.is_ref(), mkeyval.get()->m_type.is_ref()); EXPECT_EQ(mseq.is_ref(), mseq.get()->m_type.is_ref()); EXPECT_EQ(mval.is_ref(), mval.get()->m_type.is_ref()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_ref(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).is_ref(); }); + verify_assertion(t, [&](Tree const&){ return t.is_ref(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.is_ref(NONE); }); } TEST(NodeType, is_anchor_or_ref) @@ -2733,6 +2880,11 @@ seq: &seq [*valref, bar] EXPECT_EQ(mkeyval.is_anchor_or_ref(), mkeyval.get()->m_type.is_anchor_or_ref()); EXPECT_EQ(mseq.is_anchor_or_ref(), mseq.get()->m_type.is_anchor_or_ref()); EXPECT_EQ(mval.is_anchor_or_ref(), mval.get()->m_type.is_anchor_or_ref()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_anchor_or_ref(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).is_anchor_or_ref(); }); + verify_assertion(t, [&](Tree const&){ return t.is_anchor_or_ref(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.is_anchor_or_ref(NONE); }); } TEST(NodeType, is_key_quoted) @@ -2775,6 +2927,11 @@ notquoted: bar EXPECT_EQ(mmap.is_key_quoted(), mmap.get()->m_type.is_key_quoted()); EXPECT_EQ(mquoted.is_key_quoted(), mquoted.get()->m_type.is_key_quoted()); EXPECT_EQ(mnotquoted.is_key_quoted(), mnotquoted.get()->m_type.is_key_quoted()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_key_quoted(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).is_key_quoted(); }); + verify_assertion(t, [&](Tree const&){ return t.is_key_quoted(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.is_key_quoted(NONE); }); } TEST(NodeType, is_val_quoted) @@ -2817,6 +2974,11 @@ notquoted: bar EXPECT_EQ(mmap.is_val_quoted(), mmap.get()->m_type.is_val_quoted()); EXPECT_EQ(mquoted.is_val_quoted(), mquoted.get()->m_type.is_val_quoted()); EXPECT_EQ(mnotquoted.is_val_quoted(), mnotquoted.get()->m_type.is_val_quoted()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_val_quoted(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).is_val_quoted(); }); + verify_assertion(t, [&](Tree const&){ return t.is_val_quoted(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.is_val_quoted(NONE); }); } TEST(NodeType, is_quoted) @@ -2914,6 +3076,11 @@ notquoted: bar EXPECT_EQ(mquoted5.is_quoted(), mquoted5.get()->m_type.is_quoted()); EXPECT_EQ(mquoted6.is_quoted(), mquoted6.get()->m_type.is_quoted()); EXPECT_EQ(mnotquoted.is_quoted(), mnotquoted.get()->m_type.is_quoted()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_quoted(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).is_quoted(); }); + verify_assertion(t, [&](Tree const&){ return t.is_quoted(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.is_quoted(NONE); }); } @@ -2968,6 +3135,11 @@ seq: &seq [*valref, bar] EXPECT_EQ(t.parent_is_seq(keyval_id), mkeyval.parent_is_seq()); EXPECT_EQ(t.parent_is_seq(seq_id), mseq.parent_is_seq()); EXPECT_EQ(t.parent_is_seq(val_id), mval.parent_is_seq()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].parent_is_seq(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).parent_is_seq(); }); + verify_assertion(t, [&](Tree const&){ return t.parent_is_seq(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.parent_is_seq(NONE); }); } TEST(Tree, parent_is_map) @@ -3022,6 +3194,11 @@ seq: &seq [*valref, bar] EXPECT_EQ(t.parent_is_map(keyval_id), mkeyval.parent_is_map()); EXPECT_EQ(t.parent_is_map(seq_id), mseq.parent_is_map()); EXPECT_EQ(t.parent_is_map(val_id), mval.parent_is_map()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].parent_is_map(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).parent_is_map(); }); + verify_assertion(t, [&](Tree const&){ return t.parent_is_map(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.parent_is_map(NONE); }); } TEST(Tree, has_parent) @@ -3078,6 +3255,11 @@ seq: &seq [*valref, bar] EXPECT_EQ(t.has_parent(keyval_id), mkeyval.has_parent()); EXPECT_EQ(t.has_parent(seq_id), mseq.has_parent()); EXPECT_EQ(t.has_parent(val_id), mval.has_parent()); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].has_parent(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).has_parent(); }); + verify_assertion(t, [&](Tree const&){ return t.has_parent(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.has_parent(NONE); }); } @@ -3126,6 +3308,11 @@ seq: &seq [*valref, bar] EXPECT_EQ(mkeyval.num_children(), t.num_children(keyval_id)); EXPECT_EQ(mseq.num_children(), t.num_children(seq_id)); EXPECT_EQ(mval.num_children(), t.num_children(val_id)); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].num_children(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).num_children(); }); + verify_assertion(t, [&](Tree const&){ return t.num_children(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.num_children(NONE); }); } TEST(Tree, child) @@ -3170,6 +3357,11 @@ seq: &seq [*valref, bar] EXPECT_EQ(mkeyval.child(0).id(), t.child(keyval_id, 0)); EXPECT_EQ(mseq.child(0).id(), t.child(seq_id, 0)); EXPECT_EQ(mval.child(0).id(), t.child(val_id, 0)); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].child(0); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).child(0); }); + verify_assertion(t, [&](Tree const&){ return t.child(t.capacity(), 0); }); + verify_assertion(t, [&](Tree const&){ return t.child(NONE, 0); }); } TEST(Tree, find_child_by_name) @@ -3202,6 +3394,11 @@ seq: &seq [*valref, bar] EXPECT_EQ(mdoc.find_child("...").id(), t.find_child(doc_id, "...")); EXPECT_EQ(mmap.find_child("foo").id(), t.find_child(map_id, "foo")); EXPECT_EQ(mmap.find_child("bar").id(), t.find_child(map_id, "bar")); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].find_child("foo"); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).find_child("foo"); }); + verify_assertion(t, [&](Tree const&){ return t.find_child(t.capacity(), "foo"); }); + verify_assertion(t, [&](Tree const&){ return t.find_child(NONE, "foo"); }); } @@ -3214,6 +3411,7 @@ TEST(change_type, from_val) t[0].change_type(VAL); t[1].change_type(MAP); t[2].change_type(SEQ); + verify_assertion(t, [&](Tree const&){ return t[3].change_type(VAL); }); Tree expected = parse_in_arena("[val0, {}, []]"); EXPECT_EQ(emitrs_yaml(t), emitrs_yaml(expected)); } @@ -3223,6 +3421,7 @@ TEST(change_type, from_keyval) t[0].change_type(VAL); t[1].change_type(MAP); t[2].change_type(SEQ); + verify_assertion(t, [&](Tree const&){ return t[3].change_type(VAL); }); Tree expected = parse_in_arena("{keyval0: val0, keyval1: {}, keyval2: []}"); EXPECT_EQ(emitrs_yaml(t), emitrs_yaml(expected)); } @@ -3233,6 +3432,7 @@ TEST(change_type, from_map) t[0].change_type(VAL); t[1].change_type(MAP); t[2].change_type(SEQ); + verify_assertion(t, [&](Tree const&){ return t[3].change_type(VAL); }); EXPECT_FALSE(t[0].val_is_null()); EXPECT_NE(t[0].val(), nullptr); Tree expected = parse_in_arena("['', {map1: {map1key0: a, map1key1: b}}, []]"); @@ -3244,6 +3444,7 @@ TEST(change_type, from_keymap) t[0].change_type(VAL); t[1].change_type(MAP); t[2].change_type(SEQ); + verify_assertion(t, [&](Tree const&){ return t[3].change_type(VAL); }); EXPECT_FALSE(t[0].val_is_null()); EXPECT_NE(t[0].val(), nullptr); Tree expected = parse_in_arena("{map0: '', map1: {map1: {map1key0: a, map1key1: b}}, map2: []}"); @@ -3256,6 +3457,7 @@ TEST(change_type, from_seq) t[0].change_type(VAL); t[1].change_type(MAP); t[2].change_type(SEQ); + verify_assertion(t, [&](Tree const&){ return t[3].change_type(VAL); }); EXPECT_FALSE(t[0].val_is_null()); EXPECT_NE(t[0].val(), nullptr); Tree expected = parse_in_arena("['', {}, [seq20, seq21]]"); @@ -3267,6 +3469,7 @@ TEST(change_type, from_keyseq) t[0].change_type(VAL); t[1].change_type(MAP); t[2].change_type(SEQ); + verify_assertion(t, [&](Tree const&){ return t[3].change_type(VAL); }); EXPECT_FALSE(t[0].val_is_null()); EXPECT_NE(t[0].val(), nullptr); Tree expected = parse_in_arena("{map0: '', map1: {}, map2: [seq20, seq21]}"); From 4029825a4cc1ff9b2318a2af230f8c8ee48f0799 Mon Sep 17 00:00:00 2001 From: Neko Box Coder Date: Wed, 3 Apr 2024 02:49:54 +0100 Subject: [PATCH 06/15] Replacing noexcept to RYML_NOEXCEPT for stack and tree --- src/c4/yml/detail/stack.hpp | 4 ++-- src/c4/yml/tree.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/c4/yml/detail/stack.hpp b/src/c4/yml/detail/stack.hpp index a3f060d70..c12b03b99 100644 --- a/src/c4/yml/detail/stack.hpp +++ b/src/c4/yml/detail/stack.hpp @@ -51,7 +51,7 @@ class stack _free(); } - stack(stack const& that) noexcept : stack(that.m_callbacks) + stack(stack const& that) RYML_NOEXCEPT : stack(that.m_callbacks) { resize(that.m_size); _cp(&that); @@ -62,7 +62,7 @@ class stack _mv(&that); } - stack& operator= (stack const& that) noexcept + stack& operator= (stack const& that) RYML_NOEXCEPT { _cb(that.m_callbacks); resize(that.m_size); diff --git a/src/c4/yml/tree.hpp b/src/c4/yml/tree.hpp index da7e9a046..093d3b8a6 100644 --- a/src/c4/yml/tree.hpp +++ b/src/c4/yml/tree.hpp @@ -339,7 +339,7 @@ struct NodeScalar void clear() noexcept { tag.clear(); scalar.clear(); anchor.clear(); } - void set_ref_maybe_replacing_scalar(csubstr ref, bool has_scalar) noexcept + void set_ref_maybe_replacing_scalar(csubstr ref, bool has_scalar) RYML_NOEXCEPT { csubstr trimmed = ref.begins_with('*') ? ref.sub(1) : ref; anchor = trimmed; From b7bbad566e1aee9378d31ee1e50a44e905fb051d Mon Sep 17 00:00:00 2001 From: Neko Box Coder Date: Wed, 3 Apr 2024 03:41:51 +0100 Subject: [PATCH 07/15] Adding tests to at() --- test/test_tree.cpp | 87 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/test/test_tree.cpp b/test/test_tree.cpp index 8f14eae2a..637a33ede 100644 --- a/test/test_tree.cpp +++ b/test/test_tree.cpp @@ -987,6 +987,93 @@ TEST(Tree, operator_square_brackets) } } +TEST(Tree, noderef_at) +{ + { + Tree t = parse_in_arena("[0, 1, 2, 3, 4]"); + NodeRef m = t.rootref(); + ConstNodeRef const cm = t.rootref(); + EXPECT_EQ(m.at(0).val(), "0"); + EXPECT_EQ(m.at(1).val(), "1"); + EXPECT_EQ(m.at(2).val(), "2"); + EXPECT_EQ(m.at(3).val(), "3"); + EXPECT_EQ(m.at(4).val(), "4"); + EXPECT_EQ(cm.at(0).val(), "0"); + EXPECT_EQ(cm.at(1).val(), "1"); + EXPECT_EQ(cm.at(2).val(), "2"); + EXPECT_EQ(cm.at(3).val(), "3"); + EXPECT_EQ(cm.at(4).val(), "4"); + // + EXPECT_TRUE(m.at(0) == "0"); + EXPECT_TRUE(m.at(1) == "1"); + EXPECT_TRUE(m.at(2) == "2"); + EXPECT_TRUE(m.at(3) == "3"); + EXPECT_TRUE(m.at(4) == "4"); + EXPECT_TRUE(cm.at(0) == "0"); + EXPECT_TRUE(cm.at(1) == "1"); + EXPECT_TRUE(cm.at(2) == "2"); + EXPECT_TRUE(cm.at(3) == "3"); + EXPECT_TRUE(cm.at(4) == "4"); + // + EXPECT_FALSE(m.at(0) != "0"); + EXPECT_FALSE(m.at(1) != "1"); + EXPECT_FALSE(m.at(2) != "2"); + EXPECT_FALSE(m.at(3) != "3"); + EXPECT_FALSE(m.at(4) != "4"); + EXPECT_FALSE(cm.at(0) != "0"); + EXPECT_FALSE(cm.at(1) != "1"); + EXPECT_FALSE(cm.at(2) != "2"); + EXPECT_FALSE(cm.at(3) != "3"); + EXPECT_FALSE(cm.at(4) != "4"); + // + //TODO: Not sure what to replace with capacity + //verify_assertion(t, [&](Tree const&){ return cm[m.capacity()]; }); + verify_assertion(t, [&](Tree const&){ return cm.at(NONE); }); + verify_assertion(t, [&](Tree const&){ return cm.at(0).at(0); }); + verify_assertion(t, [&](Tree const&){ return cm.at("a"); }); + } + { + Tree t = parse_in_arena("{a: 0, b: 1, c: 2, d: 3, e: 4}"); + NodeRef m = t.rootref(); + ConstNodeRef const cm = t.rootref(); + EXPECT_EQ(m.at("a").val(), "0"); + EXPECT_EQ(m.at("b").val(), "1"); + EXPECT_EQ(m.at("c").val(), "2"); + EXPECT_EQ(m.at("d").val(), "3"); + EXPECT_EQ(m.at("e").val(), "4"); + EXPECT_EQ(cm.at("a").val(), "0"); + EXPECT_EQ(cm.at("b").val(), "1"); + EXPECT_EQ(cm.at("c").val(), "2"); + EXPECT_EQ(cm.at("d").val(), "3"); + EXPECT_EQ(cm.at("e").val(), "4"); + // + EXPECT_TRUE(m.at("a") == "0"); + EXPECT_TRUE(m.at("b") == "1"); + EXPECT_TRUE(m.at("c") == "2"); + EXPECT_TRUE(m.at("d") == "3"); + EXPECT_TRUE(m.at("e") == "4"); + EXPECT_TRUE(cm.at("a") == "0"); + EXPECT_TRUE(cm.at("b") == "1"); + EXPECT_TRUE(cm.at("c") == "2"); + EXPECT_TRUE(cm.at("d") == "3"); + EXPECT_TRUE(cm.at("e") == "4"); + // + EXPECT_FALSE(m.at("a") != "0"); + EXPECT_FALSE(m.at("b") != "1"); + EXPECT_FALSE(m.at("c") != "2"); + EXPECT_FALSE(m.at("d") != "3"); + EXPECT_FALSE(m.at("e") != "4"); + EXPECT_FALSE(cm.at("a") != "0"); + EXPECT_FALSE(cm.at("b") != "1"); + EXPECT_FALSE(cm.at("c") != "2"); + EXPECT_FALSE(cm.at("d") != "3"); + EXPECT_FALSE(cm.at("e") != "4"); + // + verify_assertion(t, [&](Tree const&){ return cm.at("f"); }); + verify_assertion(t, [&](Tree const&){ return cm.at("g").at("h"); }); + } +} + TEST(Tree, relocate) { // create a tree with anchors and refs, and copy it to ensure the From c6ce0ddec3ef417a34604747a4587f7c50094c57 Mon Sep 17 00:00:00 2001 From: Neko Box Coder Date: Wed, 3 Apr 2024 11:46:37 +0100 Subject: [PATCH 08/15] Adding verify_error --- test/test_case.cpp | 5 +++++ test/test_case.hpp | 2 ++ test/test_tree.cpp | 28 ++++++++++++++++++++++------ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/test/test_case.cpp b/test/test_case.cpp index bdde31463..458a7f62a 100644 --- a/test/test_case.cpp +++ b/test/test_case.cpp @@ -269,6 +269,11 @@ void ExpectError::check_assertion(Tree *tree, std::function fn, Location #endif } +void ExpectError::verify_error(Tree *tree, std::function fn, Location expected_location) +{ + ExpectError::do_check(tree, fn, expected_location); +} + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- diff --git a/test/test_case.hpp b/test/test_case.hpp index 7b4a3a487..e6741c7a1 100644 --- a/test/test_case.hpp +++ b/test/test_case.hpp @@ -175,6 +175,8 @@ struct ExpectError static void check_assertion(Tree *tree, std::function fn, Location expected={}); static void check_success(std::function fn) { check_success(nullptr, fn); } static void check_success(Tree *tree, std::function fn); + static void verify_error( std::function fn, Location expected={}) { verify_error(nullptr, fn, expected); } + static void verify_error(Tree *tree, std::function fn, Location expected={}); }; diff --git a/test/test_tree.cpp b/test/test_tree.cpp index 637a33ede..f067c807b 100644 --- a/test/test_tree.cpp +++ b/test/test_tree.cpp @@ -848,6 +848,22 @@ void verify_assertion(csubstr src, Function &&fn) }); } +template +void verify_error(Tree &tree, Function &&fn) +{ + ExpectError::verify_error(&tree, [&]{ + (void)fn(tree); + }); +} +template +void verify_error(csubstr src, Function &&fn) +{ + Tree tree = parse_in_arena(src); + ExpectError::verify_error(&tree, [&]{ + (void)fn(tree); + }); +} + TEST(Tree, ref) { @@ -1027,10 +1043,10 @@ TEST(Tree, noderef_at) EXPECT_FALSE(cm.at(4) != "4"); // //TODO: Not sure what to replace with capacity - //verify_assertion(t, [&](Tree const&){ return cm[m.capacity()]; }); - verify_assertion(t, [&](Tree const&){ return cm.at(NONE); }); - verify_assertion(t, [&](Tree const&){ return cm.at(0).at(0); }); - verify_assertion(t, [&](Tree const&){ return cm.at("a"); }); + //verify_error(t, [&](Tree const&){ return cm[m.capacity()]; }); + verify_error(t, [&](Tree const&){ return cm.at(NONE); }); + verify_error(t, [&](Tree const&){ return cm.at(0).at(0); }); + verify_error(t, [&](Tree const&){ return cm.at("a"); }); } { Tree t = parse_in_arena("{a: 0, b: 1, c: 2, d: 3, e: 4}"); @@ -1069,8 +1085,8 @@ TEST(Tree, noderef_at) EXPECT_FALSE(cm.at("d") != "3"); EXPECT_FALSE(cm.at("e") != "4"); // - verify_assertion(t, [&](Tree const&){ return cm.at("f"); }); - verify_assertion(t, [&](Tree const&){ return cm.at("g").at("h"); }); + verify_error(t, [&](Tree const&){ return cm.at("f"); }); + verify_error(t, [&](Tree const&){ return cm.at("g").at("h"); }); } } From 934afd260c4d49b43dd6719cfe12b6fd08b0dbb9 Mon Sep 17 00:00:00 2001 From: Neko Box Coder Date: Wed, 3 Apr 2024 16:39:59 +0100 Subject: [PATCH 09/15] Removing verify_error in test_case --- test/test_case.cpp | 5 ----- test/test_case.hpp | 2 -- 2 files changed, 7 deletions(-) diff --git a/test/test_case.cpp b/test/test_case.cpp index 458a7f62a..bdde31463 100644 --- a/test/test_case.cpp +++ b/test/test_case.cpp @@ -269,11 +269,6 @@ void ExpectError::check_assertion(Tree *tree, std::function fn, Location #endif } -void ExpectError::verify_error(Tree *tree, std::function fn, Location expected_location) -{ - ExpectError::do_check(tree, fn, expected_location); -} - //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- diff --git a/test/test_case.hpp b/test/test_case.hpp index e6741c7a1..7b4a3a487 100644 --- a/test/test_case.hpp +++ b/test/test_case.hpp @@ -175,8 +175,6 @@ struct ExpectError static void check_assertion(Tree *tree, std::function fn, Location expected={}); static void check_success(std::function fn) { check_success(nullptr, fn); } static void check_success(Tree *tree, std::function fn); - static void verify_error( std::function fn, Location expected={}) { verify_error(nullptr, fn, expected); } - static void verify_error(Tree *tree, std::function fn, Location expected={}); }; From f66faf869d250abe0a974505d1bf235f6e23007c Mon Sep 17 00:00:00 2001 From: Neko Box Coder Date: Wed, 3 Apr 2024 16:40:22 +0100 Subject: [PATCH 10/15] Updating test_tree verify_error() to call do_check() directly --- test/test_tree.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_tree.cpp b/test/test_tree.cpp index f067c807b..bdbbe8dbf 100644 --- a/test/test_tree.cpp +++ b/test/test_tree.cpp @@ -851,7 +851,7 @@ void verify_assertion(csubstr src, Function &&fn) template void verify_error(Tree &tree, Function &&fn) { - ExpectError::verify_error(&tree, [&]{ + ExpectError::do_check(&tree, [&]{ (void)fn(tree); }); } @@ -859,7 +859,7 @@ template void verify_error(csubstr src, Function &&fn) { Tree tree = parse_in_arena(src); - ExpectError::verify_error(&tree, [&]{ + ExpectError::do_check(&tree, [&]{ (void)fn(tree); }); } From dbaf6f302bb3bbaa98def89832b4d5873744d424 Mon Sep 17 00:00:00 2001 From: Joao Paulo Magalhaes Date: Thu, 4 Apr 2024 17:07:08 +0100 Subject: [PATCH 11/15] update README with the current quickstart --- README.md | 511 +++++++++++++++++++++++++++++++++++++++++++ changelog/current.md | 9 +- 2 files changed, 518 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dfaea6740..1706408d0 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,514 @@ easily build and run this executable using any of the build samples, eg the [`add_subdirectory()` sample](samples/add_subdirectory/). ```c++ +// Parse YAML code in place, potentially mutating the buffer: +char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}"; +ryml::Tree tree = ryml::parse_in_place(yml_buf); + +// The resulting tree contains only views to the parsed string. If +// the string was parsed in place, then the string must outlive +// the tree! This works in this case because `yml_buf` and `tree` +// live on the same scope, so have the same lifetime. + +// It is also possible to: +// +// - parse a read-only buffer using parse_in_arena(). This +// copies the YAML buffer to the tree's arena, and spares the +// headache of the string's lifetime. +// +// - reuse an existing tree (advised) +// +// - reuse an existing parser (advised) +// +// Note: it will always be significantly faster to parse in place +// and reuse tree+parser. +// +// Below you will find samples that show how to achieve reuse; but +// please note that for brevity and clarity, many of the examples +// here are parsing in the arena, and not reusing tree or parser. + + +//------------------------------------------------------------------ +// API overview + +// ryml has a two-level API: +// +// The lower level index API is based on the indices of nodes, +// where the node's id is the node's position in the tree's data +// array. This API is very efficient, but somewhat difficult to use: +size_t root_id = tree.root_id(); +size_t bar_id = tree.find_child(root_id, "bar"); // need to get the index right +CHECK(tree.is_map(root_id)); // all of the index methods are in the tree +CHECK(tree.is_seq(bar_id)); // ... and receive the subject index + +// The node API is a lightweight abstraction sitting on top of the +// index API, but offering a much more convenient interaction: +ryml::ConstNodeRef root = tree.rootref(); // a const node reference +ryml::ConstNodeRef bar = tree["bar"]; +CHECK(root.is_map()); +CHECK(bar.is_seq()); + +// A node ref is a lightweight handle to the tree and associated id: +CHECK(root.tree() == &tree); // a node ref points at its tree, WITHOUT refcount +CHECK(root.id() == root_id); // a node ref's id is the index of the node +CHECK(bar.id() == bar_id); // a node ref's id is the index of the node + +// The node API translates very cleanly to the index API, so most +// of the code examples below are using the node API. + +// WARNING. A node ref holds a raw pointer to the tree. Care must +// be taken to ensure the lifetimes match, so that a node will +// never access the tree after the goes out of scope. + + +//------------------------------------------------------------------ +// To read the parsed tree + +// ConstNodeRef::operator[] does a lookup, is O(num_children[node]). +CHECK(tree["foo"].is_keyval()); +CHECK(tree["foo"].val() == "1"); // get the val of a node (must be leaf node, otherwise it is a container and has no val) +CHECK(tree["foo"].key() == "foo"); // get the key of a node (must be child of a map, otherwise it has no key) +CHECK(tree["bar"].is_seq()); +CHECK(tree["bar"].has_key()); +CHECK(tree["bar"].key() == "bar"); +// maps use string keys, seqs use index keys: +CHECK(tree["bar"][0].val() == "2"); +CHECK(tree["bar"][1].val() == "3"); +CHECK(tree["john"].val() == "doe"); +// An index key is the position of the child within its parent, +// so even maps can also use int keys, if the key position is +// known. +CHECK(tree[0].id() == tree["foo"].id()); +CHECK(tree[1].id() == tree["bar"].id()); +CHECK(tree[2].id() == tree["john"].id()); +// Tree::operator[](int) searches a ***root*** child by its position. +CHECK(tree[0].id() == tree["foo"].id()); // 0: first child of root +CHECK(tree[1].id() == tree["bar"].id()); // 1: second child of root +CHECK(tree[2].id() == tree["john"].id()); // 2: third child of root +// NodeRef::operator[](int) searches a ***node*** child by its position: +CHECK(bar[0].val() == "2"); // 0 means first child of bar +CHECK(bar[1].val() == "3"); // 1 means second child of bar +// NodeRef::operator[](string): +// A string key is the key of the node: lookup is by name. So it +// is only available for maps, and it is NOT available for seqs, +// since seq members do not have keys. +CHECK(tree["foo"].key() == "foo"); +CHECK(tree["bar"].key() == "bar"); +CHECK(tree["john"].key() == "john"); +CHECK(bar.is_seq()); +// CHECK(bar["BOOM!"].is_seed()); // error, seqs do not have key lookup + +// Note that maps can also use index keys as well as string keys: +CHECK(root["foo"].id() == root[0].id()); +CHECK(root["bar"].id() == root[1].id()); +CHECK(root["john"].id() == root[2].id()); + +// IMPORTANT. The ryml tree uses an index-based linked list for +// storing children, so the complexity of +// `Tree::operator[csubstr]` and `Tree::operator[size_t]` is O(n), +// linear on the number of root children. If you use +// `Tree::operator[]` with a large tree where the root has many +// children, you will see a performance hit. +// +// To avoid this hit, you can create your own accelerator +// structure. For example, before doing a lookup, do a single +// traverse at the root level to fill an `map` +// mapping key names to node indices; with a node index, a lookup +// (via `Tree::get()`) is O(1), so this way you can get O(log n) +// lookup from a key. (But please do not use `std::map` if you +// care about performance; use something else like a flat map or +// sorted vector). +// +// As for node refs, the difference from `NodeRef::operator[]` and +// `ConstNodeRef::operator[]` to `Tree::operator[]` is that the +// latter refers to the root node, whereas the former are invoked +// on their target node. But the lookup process works the same for +// both and their algorithmic complexity is the same: they are +// both linear in the number of direct children. But of course, +// depending on the data, that number may be very different from +// one to another. + + +//------------------------------------------------------------------ +// Hierarchy: + +{ + ryml::ConstNodeRef foo = root.first_child(); + ryml::ConstNodeRef john = root.last_child(); + CHECK(tree.size() == 6); // O(1) number of nodes in the tree + CHECK(root.num_children() == 3); // O(num_children[root]) + CHECK(foo.num_siblings() == 3); // O(num_children[parent(foo)]) + CHECK(foo.parent().id() == root.id()); // parent() is O(1) + CHECK(root.first_child().id() == root["foo"].id()); // first_child() is O(1) + CHECK(root.last_child().id() == root["john"].id()); // last_child() is O(1) + CHECK(john.first_sibling().id() == foo.id()); + CHECK(foo.last_sibling().id() == john.id()); + // prev_sibling(), next_sibling(): (both are O(1)) + CHECK(foo.num_siblings() == root.num_children()); + CHECK(foo.prev_sibling().id() == ryml::NONE); // foo is the first_child() + CHECK(foo.next_sibling().key() == "bar"); + CHECK(foo.next_sibling().next_sibling().key() == "john"); + CHECK(foo.next_sibling().next_sibling().next_sibling().id() == ryml::NONE); // john is the last_child() +} + + +//------------------------------------------------------------------ +// Iterating: +{ + ryml::csubstr expected_keys[] = {"foo", "bar", "john"}; + // iterate children using the high-level node API: + { + size_t count = 0; + for(ryml::ConstNodeRef const& child : root.children()) + CHECK(child.key() == expected_keys[count++]); + } + // iterate siblings using the high-level node API: + { + size_t count = 0; + for(ryml::ConstNodeRef const& child : root["foo"].siblings()) + CHECK(child.key() == expected_keys[count++]); + } + // iterate children using the lower-level tree index API: + { + size_t count = 0; + for(size_t child_id = tree.first_child(root_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id)) + CHECK(tree.key(child_id) == expected_keys[count++]); + } + // iterate siblings using the lower-level tree index API: + // (notice the only difference from above is in the loop + // preamble, which calls tree.first_sibling(bar_id) instead of + // tree.first_child(root_id)) + { + size_t count = 0; + for(size_t child_id = tree.first_sibling(bar_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id)) + CHECK(tree.key(child_id) == expected_keys[count++]); + } +} + + +//------------------------------------------------------------------ +// Gotchas: + +// ryml uses assertions to prevent you from trying to obtain +// things that do not exist. For example: + +{ + ryml::ConstNodeRef seq_node = tree["bar"]; + ryml::ConstNodeRef val_node = seq_node[0]; + + CHECK(seq_node.is_seq()); // seq is a container + CHECK(!seq_node.has_val()); // ... so it has no val + //CHECK(seq_node.val() == BOOM!); // ... so attempting to get a val is undefined behavior + + CHECK(val_node.parent() == seq_node); // belongs to a seq + CHECK(!val_node.has_key()); // ... so it has no key + //CHECK(val_node.key() == BOOM!); // ... so attempting to get a key is undefined behavior + + CHECK(val_node.is_val()); // this node is a val + //CHECK(val_node.first_child() == BOOM!); // ... so attempting to get a child is undefined behavior + + // assertions are also present in methods that /may/ read the val: + CHECK(seq_node.is_seq()); // seq is a container + //CHECK(seq_node.val_is_null() BOOM!); // so cannot get the val to check +} + + +// By default, assertions are enabled unless the NDEBUG macro is +// defined (which happens in release builds). +// +// This adheres to the pay-only-for-what-you-use philosophy: if +// you are sure that your intent is correct, why would you need to +// pay the runtime cost for the assertions? +// +// The downside, of course, is that when you are not sure, release +// builds may be doing something crazy. +// +// So you can override this behavior and enable/disable +// assertions, by defining the macro RYML_USE_ASSERT to a proper +// value (see c4/yml/common.hpp). +// +// Also, to be clear, this does not apply to parse errors +// happening when the YAML is parsed. Checking for these errors is +// always enabled and cannot be switched off. + + +//------------------------------------------------------------------ +// Deserializing: use operator>> +{ + int foo = 0, bar0 = 0, bar1 = 0; + std::string john_str; + std::string bar_str; + root["foo"] >> foo; + root["bar"][0] >> bar0; + root["bar"][1] >> bar1; + root["john"] >> john_str; // requires from_chars(std::string). see serialization samples below. + root["bar"] >> ryml::key(bar_str); // to deserialize the key, use the tag function ryml::key() + CHECK(foo == 1); + CHECK(bar0 == 2); + CHECK(bar1 == 3); + CHECK(john_str == "doe"); + CHECK(bar_str == "bar"); +} + + +//------------------------------------------------------------------ +// Modifying existing nodes: operator= vs operator<< + +// As implied by its name, ConstNodeRef is a reference to a const +// node. It can be used to read from the node, but not write to it +// or modify the hierarchy of the node. If any modification is +// desired then a NodeRef must be used instead: +ryml::NodeRef wroot = tree.rootref(); + +// operator= assigns an existing string to the receiving node. +// The contents are NOT copied, and this pointer will be in effect +// until the tree goes out of scope! So BEWARE to only assign from +// strings outliving the tree. +wroot["foo"] = "says you"; +wroot["bar"][0] = "-2"; +wroot["bar"][1] = "-3"; +wroot["john"] = "ron"; +// Now the tree is _pointing_ at the memory of the strings above. +// In this case it is OK because those are static strings and will +// outlive the tree. +CHECK(root["foo"].val() == "says you"); +CHECK(root["bar"][0].val() == "-2"); +CHECK(root["bar"][1].val() == "-3"); +CHECK(root["john"].val() == "ron"); +// But WATCHOUT: do not assign from temporary objects: +// { +// std::string crash("will dangle"); +// root["john"] = ryml::to_csubstr(crash); +// } +// CHECK(root["john"] == "dangling"); // CRASH! the string was deallocated + +// operator<< first serializes the input to the tree's arena, then +// assigns the serialized string to the receiving node. This avoids +// constraints with the lifetime, since the arena lives with the tree. +CHECK(tree.arena().empty()); +wroot["foo"] << "says who"; // requires to_chars(). see serialization samples below. +wroot["bar"][0] << 20; +wroot["bar"][1] << 30; +wroot["john"] << "deere"; +CHECK(root["foo"].val() == "says who"); +CHECK(root["bar"][0].val() == "20"); +CHECK(root["bar"][1].val() == "30"); +CHECK(root["john"].val() == "deere"); +CHECK(tree.arena() == "says who2030deere"); // the result of serializations to the tree arena +// using operator<< instead of operator=, the crash above is avoided: +{ + std::string ok("in_scope"); + // root["john"] = ryml::to_csubstr(ok); // don't, will dangle + wroot["john"] << ryml::to_csubstr(ok); // OK, copy to the tree's arena +} +CHECK(root["john"] == "in_scope"); // OK! +// serializing floating points: +wroot["float"] << 2.4; +// to force a particular precision or float format: +// (see sample_float_precision() and sample_formatting()) +wroot["digits"] << ryml::fmt::real(2.4, /*num_digits*/6, ryml::FTOA_FLOAT); +CHECK(tree.arena() == "says who2030deerein_scope2.42.400000"); // the result of serializations to the tree arena + + +//------------------------------------------------------------------ +// Adding new nodes: + +// adding a keyval node to a map: +CHECK(root.num_children() == 5); +wroot["newkeyval"] = "shiny and new"; // using these strings +wroot.append_child() << ryml::key("newkeyval (serialized)") << "shiny and new (serialized)"; // serializes and assigns the serialization +CHECK(root.num_children() == 7); +CHECK(root["newkeyval"].key() == "newkeyval"); +CHECK(root["newkeyval"].val() == "shiny and new"); +CHECK(root["newkeyval (serialized)"].key() == "newkeyval (serialized)"); +CHECK(root["newkeyval (serialized)"].val() == "shiny and new (serialized)"); +CHECK( ! tree.in_arena(root["newkeyval"].key())); // it's using directly the static string above +CHECK( ! tree.in_arena(root["newkeyval"].val())); // it's using directly the static string above +CHECK( tree.in_arena(root["newkeyval (serialized)"].key())); // it's using a serialization of the string above +CHECK( tree.in_arena(root["newkeyval (serialized)"].val())); // it's using a serialization of the string above +// adding a val node to a seq: +CHECK(root["bar"].num_children() == 2); +wroot["bar"][2] = "oh so nice"; +wroot["bar"][3] << "oh so nice (serialized)"; +CHECK(root["bar"].num_children() == 4); +CHECK(root["bar"][2].val() == "oh so nice"); +CHECK(root["bar"][3].val() == "oh so nice (serialized)"); +// adding a seq node: +CHECK(root.num_children() == 7); +wroot["newseq"] |= ryml::SEQ; +wroot.append_child() << ryml::key("newseq (serialized)") |= ryml::SEQ; +CHECK(root.num_children() == 9); +CHECK(root["newseq"].num_children() == 0); +CHECK(root["newseq"].is_seq()); +CHECK(root["newseq (serialized)"].num_children() == 0); +CHECK(root["newseq (serialized)"].is_seq()); +// adding a map node: +CHECK(root.num_children() == 9); +wroot["newmap"] |= ryml::MAP; +wroot.append_child() << ryml::key("newmap (serialized)") |= ryml::MAP; +CHECK(root.num_children() == 11); +CHECK(root["newmap"].num_children() == 0); +CHECK(root["newmap"].is_map()); +CHECK(root["newmap (serialized)"].num_children() == 0); +CHECK(root["newmap (serialized)"].is_map()); +// +// When the tree is mutable, operator[] does not mutate the tree +// until the returned node is written to. +// +// Until such time, the NodeRef object keeps in itself the required +// information to write to the proper place in the tree. This is +// called being in a "seed" state. +// +// This means that passing a key/index which does not exist will +// not mutate the tree, but will instead store (in the node) the +// proper place of the tree to be able to do so, if and when it is +// required. +// +// This is a significant difference from eg, the behavior of +// std::map, which mutates the map immediately within the call to +// operator[]. +// +// All of the points above apply only if the tree is mutable. If +// the tree is const, then a NodeRef cannot be obtained from it; +// only a ConstNodeRef, which can never be used to mutate the +// tree. +CHECK(!root.has_child("I am not nothing")); +ryml::NodeRef nothing = wroot["I am nothing"]; +CHECK(nothing.valid()); // points at the tree, and a specific place in the tree +CHECK(nothing.is_seed()); // ... but nothing is there yet. +CHECK(!root.has_child("I am nothing")); // same as above +ryml::NodeRef something = wroot["I am something"]; +ryml::ConstNodeRef constsomething = wroot["I am something"]; +CHECK(!root.has_child("I am something")); // same as above +CHECK(something.valid()); +CHECK(something.is_seed()); // same as above +CHECK(!constsomething.valid()); // NOTE: because a ConstNodeRef + // cannot be used to mutate a + // tree, it is only valid() if it + // is pointing at an existing + // node. +something = "indeed"; // this will commit to the tree, mutating at the proper place +CHECK(root.has_child("I am something")); +CHECK(root["I am something"].val() == "indeed"); +CHECK(something.valid()); +CHECK(!something.is_seed()); // now the tree has this node, so the + // ref is no longer a seed +// now the constref is also valid (but it needs to be reassigned): +ryml::ConstNodeRef constsomethingnew = wroot["I am something"]; +CHECK(constsomethingnew.valid()); +// note that the old constref is now stale, because it only keeps +// the state at creation: +CHECK(!constsomething.valid()); + + +//------------------------------------------------------------------ +// Emitting: + +// emit to a FILE* +ryml::emit_yaml(tree, stdout); +// emit to a stream +std::stringstream ss; +ss << tree; +std::string stream_result = ss.str(); +// emit to a buffer: +std::string str_result = ryml::emitrs_yaml(tree); +// can emit to any given buffer: +char buf[1024]; +ryml::csubstr buf_result = ryml::emit_yaml(tree, buf); +// now check +ryml::csubstr expected_result = R"(foo: says who +bar: +- 20 +- 30 +- oh so nice +- oh so nice (serialized) +john: in_scope +float: 2.4 +digits: 2.400000 +newkeyval: shiny and new +newkeyval (serialized): shiny and new (serialized) +newseq: [] +newseq (serialized): [] +newmap: {} +newmap (serialized): {} +I am something: indeed +)"; +CHECK(buf_result == expected_result); +CHECK(str_result == expected_result); +CHECK(stream_result == expected_result); +// There are many possibilities to emit to buffer; +// please look at the emit sample functions below. + +//------------------------------------------------------------------ +// ConstNodeRef vs NodeRef + +ryml::NodeRef noderef = tree["bar"][0]; +ryml::ConstNodeRef constnoderef = tree["bar"][0]; + +// ConstNodeRef cannot be used to mutate the tree: +//constnoderef = "21"; // compile error +//constnoderef << "22"; // compile error +// ... but a NodeRef can: +noderef = "21"; // ok, can assign because it's not const +CHECK(tree["bar"][0].val() == "21"); +noderef << "22"; // ok, can serialize and assign because it's not const +CHECK(tree["bar"][0].val() == "22"); + +// it is not possible to obtain a NodeRef from a ConstNodeRef: +// noderef = constnoderef; // compile error + +// it is always possible to obtain a ConstNodeRef from a NodeRef: +constnoderef = noderef; // ok can assign const <- nonconst + +// If a tree is const, then only ConstNodeRef's can be +// obtained from that tree: +ryml::Tree const& consttree = tree; +//noderef = consttree["bar"][0]; // compile error +noderef = tree["bar"][0]; // ok +constnoderef = consttree["bar"][0]; // ok + +// ConstNodeRef and NodeRef can be compared for equality. +// Equality means they point at the same node. +CHECK(constnoderef == noderef); +CHECK(!(constnoderef != noderef)); + +//------------------------------------------------------------------ +// Dealing with UTF8 +ryml::Tree langs = ryml::parse_in_arena(R"( +en: Planet (Gas) +fr: Planète (Gazeuse) +ru: Планета (Газ) +ja: 惑星(ガス) +zh: 行星(气体) +# UTF8 decoding only happens in double-quoted strings, +# as per the YAML standard +decode this: "\u263A \xE2\x98\xBA" +and this as well: "\u2705 \U0001D11E" +)"); +// in-place UTF8 just works: +CHECK(langs["en"].val() == "Planet (Gas)"); +CHECK(langs["fr"].val() == "Planète (Gazeuse)"); +CHECK(langs["ru"].val() == "Планета (Газ)"); +CHECK(langs["ja"].val() == "惑星(ガス)"); +CHECK(langs["zh"].val() == "行星(气体)"); +// and \x \u \U codepoints are decoded (but only when they appear +// inside double-quoted strings, as dictated by the YAML +// standard): +CHECK(langs["decode this"].val() == "☺ ☺"); +CHECK(langs["and this as well"].val() == "✅ 𝄞"); + +//------------------------------------------------------------------ +// Getting the location of nodes in the source: +// +// Location tracking is opt-in: +ryml::Parser parser(ryml::ParserOptions().locations(true)); +// Now the parser will start by building the accelerator structure: +ryml::Tree tree2 = parser.parse_in_arena("expected.yml", expected_result); +// ... and use it when querying +ryml::Location loc = parser.location(tree2["bar"][1]); +CHECK(parser.location_contents(loc).begins_with("30")); +CHECK(loc.line == 3u); +CHECK(loc.col == 4u); // Parse YAML code in place, potentially mutating the buffer. // It is also possible to: // - parse a read-only buffer using parse_in_arena() @@ -690,6 +1198,7 @@ ryml's API. It is tested in the CI, and thus has the correct behavior. There you can find the following subjects being addressed: ```c++ +sample_quick_overview(); ///< briefly skim over most of the features sample_substr(); ///< about ryml's string views (from c4core) sample_parse_file(); ///< ready-to-go example of parsing a file from disk sample_parse_in_place(); ///< parse a mutable YAML source buffer @@ -706,10 +1215,12 @@ sample_base64(); ///< encode/decode base64 sample_user_scalar_types(); ///< serialize/deserialize scalar (leaf/string) types sample_user_container_types(); ///< serialize/deserialize container (map or seq) types sample_std_types(); ///< serialize/deserialize STL containers +sample_float_precision(); ///< control precision of serialized floats sample_emit_to_container(); ///< emit to memory, eg a string or vector-like container sample_emit_to_stream(); ///< emit to a stream, eg std::ostream sample_emit_to_file(); ///< emit to a FILE* sample_emit_nested_node(); ///< pick a nested node as the root when emitting +sample_emit_style(); ///< set the nodes to FLOW/BLOCK format sample_json(); ///< JSON parsing and emitting sample_anchors_and_aliases(); ///< deal with YAML anchors and aliases sample_tags(); ///< deal with YAML type tags diff --git a/changelog/current.md b/changelog/current.md index 89f848474..fc993c370 100644 --- a/changelog/current.md +++ b/changelog/current.md @@ -53,5 +53,10 @@ ``` - [PR#368](https://github.com/biojppm/rapidyaml/pull/368) - fix pedantic compiler warnings. - Fix [#373](https://github.com/biojppm/rapidyaml/issues/373) - false parse error with empty quoted keys in block-style map ([PR#374](https://github.com/biojppm/rapidyaml/pull/374)). -- Fix [#356](https://github.com/biojppm/rapidyaml/issues/356) - fix overzealous check in `emit_as()`. An id may be larger than the tree's size, eg when nodes were removed. -([PR#357](https://github.com/biojppm/rapidyaml/pull/357)). +- Fix [#356](https://github.com/biojppm/rapidyaml/issues/356) - fix overzealous check in `emit_as()`. An id may be larger than the tree's size, eg when nodes were removed. ([PR#357](https://github.com/biojppm/rapidyaml/pull/357)). +- Fix [#417](https://github.com/biojppm/rapidyaml/issues/417)) - add quickstart example explaining how to avoid precision loss while serializing floats ([PR#420](https://github.com/biojppm/rapidyaml/pull/420)). + +### Thanks + +- @Neko-Box-Coder +- @jdrouhard From 25e806b209350bfe327e3c895ffb13bfc2d620d2 Mon Sep 17 00:00:00 2001 From: Joao Paulo Magalhaes Date: Wed, 3 Apr 2024 16:35:03 +0100 Subject: [PATCH 12/15] (Const)NodeRef: add .at(), improve coverage, verify error handling --- README.md | 6 + changelog/current.md | 28 +-- samples/quickstart.cpp | 6 +- src/c4/yml/node.hpp | 140 ++++++++++++-- test/test_case.hpp | 4 +- test/test_noderef.cpp | 415 ++++++++++++++++++++++++++++------------ test/test_tree.cpp | 420 ++++++++++++++++++++++++----------------- 7 files changed, 702 insertions(+), 317 deletions(-) diff --git a/README.md b/README.md index 1706408d0..282393e89 100644 --- a/README.md +++ b/README.md @@ -1355,6 +1355,12 @@ ryml: because this may cost up to 10% in processing time. * `RYML_DEFAULT_CALLBACKS=ON/OFF`. Enable/disable ryml's default implementation of error and allocation callbacks. Defaults to `ON`. + * `RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS=ON/OFF` - Enable/disable + the same-named macro, which will make the default error handler + provided by ryml throw a `std::runtime_error` exception. + * `RYML_USE_ASSERT` - enable assertions in the code regardless of + build type. This is disabled by default. Failed assertions will + trigger a call to the error callback. * `RYML_STANDALONE=ON/OFF`. ryml uses [c4core](https://github.com/biojppm/c4core), a C++ library with low-level multi-platform utilities for C++. When `RYML_STANDALONE=ON`, c4core is diff --git a/changelog/current.md b/changelog/current.md index fc993c370..587333f0e 100644 --- a/changelog/current.md +++ b/changelog/current.md @@ -1,13 +1,13 @@ +### Error handling -### Fixes +Fix major error handling problem reported in [#389](https://github.com/biojppm/rapidyaml/issues/389) ([PR#411](https://github.com/biojppm/rapidyaml/pull/411)): -- Fix major error handling problem reported in [#389](https://github.com/biojppm/rapidyaml/issues/389) ([PR#411](https://github.com/biojppm/rapidyaml/pull/411)): - The `NodeRef` and `ConstNodeRef` classes had many methods marked `noexcept` that were doing assertions which could throw exceptions, causing an abort instead of a throw whenever the assertion called an exception-throwing error callback. - Also, this problem was compounded by exceptions being enabled in every build type -- despite the intention to have them only in debug builds. There was a problem in the preprocessor code to enable assertions which led to assertions being enabled in release builds even when `RYML_USE_ASSERT` was defined to 0. Thanks to @jdrouhard for reporting this. - - Although the code is and was extensively tested, the testing was addressing mostly the happy path. In the fix, I added tests to ensure that the error behavior is as intended. - - Together with this changeset, a major revision was carried out of the asserting/checking status of each function in the node classes. In most cases, assertions were added to cases that were missing them. So **beware** - user code that was invalid will now assert or error out. Also, assertions and checks are now directed as much as possible to the callbacks of the closest scope, ie if a tree has custom callbacks, errors should go through those callbacks. + - Although the code is and was extensively tested, the testing was addressing mostly the happy path. Tests were added to ensure that the error behavior is as intended. + - Together with this changeset, a major revision was carried out of the asserting/checking status of each function in the node classes. In most cases, assertions were added to functions cases that were missing them. So **beware** - user code that was invalid will now assert or error out. Also, assertions and checks are now directed as much as possible to the callbacks of the closest scope, ie if a tree has custom callbacks, errors should go through those callbacks. - Also, the intended assertion behavior is now in place: *no assertions in release builds*. **Beware** as well - user code which was relying on this will now silently succeed and return garbage in release builds. See the next points, which may help: - - Added new methods to the node class: + - Added new methods to the `NodeRef`/`ConstNodeRef` classes: ```c++ /** Distinguish between a valid seed vs a valid non-seed ref. */ bool readable() const { return valid() && !is_seed(); } @@ -21,8 +21,9 @@ * readable, or when it is not a map. This behaviour is similar to * std::vector::at(), but the error consists in calling the error * callback instead of directly raising an exception. */ - ConstNodeRef ConstNodeRef::at(csubstr key) const; - ConstNodeRef NodeRef::at(csubstr key) const; + ConstNodeRef at(csubstr key) const; + /** Likewise, but return a seed node when the key is not found */ + ConstNodeRef at(csubstr key); /** Get a child by position, with error checking; complexity is * O(pos). @@ -30,16 +31,21 @@ * Behaves as operator[](size_t) const, but always raises an error * (even when RYML_USE_ASSERT is set to false) when the returned * node does not exist, or when this node is not readable, or when - * it is not a map. This behaviour is similar to + * it is not a container. This behaviour is similar to * std::vector::at(), but the error consists in calling the error * callback instead of directly raising an exception. */ - ConstNodeRef ConstNodeRef::at(size_t pos) const; - ConstNodeRef NodeRef::at(size_t pos) const; - ``` + ConstNodeRef at(size_t pos) const; + /** Likewise, but return a seed node when pos is not found */ + ConstNodeRef at(csubstr key); + ``` - Added macros and respective cmake options to control error handling: - `RYML_USE_ASSERT` - enable assertions regardless of build type. This is disabled by default. - `RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS` - defines the same macro, which will make the default error handler provided by ryml throw exceptions instead of calling `std::abort()`. This is disabled by default. - Also, `RYML_DEBUG_BREAK()` is now enabled only if `RYML_DBG` is defined, as reported in [#362](https://github.com/biojppm/rapidyaml/issues/362). + + +### More fixes + - Fix [#390](https://github.com/biojppm/rapidyaml/pull/390) - `csubstr::first_real_span()` failed on scientific numbers with one digit in the exponent. - Fix [#361](https://github.com/biojppm/rapidyaml/pull/361) - parse error on map scalars containing `:` and starting on the next line: ```yaml diff --git a/samples/quickstart.cpp b/samples/quickstart.cpp index 0701b2387..12ee529a2 100644 --- a/samples/quickstart.cpp +++ b/samples/quickstart.cpp @@ -4190,13 +4190,9 @@ void sample_error_handler() // crash. ryml::set_callbacks(errh.callbacks()); errh.check_effect(/*committed*/true); - bool had_parse_error = true; - errh.check_error_occurs([&had_parse_error]{ - had_parse_error = true; + errh.check_error_occurs([&]{ ryml::Tree tree = ryml::parse_in_arena("errorhandler.yml", "[a: b\n}"); - had_parse_error = false; // this line is not executed }); - CHECK(had_parse_error); ryml::set_callbacks(errh.defaults); // restore defaults. errh.check_effect(/*committed*/false); } diff --git a/src/c4/yml/node.hpp b/src/c4/yml/node.hpp index 011086e1b..e1d234171 100644 --- a/src/c4/yml/node.hpp +++ b/src/c4/yml/node.hpp @@ -323,6 +323,14 @@ struct RoNodeMethods C4_ALWAYS_INLINE size_t num_other_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->num_other_siblings(id_); } C4_ALWAYS_INLINE size_t sibling_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); _RYML_CB_ASSERT(tree_->callbacks(), n.readable()); return tree_->child_pos(tree_->parent(id_), n.m_id); } + /** @} */ + +public: + + /** @name square_brackets + * operator[] */ + /** @{ */ + /** Find child by key; complexity is O(num_children). * * Returns the requested node, or an object in seed state if no @@ -332,10 +340,11 @@ struct RoNodeMethods * to the tree provided that its create() method is called prior * to writing, which happens in most modifying methods in * NodeRef. It is the caller's responsibility to verify that the - * returned node is readable before subsequently using it. + * returned node is readable before subsequently using it to read + * from the tree. * * @warning the calling object must be readable. This precondition - * is asserted. This assertion is performed only if @ref + * is asserted. The assertion is performed only if @ref * RYML_USE_ASSERT is set to true. As with the non-const overload, * it is UB to call this method if the node is not readable. * @@ -357,10 +366,11 @@ struct RoNodeMethods * to the tree provided that its create() method is called prior * to writing, which happens in most modifying methods in * NodeRef. It is the caller's responsibility to verify that the - * returned node is readable before subsequently using it. + * returned node is readable before subsequently using it to read + * from the tree. * * @warning the calling object must be readable. This precondition - * is asserted. This assertion is performed only if @ref + * is asserted. The assertion is performed only if @ref * RYML_USE_ASSERT is set to true. As with the non-const overload, * it is UB to call this method if the node is not readable. * @@ -377,7 +387,7 @@ struct RoNodeMethods * * Behaves similar to the non-const overload, but further asserts * that the returned node is readable (because it can never be in - * a seed state). This assertion is performed only if @ref + * a seed state). The assertion is performed only if @ref * RYML_USE_ASSERT is set to true. As with the non-const overload, * it is UB to use the return value if it is not valid. * @@ -407,6 +417,90 @@ struct RoNodeMethods return {tree_, ch}; } + /** @} */ + +public: + + /** @name at + * + * These functions are the analogue to operator[], with the + * difference that they */ + /** @{ */ + + /** Find child by key; complexity is O(num_children). + * + * Returns the requested node, or an object in seed state if no + * such child is found (see @ref NodeRef for an explanation of + * what is seed state). When the object is in seed state, using it + * to read from the tree is UB. The seed node can be subsequently + * used to write to the tree provided that its create() method is + * called prior to writing, which happens inside most mutating + * methods in NodeRef. It is the caller's responsibility to verify + * that the returned node is readable before subsequently using it + * to read from the tree. + * + * @warning This method will call the error callback (regardless + * of build type) whenever any of the following preconditions is + * violated: a) the object is valid (points at a tree and a node), + * b) the calling object must be readable (must not be in seed + * state), c) the calling object must be pointing at a MAP + * node. The preconditions are similar to the non-const + * operator[](csubstr), but instead of using assertions, this + * function directly checks those conditions and calls the error + * callback if any of the checks fail. + * + * @note since it is valid behavior for the returned node to be in + * seed state, the error callback is not invoked when this + * happens. */ + template + C4_ALWAYS_INLINE auto at(csubstr key) -> _C4_IF_MUTABLE(Impl) + { + RYML_CHECK(tree_ != nullptr); + _RYML_CB_CHECK(tree_->m_callbacks, (id_ >= 0 && id_ < tree_->capacity())); + _RYML_CB_CHECK(tree_->m_callbacks, ((Impl const*)this)->readable()); + _RYML_CB_CHECK(tree_->m_callbacks, tree_->is_map(id_)); + size_t ch = tree__->find_child(id__, key); + return ch != NONE ? Impl(tree__, ch) : Impl(tree__, id__, key); + } + + /** Find child by position; complexity is O(pos). + * + * Returns the requested node, or an object in seed state if no + * such child is found (see @ref NodeRef for an explanation of + * what is seed state). When the object is in seed state, using it + * to read from the tree is UB. The seed node can be used to write + * to the tree provided that its create() method is called prior + * to writing, which happens in most modifying methods in + * NodeRef. It is the caller's responsibility to verify that the + * returned node is readable before subsequently using it to read + * from the tree. + * + * @warning This method will call the error callback (regardless + * of build type) whenever any of the following preconditions is + * violated: a) the object is valid (points at a tree and a node), + * b) the calling object must be readable (must not be in seed + * state), c) the calling object must be pointing at a MAP + * node. The preconditions are similar to the non-const + * operator[](size_t), but instead of using assertions, this + * function directly checks those conditions and calls the error + * callback if any of the checks fail. + * + * @note since it is valid behavior for the returned node to be in + * seed state, the error callback is not invoked when this + * happens. */ + template + C4_ALWAYS_INLINE auto at(size_t pos) -> _C4_IF_MUTABLE(Impl) + { + RYML_CHECK(tree_ != nullptr); + const size_t cap = tree_->capacity(); + _RYML_CB_CHECK(tree_->m_callbacks, (id_ >= 0 && id_ < cap)); + _RYML_CB_CHECK(tree_->m_callbacks, (pos >= 0 && pos < cap)); + _RYML_CB_CHECK(tree_->m_callbacks, ((Impl const*)this)->readable()); + _RYML_CB_CHECK(tree_->m_callbacks, tree_->is_container(id_)); + size_t ch = tree__->child(id__, pos); + return ch != NONE ? Impl(tree__, ch) : Impl(tree__, id__, pos); + } + /** Get a child by name, with error checking; complexity is * O(num_children). * @@ -419,8 +513,9 @@ struct RoNodeMethods ConstImpl at(csubstr key) const { RYML_CHECK(tree_ != nullptr); + _RYML_CB_CHECK(tree_->m_callbacks, (id_ >= 0 && id_ < tree_->capacity())); _RYML_CB_CHECK(tree_->m_callbacks, ((Impl const*)this)->readable()); - _RYML_CB_CHECK(tree_->m_callbacks, ((Impl const*)this)->is_map()); + _RYML_CB_CHECK(tree_->m_callbacks, tree_->is_map(id_)); size_t ch = tree_->find_child(id_, key); _RYML_CB_CHECK(tree_->m_callbacks, ch != NONE); return {tree_, ch}; @@ -432,16 +527,19 @@ struct RoNodeMethods * Behaves as operator[](size_t) const, but always raises an error * (even when RYML_USE_ASSERT is set to false) when the returned * node does not exist, or when this node is not readable, or when - * it is not a map. This behaviour is similar to + * it is not a container. This behaviour is similar to * std::vector::at(), but the error consists in calling the error * callback instead of directly raising an exception. */ ConstImpl at(size_t pos) const { RYML_CHECK(tree_ != nullptr); + const size_t cap = tree_->capacity(); + _RYML_CB_CHECK(tree_->m_callbacks, (id_ >= 0 && id_ < cap)); + _RYML_CB_CHECK(tree_->m_callbacks, (pos >= 0 && pos < cap)); _RYML_CB_CHECK(tree_->m_callbacks, ((Impl const*)this)->readable()); - _RYML_CB_CHECK(tree_->m_callbacks, ((Impl const*)this)->is_container()); + _RYML_CB_CHECK(tree_->m_callbacks, tree_->is_container(id_)); size_t ch = tree_->child(id_, pos); - _RYML_CB_ASSERT(tree_->m_callbacks, ch != NONE); + _RYML_CB_CHECK(tree_->m_callbacks, ch != NONE); return {tree_, ch}; } @@ -650,6 +748,13 @@ struct RoNodeMethods //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- +/** This object holds a pointer to an existing tree, and a node id. It + * can be used only to read from the tree. + * + * + * + * @warning The lifetime of the tree must be larger than that of this + * object. It is up to the user to ensure that this happens. */ class RYML_EXPORT ConstNodeRef : public detail::RoNodeMethods { public: @@ -746,9 +851,21 @@ class RYML_EXPORT ConstNodeRef : public detail::RoNodeMethods { public: @@ -1318,6 +1435,7 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods /** @} */ #undef _C4RV +#undef _C4RID }; diff --git a/test/test_case.hpp b/test/test_case.hpp index 7b4a3a487..99f29e590 100644 --- a/test/test_case.hpp +++ b/test/test_case.hpp @@ -171,10 +171,10 @@ struct ExpectError static void do_check( std::function fn, Location expected={}) { do_check(nullptr, fn, expected); } static void do_check(Tree *tree, std::function fn, Location expected={}); + static void check_success( std::function fn) { check_success(nullptr, fn); } + static void check_success(Tree *tree, std::function fn); static void check_assertion( std::function fn, Location expected={}) { check_assertion(nullptr, fn, expected); } static void check_assertion(Tree *tree, std::function fn, Location expected={}); - static void check_success(std::function fn) { check_success(nullptr, fn); } - static void check_success(Tree *tree, std::function fn); }; diff --git a/test/test_noderef.cpp b/test/test_noderef.cpp index d41082758..121ef2a87 100644 --- a/test/test_noderef.cpp +++ b/test/test_noderef.cpp @@ -149,6 +149,47 @@ TEST(NodeRef, general) EXPECT_EQ(root["b"]["aaa"].val(), "0"); } +TEST(NodeRef, operator_equal_equal) +{ + Tree tree1 = parse_in_arena("{a: a1, b: b1}"); + NodeRef a1 = tree1["a"]; + NodeRef b1 = tree1["b"]; + NodeRef a1_ = a1; + NodeRef b1_ = b1; + NodeRef seedc1 = tree1["c"]; + NodeRef seedc1_ = seedc1; + NodeRef seedd1 = tree1["d"]; + NodeRef seedd1_ = seedd1; + EXPECT_EQ(a1, a1_); + EXPECT_EQ(b1, b1_); + EXPECT_NE(a1, b1); + EXPECT_NE(b1, a1); + EXPECT_EQ(seedc1, seedc1_); + EXPECT_EQ(seedd1, seedd1_); + EXPECT_NE(seedc1, seedd1); + Tree tree2 = parse_in_arena("{a: a2, b: b2}"); + NodeRef a2 = tree2["a"]; + NodeRef b2 = tree2["b"]; + NodeRef a2_ = a2; + NodeRef b2_ = b2; + NodeRef seedc2 = tree2["c"]; + NodeRef seedc2_ = seedc2; + NodeRef seedd2 = tree2["d"]; + NodeRef seedd2_ = seedd2; + EXPECT_EQ(a2, a2_); + EXPECT_EQ(b2, b2_); + EXPECT_NE(a2, b2); + EXPECT_NE(b2, a2); + EXPECT_EQ(seedc2, seedc2_); + EXPECT_EQ(seedd2, seedd2_); + EXPECT_NE(seedc2, seedd2); + // + EXPECT_NE(a1, a2); + EXPECT_NE(b1, b2); + EXPECT_NE(seedc1, seedc2); + EXPECT_NE(seedd1, seedd2); +} + TEST(NodeRef, valid_vs_seed_vs_readable) { static_assert(!ConstNodeRef::is_seed(), "ConstNodeRef must never be a seed"); @@ -181,133 +222,132 @@ TEST(NodeRef, valid_vs_seed_vs_readable) EXPECT_FALSE(none.readable()); } -#define _TEST_FAIL_READ(method_expr) \ +#define _TEST_FAIL(method_expr) \ { \ SCOPED_TRACE(#method_expr); \ - std::cout << __FILE__ << ":" << __LINE__ << ": " << #method_expr << "\n"; \ if(tree) \ ExpectError::check_assertion(tree, [&]{ return method_expr; }); \ else \ ExpectError::check_assertion([&]{ return method_expr; }); \ } -#define _TEST_SUCCEED_READ(method_expr) \ +#define _TEST_SUCCEED(method_expr) \ { \ SCOPED_TRACE(#method_expr); \ - std::cout << __FILE__ << ":" << __LINE__ << ": " << #method_expr << "\n"; \ if(tree) \ ExpectError::check_success(tree, [&]{ return method_expr; }); \ else \ ExpectError::check_success([&]{ return method_expr; }); \ } + template void test_fail_read(Tree *tree, NodeT node) { - _TEST_SUCCEED_READ(node.get()) - _TEST_FAIL_READ(node.type()) - _TEST_FAIL_READ(node.type_str()) - _TEST_FAIL_READ(node.key()) - _TEST_FAIL_READ(node.key_tag()) - _TEST_FAIL_READ(node.key_anchor()) - _TEST_FAIL_READ(node.key_ref()) - _TEST_FAIL_READ(node.key_is_null()) - _TEST_FAIL_READ(node.keysc()) - _TEST_FAIL_READ(node.val()) - _TEST_FAIL_READ(node.val_tag()) - _TEST_FAIL_READ(node.val_anchor()) - _TEST_FAIL_READ(node.val_ref()) - _TEST_FAIL_READ(node.val_is_null()) - _TEST_FAIL_READ(node.valsc()) - _TEST_FAIL_READ(node.is_map()) - _TEST_FAIL_READ(node.empty()) - _TEST_FAIL_READ(node.is_stream()) - _TEST_FAIL_READ(node.is_doc()) - _TEST_FAIL_READ(node.is_container()) - _TEST_FAIL_READ(node.is_map()) - _TEST_FAIL_READ(node.is_seq()) - _TEST_FAIL_READ(node.has_val()) - _TEST_FAIL_READ(node.has_key()) - _TEST_FAIL_READ(node.is_keyval()) - _TEST_FAIL_READ(node.has_key_tag()) - _TEST_FAIL_READ(node.has_val_tag()) - _TEST_FAIL_READ(node.has_key_anchor()) - _TEST_FAIL_READ(node.has_val_anchor()) - _TEST_FAIL_READ(node.is_val_anchor()) - _TEST_FAIL_READ(node.has_anchor()) - _TEST_FAIL_READ(node.is_anchor()) - _TEST_FAIL_READ(node.is_key_ref()) - _TEST_FAIL_READ(node.is_val_ref()) - _TEST_FAIL_READ(node.is_ref()) - _TEST_FAIL_READ(node.is_anchor_or_ref()) - _TEST_FAIL_READ(node.is_key_quoted()) - _TEST_FAIL_READ(node.is_val_quoted()) - _TEST_FAIL_READ(node.parent_is_seq()) - _TEST_FAIL_READ(node.parent_is_map()) - _TEST_FAIL_READ(node.is_root()) - _TEST_FAIL_READ(node.has_parent()) - _TEST_FAIL_READ(node.has_child(0)) - _TEST_FAIL_READ(node.has_child("key")) - _TEST_FAIL_READ(node.has_children()) - _TEST_FAIL_READ(node.has_sibling("key")) - _TEST_FAIL_READ(node.has_other_siblings()) - _TEST_FAIL_READ(node.doc(0)) - _TEST_FAIL_READ(node.parent()) - _TEST_FAIL_READ(node.num_children()) - _TEST_FAIL_READ(node.first_child()) - _TEST_FAIL_READ(node.last_child()) - _TEST_FAIL_READ(node.child(0)) - _TEST_FAIL_READ(node.find_child("key")) - _TEST_FAIL_READ(node.prev_sibling()) - _TEST_FAIL_READ(node.next_sibling()) - _TEST_FAIL_READ(node.first_sibling()) - _TEST_FAIL_READ(node.last_sibling()) - _TEST_FAIL_READ(node.sibling(0)) - _TEST_FAIL_READ(node.find_sibling("key")) - _TEST_FAIL_READ(node.num_children()) - _TEST_FAIL_READ(node.num_siblings()) - _TEST_FAIL_READ(node.num_other_siblings()) - _TEST_FAIL_READ(node["key"]) - _TEST_FAIL_READ(node[0]) - _TEST_FAIL_READ(node.at("key")) - _TEST_FAIL_READ(node.at(0)) + _TEST_SUCCEED(node.get()) + _TEST_FAIL(node.type()) + _TEST_FAIL(node.type_str()) + _TEST_FAIL(node.key()) + _TEST_FAIL(node.key_tag()) + _TEST_FAIL(node.key_anchor()) + _TEST_FAIL(node.key_ref()) + _TEST_FAIL(node.key_is_null()) + _TEST_FAIL(node.keysc()) + _TEST_FAIL(node.val()) + _TEST_FAIL(node.val_tag()) + _TEST_FAIL(node.val_anchor()) + _TEST_FAIL(node.val_ref()) + _TEST_FAIL(node.val_is_null()) + _TEST_FAIL(node.valsc()) + _TEST_FAIL(node.is_map()) + _TEST_FAIL(node.empty()) + _TEST_FAIL(node.is_stream()) + _TEST_FAIL(node.is_doc()) + _TEST_FAIL(node.is_container()) + _TEST_FAIL(node.is_map()) + _TEST_FAIL(node.is_seq()) + _TEST_FAIL(node.has_val()) + _TEST_FAIL(node.has_key()) + _TEST_FAIL(node.is_keyval()) + _TEST_FAIL(node.has_key_tag()) + _TEST_FAIL(node.has_val_tag()) + _TEST_FAIL(node.has_key_anchor()) + _TEST_FAIL(node.has_val_anchor()) + _TEST_FAIL(node.is_val_anchor()) + _TEST_FAIL(node.has_anchor()) + _TEST_FAIL(node.is_anchor()) + _TEST_FAIL(node.is_key_ref()) + _TEST_FAIL(node.is_val_ref()) + _TEST_FAIL(node.is_ref()) + _TEST_FAIL(node.is_anchor_or_ref()) + _TEST_FAIL(node.is_key_quoted()) + _TEST_FAIL(node.is_val_quoted()) + _TEST_FAIL(node.parent_is_seq()) + _TEST_FAIL(node.parent_is_map()) + _TEST_FAIL(node.is_root()) + _TEST_FAIL(node.has_parent()) + _TEST_FAIL(node.has_child(0)) + _TEST_FAIL(node.has_child("key")) + _TEST_FAIL(node.has_children()) + _TEST_FAIL(node.has_sibling("key")) + _TEST_FAIL(node.has_other_siblings()) + _TEST_FAIL(node.doc(0)) + _TEST_FAIL(node.parent()) + _TEST_FAIL(node.num_children()) + _TEST_FAIL(node.first_child()) + _TEST_FAIL(node.last_child()) + _TEST_FAIL(node.child(0)) + _TEST_FAIL(node.find_child("key")) + _TEST_FAIL(node.prev_sibling()) + _TEST_FAIL(node.next_sibling()) + _TEST_FAIL(node.first_sibling()) + _TEST_FAIL(node.last_sibling()) + _TEST_FAIL(node.sibling(0)) + _TEST_FAIL(node.find_sibling("key")) + _TEST_FAIL(node.num_children()) + _TEST_FAIL(node.num_siblings()) + _TEST_FAIL(node.num_other_siblings()) + _TEST_FAIL(node["key"]) + _TEST_FAIL(node[0]) + _TEST_FAIL(node.at("key")) + _TEST_FAIL(node.at(0)) int val; - _TEST_FAIL_READ(node >> val) - _TEST_FAIL_READ(node >> key(val)) - _TEST_FAIL_READ(node >> fmt::base64(val)) - _TEST_FAIL_READ(node >> key(fmt::base64(val))) - _TEST_FAIL_READ(node.deserialize_key(fmt::base64(val))) - _TEST_FAIL_READ(node.deserialize_val(fmt::base64(val))) - _TEST_FAIL_READ(node.get_if("key", &val)); - _TEST_FAIL_READ(node.get_if("key", &val, 0)); + _TEST_FAIL(node >> val) + _TEST_FAIL(node >> key(val)) + _TEST_FAIL(node >> fmt::base64(val)) + _TEST_FAIL(node >> key(fmt::base64(val))) + _TEST_FAIL(node.deserialize_key(fmt::base64(val))) + _TEST_FAIL(node.deserialize_val(fmt::base64(val))) + _TEST_FAIL(node.get_if("key", &val)); + _TEST_FAIL(node.get_if("key", &val, 0)); const NodeT const_node = node; - _TEST_FAIL_READ(node.begin()); - _TEST_FAIL_READ(node.cbegin()); - _TEST_FAIL_READ(const_node.begin()); - _TEST_FAIL_READ(const_node.cbegin()); - _TEST_FAIL_READ(node.end()); - _TEST_FAIL_READ(node.end()); - _TEST_FAIL_READ(const_node.end()); - _TEST_FAIL_READ(const_node.end()); - _TEST_FAIL_READ(node.children()); - _TEST_FAIL_READ(node.children()); - _TEST_FAIL_READ(const_node.children()); - _TEST_FAIL_READ(const_node.children()); - _TEST_FAIL_READ(node.siblings()); - _TEST_FAIL_READ(node.siblings()); - _TEST_FAIL_READ(const_node.siblings()); - _TEST_FAIL_READ(const_node.siblings()); - //_TEST_FAIL_READ(node.visit([](NodeT &n, size_t level){ (void)n; (void)level; return false; })); - //_TEST_FAIL_READ(const_node.visit([](const NodeT &n, size_t level){ (void)n; (void)level; return false; })); - _TEST_SUCCEED_READ(const_node == node); - _TEST_SUCCEED_READ(const_node != node); - _TEST_SUCCEED_READ(const_node == nullptr); - _TEST_SUCCEED_READ(const_node != nullptr); - _TEST_FAIL_READ(const_node == "val"); - _TEST_FAIL_READ(const_node != "val"); + _TEST_FAIL(node.begin()); + _TEST_FAIL(node.cbegin()); + _TEST_FAIL(const_node.begin()); + _TEST_FAIL(const_node.cbegin()); + _TEST_FAIL(node.end()); + _TEST_FAIL(node.end()); + _TEST_FAIL(const_node.end()); + _TEST_FAIL(const_node.end()); + _TEST_FAIL(node.children()); + _TEST_FAIL(node.children()); + _TEST_FAIL(const_node.children()); + _TEST_FAIL(const_node.children()); + _TEST_FAIL(node.siblings()); + _TEST_FAIL(node.siblings()); + _TEST_FAIL(const_node.siblings()); + _TEST_FAIL(const_node.siblings()); + //_TEST_FAIL(node.visit([](NodeT &n, size_t level){ (void)n; (void)level; return false; })); + //_TEST_FAIL(const_node.visit([](const NodeT &n, size_t level){ (void)n; (void)level; return false; })); + _TEST_SUCCEED(const_node == node); + _TEST_SUCCEED(const_node != node); + _TEST_SUCCEED(const_node == nullptr); + _TEST_SUCCEED(const_node != nullptr); + _TEST_FAIL(const_node == "val"); + _TEST_FAIL(const_node != "val"); if(std::is_same::value) { ConstNodeRef other; - _TEST_SUCCEED_READ(node == other); - _TEST_SUCCEED_READ(node != node); + _TEST_SUCCEED(node == other); + _TEST_SUCCEED(node != node); } } template @@ -315,16 +355,16 @@ void test_fail_read_subject(Tree *tree, NodeT node, NodeT subject) { if(node.readable()) { - _TEST_SUCCEED_READ(node.has_child(subject)) - _TEST_SUCCEED_READ(node.has_sibling(subject)) + _TEST_SUCCEED(node.has_child(subject)) + _TEST_SUCCEED(node.has_sibling(subject)) } else { - _TEST_FAIL_READ(node.has_child(subject)) - _TEST_FAIL_READ(node.has_sibling(subject)) + _TEST_FAIL(node.has_child(subject)) + _TEST_FAIL(node.has_sibling(subject)) } - _TEST_FAIL_READ(node.child_pos(subject)) - _TEST_FAIL_READ(node.sibling_pos(subject)) + _TEST_FAIL(node.child_pos(subject)) + _TEST_FAIL(node.sibling_pos(subject)) } #undef _TEST_FAIL_READ #undef _TEST_SUCCEED_READ @@ -446,12 +486,10 @@ void noderef_check_tree(ConstNodeRef const& root) EXPECT_EQ( root[5].val(), "5"); } -TEST(NodeRef, append_child) +TEST(NodeRef, append_child_1) { Tree t; - NodeRef root(&t); - root |= SEQ; root.append_child({"0"}); root.append_child({"1"}); @@ -459,16 +497,55 @@ TEST(NodeRef, append_child) root.append_child({"3"}); root.append_child({"4"}); root.append_child({"5"}); + noderef_check_tree(root); +} +TEST(NodeRef, append_child_2) +{ + Tree t; + NodeRef root(&t); + root |= SEQ; + root.append_child() = "0"; + root.append_child() = "1"; + root.append_child() = "2"; + root.append_child() = "3"; + root.append_child() = "4"; + root.append_child() = "5"; noderef_check_tree(root); } -TEST(NodeRef, prepend_child) +TEST(NodeRef, append_sibling_1) { Tree t; + NodeRef root(&t); + root |= SEQ; + NodeRef first = root.append_child({"0"}); + first.append_sibling({"1"}); + first.append_sibling({"2"}); + first.append_sibling({"3"}); + first.append_sibling({"4"}); + first.append_sibling({"5"}); + noderef_check_tree(root); +} +TEST(NodeRef, append_sibling_2) +{ + Tree t; NodeRef root(&t); + root |= SEQ; + NodeRef first = root.append_child() << "0"; + first.append_sibling() << "1"; + first.append_sibling() << "2"; + first.append_sibling() << "3"; + first.append_sibling() << "4"; + first.append_sibling() << "5"; + noderef_check_tree(root); +} +TEST(NodeRef, prepend_child_1) +{ + Tree t; + NodeRef root(&t); root |= SEQ; root.prepend_child({"5"}); root.prepend_child({"4"}); @@ -476,17 +553,57 @@ TEST(NodeRef, prepend_child) root.prepend_child({"2"}); root.prepend_child({"1"}); root.prepend_child({"0"}); + noderef_check_tree(root); +} +TEST(NodeRef, prepend_child_2) +{ + Tree t; + NodeRef root(&t); + root |= SEQ; + root.prepend_child() << "5"; + root.prepend_child() << "4"; + root.prepend_child() << "3"; + root.prepend_child() << "2"; + root.prepend_child() << "1"; + root.prepend_child() << "0"; + noderef_check_tree(root); +} + +TEST(NodeRef, prepend_sibling_1) +{ + Tree t; + NodeRef root(&t); + root |= SEQ; + NodeRef last = root.prepend_child({"5"}); + last.prepend_sibling({"4"}); + last.prepend_sibling({"3"}); + last.prepend_sibling({"2"}); + last.prepend_sibling({"1"}); + last.prepend_sibling({"0"}); noderef_check_tree(root); } -TEST(NodeRef, insert_child) +TEST(NodeRef, prepend_sibling_2) { Tree t; + NodeRef root(&t); + root |= SEQ; + NodeRef last = root.prepend_child(); + last = "5"; + last.prepend_sibling() = "4"; + last.prepend_sibling() = "3"; + last.prepend_sibling() = "2"; + last.prepend_sibling() = "1"; + last.prepend_sibling() = "0"; + noderef_check_tree(root); +} +TEST(NodeRef, insert_child_1) +{ + Tree t; NodeRef root(&t); NodeRef none(&t, NONE); - root |= SEQ; root.insert_child({"3"}, none); root.insert_child({"4"}, root[0]); @@ -494,7 +611,51 @@ TEST(NodeRef, insert_child) root.insert_child({"5"}, root[2]); root.insert_child({"1"}, root[0]); root.insert_child({"2"}, root[1]); + noderef_check_tree(root); +} +TEST(NodeRef, insert_child_2) +{ + Tree t; + NodeRef root(&t); + NodeRef none(&t, NONE); + root |= SEQ; + root.insert_child(none) << "3"; + root.insert_child(root[0]) << "4"; + root.insert_child(none) << "0"; + root.insert_child(root[2]) << "5"; + root.insert_child(root[0]) << "1"; + root.insert_child(root[1]) << "2"; + noderef_check_tree(root); +} + +TEST(NodeRef, insert_sibling_1) +{ + Tree t; + NodeRef root(&t); + NodeRef none(&t, NONE); + root |= SEQ; + NodeRef first = root.insert_child({"3"}, none); + first.insert_sibling({"4"}, root[0]); + first.insert_sibling({"0"}, none); + first.insert_sibling({"5"}, root[2]); + first.insert_sibling({"1"}, root[0]); + first.insert_sibling({"2"}, root[1]); + noderef_check_tree(root); +} + +TEST(NodeRef, insert_sibling_2) +{ + Tree t; + NodeRef root(&t); + NodeRef none(&t, NONE); + root |= SEQ; + NodeRef first = root.insert_child(none) << "3"; + first.insert_sibling(root[0]) << "4"; + first.insert_sibling(none) << "0"; + first.insert_sibling(root[2]) << "5"; + first.insert_sibling(root[0]) << "1"; + first.insert_sibling(root[1]) << "2"; noderef_check_tree(root); } @@ -1099,6 +1260,26 @@ TEST(NodeRef, overload_sets) } } +TEST(NodeRef, get_if) +{ + const Tree tree = parse_in_arena("{a: 1, b: 2}"); + ConstNodeRef root = tree.rootref(); + int val = 0; + EXPECT_TRUE(root.get_if("a", &val)); + EXPECT_EQ(val, 1); + EXPECT_TRUE(root.get_if("b", &val)); + EXPECT_EQ(val, 2); + EXPECT_FALSE(root.get_if("c", &val)); + EXPECT_EQ(val, 2); + int fallback = 3; + EXPECT_TRUE(root.get_if("a", &val, fallback)); + EXPECT_EQ(val, 1); + EXPECT_TRUE(root.get_if("b", &val, fallback)); + EXPECT_EQ(val, 2); + EXPECT_FALSE(root.get_if("c", &val, fallback)); + EXPECT_EQ(val, 3); +} + //------------------------------------------- // this is needed to use the test case library diff --git a/test/test_tree.cpp b/test/test_tree.cpp index bdbbe8dbf..04fce801a 100644 --- a/test/test_tree.cpp +++ b/test/test_tree.cpp @@ -833,14 +833,30 @@ TEST(Tree, clear) //------------------------------------------- template -void verify_assertion(Tree &tree, Function &&fn) +void verify_success_(Tree &tree, Function &&fn) +{ + ExpectError::check_success(&tree, [&]{ + (void)fn(tree); + }); +} +template +void verify_success_(csubstr src, Function &&fn) +{ + Tree tree = parse_in_arena(src); + ExpectError::check_success(&tree, [&]{ + (void)fn(tree); + }); +} + +template +void verify_assertion_(Tree &tree, Function &&fn) { ExpectError::check_assertion(&tree, [&]{ (void)fn(tree); }); } template -void verify_assertion(csubstr src, Function &&fn) +void verify_assertion_(csubstr src, Function &&fn) { Tree tree = parse_in_arena(src); ExpectError::check_assertion(&tree, [&]{ @@ -849,14 +865,14 @@ void verify_assertion(csubstr src, Function &&fn) } template -void verify_error(Tree &tree, Function &&fn) +void verify_error_(Tree &tree, Function &&fn) { ExpectError::do_check(&tree, [&]{ (void)fn(tree); }); } template -void verify_error(csubstr src, Function &&fn) +void verify_error_(csubstr src, Function &&fn) { Tree tree = parse_in_arena(src); ExpectError::do_check(&tree, [&]{ @@ -864,6 +880,24 @@ void verify_error(csubstr src, Function &&fn) }); } +#define verify_success(...) \ + { \ + SCOPED_TRACE("caller"); \ + verify_success_(__VA_ARGS__); \ + } + +#define verify_assertion(...) \ + { \ + SCOPED_TRACE("caller"); \ + verify_assertion_(__VA_ARGS__); \ + } + +#define verify_error(...) \ + { \ + SCOPED_TRACE("caller"); \ + verify_error_(__VA_ARGS__); \ + } + TEST(Tree, ref) { @@ -917,177 +951,221 @@ TEST(Tree, ref_const) } -TEST(Tree, operator_square_brackets) +TEST(Tree, operator_square_brackets_seq) { - { - Tree t = parse_in_arena("[0, 1, 2, 3, 4]"); - Tree &m = t; - Tree const& cm = t; - EXPECT_EQ(m[0].val(), "0"); - EXPECT_EQ(m[1].val(), "1"); - EXPECT_EQ(m[2].val(), "2"); - EXPECT_EQ(m[3].val(), "3"); - EXPECT_EQ(m[4].val(), "4"); - EXPECT_EQ(cm[0].val(), "0"); - EXPECT_EQ(cm[1].val(), "1"); - EXPECT_EQ(cm[2].val(), "2"); - EXPECT_EQ(cm[3].val(), "3"); - EXPECT_EQ(cm[4].val(), "4"); - // - EXPECT_TRUE(m[0] == "0"); - EXPECT_TRUE(m[1] == "1"); - EXPECT_TRUE(m[2] == "2"); - EXPECT_TRUE(m[3] == "3"); - EXPECT_TRUE(m[4] == "4"); - EXPECT_TRUE(cm[0] == "0"); - EXPECT_TRUE(cm[1] == "1"); - EXPECT_TRUE(cm[2] == "2"); - EXPECT_TRUE(cm[3] == "3"); - EXPECT_TRUE(cm[4] == "4"); - // - EXPECT_FALSE(m[0] != "0"); - EXPECT_FALSE(m[1] != "1"); - EXPECT_FALSE(m[2] != "2"); - EXPECT_FALSE(m[3] != "3"); - EXPECT_FALSE(m[4] != "4"); - EXPECT_FALSE(cm[0] != "0"); - EXPECT_FALSE(cm[1] != "1"); - EXPECT_FALSE(cm[2] != "2"); - EXPECT_FALSE(cm[3] != "3"); - EXPECT_FALSE(cm[4] != "4"); - // - verify_assertion(t, [&](Tree const&){ return cm[m.capacity()]; }); - verify_assertion(t, [&](Tree const&){ return cm[NONE]; }); - verify_assertion(t, [&](Tree const&){ return cm[0][0]; }); - verify_assertion(t, [&](Tree const&){ return cm["a"]; }); - } - { - Tree t = parse_in_arena("{a: 0, b: 1, c: 2, d: 3, e: 4}"); - Tree &m = t; - Tree const& cm = t; - EXPECT_EQ(m["a"].val(), "0"); - EXPECT_EQ(m["b"].val(), "1"); - EXPECT_EQ(m["c"].val(), "2"); - EXPECT_EQ(m["d"].val(), "3"); - EXPECT_EQ(m["e"].val(), "4"); - EXPECT_EQ(cm["a"].val(), "0"); - EXPECT_EQ(cm["b"].val(), "1"); - EXPECT_EQ(cm["c"].val(), "2"); - EXPECT_EQ(cm["d"].val(), "3"); - EXPECT_EQ(cm["e"].val(), "4"); - // - EXPECT_TRUE(m["a"] == "0"); - EXPECT_TRUE(m["b"] == "1"); - EXPECT_TRUE(m["c"] == "2"); - EXPECT_TRUE(m["d"] == "3"); - EXPECT_TRUE(m["e"] == "4"); - EXPECT_TRUE(cm["a"] == "0"); - EXPECT_TRUE(cm["b"] == "1"); - EXPECT_TRUE(cm["c"] == "2"); - EXPECT_TRUE(cm["d"] == "3"); - EXPECT_TRUE(cm["e"] == "4"); - // - EXPECT_FALSE(m["a"] != "0"); - EXPECT_FALSE(m["b"] != "1"); - EXPECT_FALSE(m["c"] != "2"); - EXPECT_FALSE(m["d"] != "3"); - EXPECT_FALSE(m["e"] != "4"); - EXPECT_FALSE(cm["a"] != "0"); - EXPECT_FALSE(cm["b"] != "1"); - EXPECT_FALSE(cm["c"] != "2"); - EXPECT_FALSE(cm["d"] != "3"); - EXPECT_FALSE(cm["e"] != "4"); - // - verify_assertion(t, [&](Tree const&){ return cm["f"]; }); - verify_assertion(t, [&](Tree const&){ return cm["g"]["h"]; }); - } + Tree t = parse_in_arena("[0, 1, 2, 3, 4]"); + Tree &m = t; + Tree const& cm = t; + EXPECT_EQ(m[0].val(), "0"); + EXPECT_EQ(m[1].val(), "1"); + EXPECT_EQ(m[2].val(), "2"); + EXPECT_EQ(m[3].val(), "3"); + EXPECT_EQ(m[4].val(), "4"); + EXPECT_EQ(cm[0].val(), "0"); + EXPECT_EQ(cm[1].val(), "1"); + EXPECT_EQ(cm[2].val(), "2"); + EXPECT_EQ(cm[3].val(), "3"); + EXPECT_EQ(cm[4].val(), "4"); + // + EXPECT_TRUE(m[0] == "0"); + EXPECT_TRUE(m[1] == "1"); + EXPECT_TRUE(m[2] == "2"); + EXPECT_TRUE(m[3] == "3"); + EXPECT_TRUE(m[4] == "4"); + EXPECT_TRUE(cm[0] == "0"); + EXPECT_TRUE(cm[1] == "1"); + EXPECT_TRUE(cm[2] == "2"); + EXPECT_TRUE(cm[3] == "3"); + EXPECT_TRUE(cm[4] == "4"); + // + EXPECT_FALSE(m[0] != "0"); + EXPECT_FALSE(m[1] != "1"); + EXPECT_FALSE(m[2] != "2"); + EXPECT_FALSE(m[3] != "3"); + EXPECT_FALSE(m[4] != "4"); + EXPECT_FALSE(cm[0] != "0"); + EXPECT_FALSE(cm[1] != "1"); + EXPECT_FALSE(cm[2] != "2"); + EXPECT_FALSE(cm[3] != "3"); + EXPECT_FALSE(cm[4] != "4"); + // + verify_assertion(t, [&](Tree const&){ return cm[m.capacity()]; }); + verify_assertion(t, [&](Tree const&){ return cm[NONE]; }); + verify_assertion(t, [&](Tree const&){ return cm[0][0]; }); + verify_assertion(t, [&](Tree const&){ return cm["a"]; }); } -TEST(Tree, noderef_at) +TEST(Tree, operator_square_brackets_map) { - { - Tree t = parse_in_arena("[0, 1, 2, 3, 4]"); - NodeRef m = t.rootref(); - ConstNodeRef const cm = t.rootref(); - EXPECT_EQ(m.at(0).val(), "0"); - EXPECT_EQ(m.at(1).val(), "1"); - EXPECT_EQ(m.at(2).val(), "2"); - EXPECT_EQ(m.at(3).val(), "3"); - EXPECT_EQ(m.at(4).val(), "4"); - EXPECT_EQ(cm.at(0).val(), "0"); - EXPECT_EQ(cm.at(1).val(), "1"); - EXPECT_EQ(cm.at(2).val(), "2"); - EXPECT_EQ(cm.at(3).val(), "3"); - EXPECT_EQ(cm.at(4).val(), "4"); - // - EXPECT_TRUE(m.at(0) == "0"); - EXPECT_TRUE(m.at(1) == "1"); - EXPECT_TRUE(m.at(2) == "2"); - EXPECT_TRUE(m.at(3) == "3"); - EXPECT_TRUE(m.at(4) == "4"); - EXPECT_TRUE(cm.at(0) == "0"); - EXPECT_TRUE(cm.at(1) == "1"); - EXPECT_TRUE(cm.at(2) == "2"); - EXPECT_TRUE(cm.at(3) == "3"); - EXPECT_TRUE(cm.at(4) == "4"); - // - EXPECT_FALSE(m.at(0) != "0"); - EXPECT_FALSE(m.at(1) != "1"); - EXPECT_FALSE(m.at(2) != "2"); - EXPECT_FALSE(m.at(3) != "3"); - EXPECT_FALSE(m.at(4) != "4"); - EXPECT_FALSE(cm.at(0) != "0"); - EXPECT_FALSE(cm.at(1) != "1"); - EXPECT_FALSE(cm.at(2) != "2"); - EXPECT_FALSE(cm.at(3) != "3"); - EXPECT_FALSE(cm.at(4) != "4"); - // - //TODO: Not sure what to replace with capacity - //verify_error(t, [&](Tree const&){ return cm[m.capacity()]; }); - verify_error(t, [&](Tree const&){ return cm.at(NONE); }); - verify_error(t, [&](Tree const&){ return cm.at(0).at(0); }); - verify_error(t, [&](Tree const&){ return cm.at("a"); }); - } - { - Tree t = parse_in_arena("{a: 0, b: 1, c: 2, d: 3, e: 4}"); - NodeRef m = t.rootref(); - ConstNodeRef const cm = t.rootref(); - EXPECT_EQ(m.at("a").val(), "0"); - EXPECT_EQ(m.at("b").val(), "1"); - EXPECT_EQ(m.at("c").val(), "2"); - EXPECT_EQ(m.at("d").val(), "3"); - EXPECT_EQ(m.at("e").val(), "4"); - EXPECT_EQ(cm.at("a").val(), "0"); - EXPECT_EQ(cm.at("b").val(), "1"); - EXPECT_EQ(cm.at("c").val(), "2"); - EXPECT_EQ(cm.at("d").val(), "3"); - EXPECT_EQ(cm.at("e").val(), "4"); - // - EXPECT_TRUE(m.at("a") == "0"); - EXPECT_TRUE(m.at("b") == "1"); - EXPECT_TRUE(m.at("c") == "2"); - EXPECT_TRUE(m.at("d") == "3"); - EXPECT_TRUE(m.at("e") == "4"); - EXPECT_TRUE(cm.at("a") == "0"); - EXPECT_TRUE(cm.at("b") == "1"); - EXPECT_TRUE(cm.at("c") == "2"); - EXPECT_TRUE(cm.at("d") == "3"); - EXPECT_TRUE(cm.at("e") == "4"); - // - EXPECT_FALSE(m.at("a") != "0"); - EXPECT_FALSE(m.at("b") != "1"); - EXPECT_FALSE(m.at("c") != "2"); - EXPECT_FALSE(m.at("d") != "3"); - EXPECT_FALSE(m.at("e") != "4"); - EXPECT_FALSE(cm.at("a") != "0"); - EXPECT_FALSE(cm.at("b") != "1"); - EXPECT_FALSE(cm.at("c") != "2"); - EXPECT_FALSE(cm.at("d") != "3"); - EXPECT_FALSE(cm.at("e") != "4"); - // - verify_error(t, [&](Tree const&){ return cm.at("f"); }); - verify_error(t, [&](Tree const&){ return cm.at("g").at("h"); }); - } + Tree t = parse_in_arena("{a: 0, b: 1, c: 2, d: 3, e: 4}"); + Tree &m = t; + Tree const& cm = t; + EXPECT_EQ(m["a"].val(), "0"); + EXPECT_EQ(m["b"].val(), "1"); + EXPECT_EQ(m["c"].val(), "2"); + EXPECT_EQ(m["d"].val(), "3"); + EXPECT_EQ(m["e"].val(), "4"); + EXPECT_EQ(cm["a"].val(), "0"); + EXPECT_EQ(cm["b"].val(), "1"); + EXPECT_EQ(cm["c"].val(), "2"); + EXPECT_EQ(cm["d"].val(), "3"); + EXPECT_EQ(cm["e"].val(), "4"); + // + EXPECT_TRUE(m["a"] == "0"); + EXPECT_TRUE(m["b"] == "1"); + EXPECT_TRUE(m["c"] == "2"); + EXPECT_TRUE(m["d"] == "3"); + EXPECT_TRUE(m["e"] == "4"); + EXPECT_TRUE(cm["a"] == "0"); + EXPECT_TRUE(cm["b"] == "1"); + EXPECT_TRUE(cm["c"] == "2"); + EXPECT_TRUE(cm["d"] == "3"); + EXPECT_TRUE(cm["e"] == "4"); + // + EXPECT_FALSE(m["a"] != "0"); + EXPECT_FALSE(m["b"] != "1"); + EXPECT_FALSE(m["c"] != "2"); + EXPECT_FALSE(m["d"] != "3"); + EXPECT_FALSE(m["e"] != "4"); + EXPECT_FALSE(cm["a"] != "0"); + EXPECT_FALSE(cm["b"] != "1"); + EXPECT_FALSE(cm["c"] != "2"); + EXPECT_FALSE(cm["d"] != "3"); + EXPECT_FALSE(cm["e"] != "4"); + // + verify_assertion(t, [&](Tree const&){ return cm["f"]; }); + verify_assertion(t, [&](Tree const&){ return cm["g"]["h"]; }); +} + +TEST(Tree, noderef_at_seq) +{ + Tree t = parse_in_arena("[0, 1, 2, 3, 4]"); + NodeRef m = t.rootref(); + ConstNodeRef const cm = t.rootref(); + EXPECT_EQ(m.at(0).val(), "0"); + EXPECT_EQ(m.at(1).val(), "1"); + EXPECT_EQ(m.at(2).val(), "2"); + EXPECT_EQ(m.at(3).val(), "3"); + EXPECT_EQ(m.at(4).val(), "4"); + EXPECT_EQ(cm.at(0).val(), "0"); + EXPECT_EQ(cm.at(1).val(), "1"); + EXPECT_EQ(cm.at(2).val(), "2"); + EXPECT_EQ(cm.at(3).val(), "3"); + EXPECT_EQ(cm.at(4).val(), "4"); + // + EXPECT_TRUE(m.at(0) == "0"); + EXPECT_TRUE(m.at(1) == "1"); + EXPECT_TRUE(m.at(2) == "2"); + EXPECT_TRUE(m.at(3) == "3"); + EXPECT_TRUE(m.at(4) == "4"); + EXPECT_TRUE(cm.at(0) == "0"); + EXPECT_TRUE(cm.at(1) == "1"); + EXPECT_TRUE(cm.at(2) == "2"); + EXPECT_TRUE(cm.at(3) == "3"); + EXPECT_TRUE(cm.at(4) == "4"); + // + EXPECT_FALSE(m.at(0) != "0"); + EXPECT_FALSE(m.at(1) != "1"); + EXPECT_FALSE(m.at(2) != "2"); + EXPECT_FALSE(m.at(3) != "3"); + EXPECT_FALSE(m.at(4) != "4"); + EXPECT_FALSE(cm.at(0) != "0"); + EXPECT_FALSE(cm.at(1) != "1"); + EXPECT_FALSE(cm.at(2) != "2"); + EXPECT_FALSE(cm.at(3) != "3"); + EXPECT_FALSE(cm.at(4) != "4"); + // + EXPECT_EQ(cm.num_children(), 5); + EXPECT_EQ(cm.num_children(), m.num_children()); + // + verify_error(t, [&](Tree const&){ return cm.at(NONE); }); + verify_error(t, [&](Tree const&){ return cm.at(t.capacity()); }); + verify_error(t, [&](Tree const&){ return cm.at(5); }); + verify_error(t, [&](Tree const&){ return cm.at(6); }); + verify_error(t, [&](Tree const&){ return cm.at(7); }); + verify_error(t, [&](Tree const&){ return cm.at(10); }); + verify_error(t, [&](Tree const&){ return cm.at(0).at(0); }); + verify_error(t, [&](Tree const&){ return cm.at("a"); }); + // + verify_error(t, [&](Tree const&){ return m.at(NONE); }); + verify_error(t, [&](Tree const&){ return m.at(t.capacity()); }); + verify_success(t, [&](Tree const&){ return m.at(5); }); + verify_success(t, [&](Tree const&){ return m.at(6); }); + verify_error(t, [&](Tree const&){ return m.at(0).at(0); }); + verify_error(t, [&](Tree const&){ return m.at("a"); }); + EXPECT_TRUE(m.at(5).is_seed()); + EXPECT_TRUE(m.at(6).is_seed()); + // + NodeRef to_be_removed_orig = m.at(4); + NodeRef to_be_removed = to_be_removed_orig; + EXPECT_EQ(to_be_removed.id(), 5); + EXPECT_EQ(to_be_removed_orig.id(), 5); + m.remove_child(to_be_removed); + EXPECT_EQ(to_be_removed.id(), 5); // it is stale now + EXPECT_EQ(to_be_removed_orig.id(), 5); // it is stale now + EXPECT_EQ(m.num_children(), 4); + EXPECT_TRUE(m.at(4).is_seed()); + verify_success(t, [&](Tree const&){ return m.at(4); }); + verify_error(t, [&](Tree const&){ return cm.at(4); }); +} + +TEST(Tree, noderef_at_map) +{ + Tree t = parse_in_arena("{a: 0, b: 1, c: 2, d: 3, e: 4}"); + NodeRef m = t.rootref(); + ConstNodeRef const cm = t.rootref(); + EXPECT_EQ(m.at("a").val(), "0"); + EXPECT_EQ(m.at("b").val(), "1"); + EXPECT_EQ(m.at("c").val(), "2"); + EXPECT_EQ(m.at("d").val(), "3"); + EXPECT_EQ(m.at("e").val(), "4"); + EXPECT_EQ(cm.at("a").val(), "0"); + EXPECT_EQ(cm.at("b").val(), "1"); + EXPECT_EQ(cm.at("c").val(), "2"); + EXPECT_EQ(cm.at("d").val(), "3"); + EXPECT_EQ(cm.at("e").val(), "4"); + // + EXPECT_TRUE(m.at("a") == "0"); + EXPECT_TRUE(m.at("b") == "1"); + EXPECT_TRUE(m.at("c") == "2"); + EXPECT_TRUE(m.at("d") == "3"); + EXPECT_TRUE(m.at("e") == "4"); + EXPECT_TRUE(cm.at("a") == "0"); + EXPECT_TRUE(cm.at("b") == "1"); + EXPECT_TRUE(cm.at("c") == "2"); + EXPECT_TRUE(cm.at("d") == "3"); + EXPECT_TRUE(cm.at("e") == "4"); + // + EXPECT_FALSE(m.at("a") != "0"); + EXPECT_FALSE(m.at("b") != "1"); + EXPECT_FALSE(m.at("c") != "2"); + EXPECT_FALSE(m.at("d") != "3"); + EXPECT_FALSE(m.at("e") != "4"); + EXPECT_FALSE(cm.at("a") != "0"); + EXPECT_FALSE(cm.at("b") != "1"); + EXPECT_FALSE(cm.at("c") != "2"); + EXPECT_FALSE(cm.at("d") != "3"); + EXPECT_FALSE(cm.at("e") != "4"); + // + verify_error(t, [&](Tree const&){ return cm.at(t.capacity()); }); + verify_error(t, [&](Tree const&){ return cm.at(NONE); }); + verify_error(t, [&](Tree const&){ return cm.at(cm.num_children()); }); + verify_error(t, [&](Tree const&){ return cm.at(5); }); + verify_error(t, [&](Tree const&){ return cm.at(6); }); + verify_error(t, [&](Tree const&){ return cm.at("f"); }); + verify_error(t, [&](Tree const&){ return cm.at("g").at("h"); }); + // + verify_error(t, [&](Tree const&){ return m.at(t.capacity()); }); + verify_error(t, [&](Tree const&){ return m.at(NONE); }); + verify_success(t, [&](Tree const&){ return m.at(cm.num_children()); }); + verify_success(t, [&](Tree const&){ return m.at(5); }); + verify_success(t, [&](Tree const&){ return m.at(6); }); + verify_success(t, [&](Tree const&){ return m.at("f"); }); + verify_error(t, [&](Tree const&){ return m.at("g").at("h"); }); + EXPECT_TRUE(m.at(cm.num_children()).is_seed()); + EXPECT_TRUE(m.at(5).is_seed()); + EXPECT_TRUE(m.at(6).is_seed()); + EXPECT_TRUE(m.at("f").is_seed()); } TEST(Tree, relocate) From 12236b7fc4433b54463e92edd1948e4ffb69661c Mon Sep 17 00:00:00 2001 From: Joao Paulo Magalhaes Date: Thu, 4 Apr 2024 19:27:29 +0100 Subject: [PATCH 13/15] Improve some comments, elaborate explanation of NodeRef states --- README.md | 516 +++++------------------------------------ changelog/current.md | 4 +- samples/quickstart.cpp | 80 +++++-- src/c4/yml/node.hpp | 93 +++++--- 4 files changed, 177 insertions(+), 516 deletions(-) diff --git a/README.md b/README.md index 282393e89..d4d1a18b4 100644 --- a/README.md +++ b/README.md @@ -602,7 +602,8 @@ CHECK(root["newmap (serialized)"].is_map()); // This means that passing a key/index which does not exist will // not mutate the tree, but will instead store (in the node) the // proper place of the tree to be able to do so, if and when it is -// required. +// required. This is why the node is said to be in "seed" state - +// it allows creating the entry in the tree in the future. // // This is a significant difference from eg, the behavior of // std::map, which mutates the map immediately within the call to @@ -612,50 +613,67 @@ CHECK(root["newmap (serialized)"].is_map()); // the tree is const, then a NodeRef cannot be obtained from it; // only a ConstNodeRef, which can never be used to mutate the // tree. +// CHECK(!root.has_child("I am not nothing")); ryml::NodeRef nothing = wroot["I am nothing"]; CHECK(nothing.valid()); // points at the tree, and a specific place in the tree CHECK(nothing.is_seed()); // ... but nothing is there yet. CHECK(!root.has_child("I am nothing")); // same as above +CHECK(!nothing.readable()); // ... and this node cannot be used to + // read anything from the tree ryml::NodeRef something = wroot["I am something"]; ryml::ConstNodeRef constsomething = wroot["I am something"]; CHECK(!root.has_child("I am something")); // same as above CHECK(something.valid()); CHECK(something.is_seed()); // same as above -CHECK(!constsomething.valid()); // NOTE: because a ConstNodeRef - // cannot be used to mutate a - // tree, it is only valid() if it - // is pointing at an existing - // node. -something = "indeed"; // this will commit to the tree, mutating at the proper place +CHECK(!something.readable()); // same as above +CHECK(!constsomething.valid()); // NOTE: because a ConstNodeRef cannot be + // used to mutate a tree, it is only valid() + // if it is pointing at an existing node. +something = "indeed"; // this will commit the seed to the tree, mutating at the proper place CHECK(root.has_child("I am something")); CHECK(root["I am something"].val() == "indeed"); -CHECK(something.valid()); +CHECK(something.valid()); // it was already valid CHECK(!something.is_seed()); // now the tree has this node, so the // ref is no longer a seed +CHECK(something.readable()); // and it is now readable +// // now the constref is also valid (but it needs to be reassigned): ryml::ConstNodeRef constsomethingnew = wroot["I am something"]; CHECK(constsomethingnew.valid()); +CHECK(constsomethingnew.readable()); // note that the old constref is now stale, because it only keeps // the state at creation: CHECK(!constsomething.valid()); +CHECK(!constsomething.readable()); +// +// ----------------------------------------------------------- +// Remember: a seed node cannot be used to read from the tree! +// ----------------------------------------------------------- +// +// The seed node needs to be created and become readable first. +// +// Trying to invoke any tree-reading method on a node that is not +// readable will cause an assertion (see RYML_USE_ASSERT). +// +// It is your responsibility to verify that the preconditions are +// met. If you are not sure about the structure of your data, +// write your code defensively to signify your full intent: +// +ryml::NodeRef wbar = wroot["bar"]; +if(wbar.readable() && wbar.is_seq()) // .is_seq() requires .readable() +{ + CHECK(wbar[0].readable() && wbar[0].val() == "20"); + CHECK( ! wbar[100].readable() || wbar[100].val() == "100"); // <- no crash because it is not .readable(), so never tries to call .val() + // this would work as well: + CHECK( ! wbar[0].is_seed() && wbar[0].val() == "20"); + CHECK(wbar[100].is_seed() || wbar[100].val() == "100"); +} //------------------------------------------------------------------ // Emitting: -// emit to a FILE* -ryml::emit_yaml(tree, stdout); -// emit to a stream -std::stringstream ss; -ss << tree; -std::string stream_result = ss.str(); -// emit to a buffer: -std::string str_result = ryml::emitrs_yaml(tree); -// can emit to any given buffer: -char buf[1024]; -ryml::csubstr buf_result = ryml::emit_yaml(tree, buf); -// now check ryml::csubstr expected_result = R"(foo: says who bar: - 20 @@ -673,466 +691,36 @@ newmap: {} newmap (serialized): {} I am something: indeed )"; -CHECK(buf_result == expected_result); -CHECK(str_result == expected_result); -CHECK(stream_result == expected_result); -// There are many possibilities to emit to buffer; -// please look at the emit sample functions below. - -//------------------------------------------------------------------ -// ConstNodeRef vs NodeRef - -ryml::NodeRef noderef = tree["bar"][0]; -ryml::ConstNodeRef constnoderef = tree["bar"][0]; - -// ConstNodeRef cannot be used to mutate the tree: -//constnoderef = "21"; // compile error -//constnoderef << "22"; // compile error -// ... but a NodeRef can: -noderef = "21"; // ok, can assign because it's not const -CHECK(tree["bar"][0].val() == "21"); -noderef << "22"; // ok, can serialize and assign because it's not const -CHECK(tree["bar"][0].val() == "22"); - -// it is not possible to obtain a NodeRef from a ConstNodeRef: -// noderef = constnoderef; // compile error - -// it is always possible to obtain a ConstNodeRef from a NodeRef: -constnoderef = noderef; // ok can assign const <- nonconst - -// If a tree is const, then only ConstNodeRef's can be -// obtained from that tree: -ryml::Tree const& consttree = tree; -//noderef = consttree["bar"][0]; // compile error -noderef = tree["bar"][0]; // ok -constnoderef = consttree["bar"][0]; // ok - -// ConstNodeRef and NodeRef can be compared for equality. -// Equality means they point at the same node. -CHECK(constnoderef == noderef); -CHECK(!(constnoderef != noderef)); - -//------------------------------------------------------------------ -// Dealing with UTF8 -ryml::Tree langs = ryml::parse_in_arena(R"( -en: Planet (Gas) -fr: Planète (Gazeuse) -ru: Планета (Газ) -ja: 惑星(ガス) -zh: 行星(气体) -# UTF8 decoding only happens in double-quoted strings, -# as per the YAML standard -decode this: "\u263A \xE2\x98\xBA" -and this as well: "\u2705 \U0001D11E" -)"); -// in-place UTF8 just works: -CHECK(langs["en"].val() == "Planet (Gas)"); -CHECK(langs["fr"].val() == "Planète (Gazeuse)"); -CHECK(langs["ru"].val() == "Планета (Газ)"); -CHECK(langs["ja"].val() == "惑星(ガス)"); -CHECK(langs["zh"].val() == "行星(气体)"); -// and \x \u \U codepoints are decoded (but only when they appear -// inside double-quoted strings, as dictated by the YAML -// standard): -CHECK(langs["decode this"].val() == "☺ ☺"); -CHECK(langs["and this as well"].val() == "✅ 𝄞"); - -//------------------------------------------------------------------ -// Getting the location of nodes in the source: -// -// Location tracking is opt-in: -ryml::Parser parser(ryml::ParserOptions().locations(true)); -// Now the parser will start by building the accelerator structure: -ryml::Tree tree2 = parser.parse_in_arena("expected.yml", expected_result); -// ... and use it when querying -ryml::Location loc = parser.location(tree2["bar"][1]); -CHECK(parser.location_contents(loc).begins_with("30")); -CHECK(loc.line == 3u); -CHECK(loc.col == 4u); -// Parse YAML code in place, potentially mutating the buffer. -// It is also possible to: -// - parse a read-only buffer using parse_in_arena() -// - reuse an existing tree (advised) -// - reuse an existing parser (advised) -char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}"; -ryml::Tree tree = ryml::parse_in_place(yml_buf); - -// Note: it will always be significantly faster to use mutable -// buffers and reuse tree+parser. -// -// Below you will find samples that show how to achieve reuse; but -// please note that for brevity and clarity, many of the examples -// here are parsing immutable buffers, and not reusing tree or -// parser. - - -//------------------------------------------------------------------ -// API overview - -// ryml has a two-level API: -// -// The lower level index API is based on the indices of nodes, -// where the node's id is the node's position in the tree's data -// array. This API is very efficient, but somewhat difficult to use: -size_t root_id = tree.root_id(); -size_t bar_id = tree.find_child(root_id, "bar"); // need to get the index right -CHECK(tree.is_map(root_id)); // all of the index methods are in the tree -CHECK(tree.is_seq(bar_id)); // ... and receive the subject index - -// The node API is a lightweight abstraction sitting on top of the -// index API, but offering a much more convenient interaction: -ryml::ConstNodeRef root = tree.rootref(); -ryml::ConstNodeRef bar = tree["bar"]; -CHECK(root.is_map()); -CHECK(bar.is_seq()); -// A node ref is a lightweight handle to the tree and associated id: -CHECK(root.tree() == &tree); // a node ref points at its tree, WITHOUT refcount -CHECK(root.id() == root_id); // a node ref's id is the index of the node -CHECK(bar.id() == bar_id); // a node ref's id is the index of the node - -// The node API translates very cleanly to the index API, so most -// of the code examples below are using the node API. - -// One significant point of the node API is that it holds a raw -// pointer to the tree. Care must be taken to ensure the lifetimes -// match, so that a node will never access the tree after the tree -// went out of scope. - - -//------------------------------------------------------------------ -// To read the parsed tree - -// ConstNodeRef::operator[] does a lookup, is O(num_children[node]). -CHECK(tree["foo"].is_keyval()); -CHECK(tree["foo"].key() == "foo"); -CHECK(tree["foo"].val() == "1"); -CHECK(tree["bar"].is_seq()); -CHECK(tree["bar"].has_key()); -CHECK(tree["bar"].key() == "bar"); -// maps use string keys, seqs use integral keys: -CHECK(tree["bar"][0].val() == "2"); -CHECK(tree["bar"][1].val() == "3"); -CHECK(tree["john"].val() == "doe"); -// An integral key is the position of the child within its parent, -// so even maps can also use int keys, if the key position is -// known. -CHECK(tree[0].id() == tree["foo"].id()); -CHECK(tree[1].id() == tree["bar"].id()); -CHECK(tree[2].id() == tree["john"].id()); -// Tree::operator[](int) searches a ***root*** child by its position. -CHECK(tree[0].id() == tree["foo"].id()); // 0: first child of root -CHECK(tree[1].id() == tree["bar"].id()); // 1: first child of root -CHECK(tree[2].id() == tree["john"].id()); // 2: first child of root -// NodeRef::operator[](int) searches a ***node*** child by its position: -CHECK(bar[0].val() == "2"); // 0 means first child of bar -CHECK(bar[1].val() == "3"); // 1 means second child of bar -// NodeRef::operator[](string): -// A string key is the key of the node: lookup is by name. So it -// is only available for maps, and it is NOT available for seqs, -// since seq members do not have keys. -CHECK(tree["foo"].key() == "foo"); -CHECK(tree["bar"].key() == "bar"); -CHECK(tree["john"].key() == "john"); -CHECK(bar.is_seq()); -// CHECK(bar["BOOM!"].is_seed()); // error, seqs do not have key lookup - -// Note that maps can also use index keys as well as string keys: -CHECK(root["foo"].id() == root[0].id()); -CHECK(root["bar"].id() == root[1].id()); -CHECK(root["john"].id() == root[2].id()); - -// IMPORTANT. The ryml tree uses indexed linked lists for storing -// children, so the complexity of `Tree::operator[csubstr]` and -// `Tree::operator[size_t]` is linear on the number of root -// children. If you use `Tree::operator[]` with a large tree where -// the root has many children, you will see a performance hit. -// -// To avoid this hit, you can create your own accelerator -// structure. For example, before doing a lookup, do a single -// traverse at the root level to fill an `map` -// mapping key names to node indices; with a node index, a lookup -// (via `Tree::get()`) is O(1), so this way you can get O(log n) -// lookup from a key. (But please do not use `std::map` if you -// care about performance; use something else like a flat map or -// sorted vector). -// -// As for node refs, the difference from `NodeRef::operator[]` and -// `ConstNodeRef::operator[]` to `Tree::operator[]` is that the -// latter refers to the root node, whereas the former are invoked -// on their target node. But the lookup process works the same for -// both and their algorithmic complexity is the same: they are -// both linear in the number of direct children. But of course, -// depending on the data, that number may be very different from -// one to another. - -//------------------------------------------------------------------ -// Hierarchy: - -{ - ryml::ConstNodeRef foo = root.first_child(); - ryml::ConstNodeRef john = root.last_child(); - CHECK(tree.size() == 6); // O(1) number of nodes in the tree - CHECK(root.num_children() == 3); // O(num_children[root]) - CHECK(foo.num_siblings() == 3); // O(num_children[parent(foo)]) - CHECK(foo.parent().id() == root.id()); // parent() is O(1) - CHECK(root.first_child().id() == root["foo"].id()); // first_child() is O(1) - CHECK(root.last_child().id() == root["john"].id()); // last_child() is O(1) - CHECK(john.first_sibling().id() == foo.id()); - CHECK(foo.last_sibling().id() == john.id()); - // prev_sibling(), next_sibling(): (both are O(1)) - CHECK(foo.num_siblings() == root.num_children()); - CHECK(foo.prev_sibling().id() == ryml::NONE); // foo is the first_child() - CHECK(foo.next_sibling().key() == "bar"); - CHECK(foo.next_sibling().next_sibling().key() == "john"); - CHECK(foo.next_sibling().next_sibling().next_sibling().id() == ryml::NONE); // john is the last_child() -} - - -//------------------------------------------------------------------ -// Iterating: -{ - ryml::csubstr expected_keys[] = {"foo", "bar", "john"}; - // iterate children using the high-level node API: - { - size_t count = 0; - for(ryml::ConstNodeRef const& child : root.children()) - CHECK(child.key() == expected_keys[count++]); - } - // iterate siblings using the high-level node API: - { - size_t count = 0; - for(ryml::ConstNodeRef const& child : root["foo"].siblings()) - CHECK(child.key() == expected_keys[count++]); - } - // iterate children using the lower-level tree index API: - { - size_t count = 0; - for(size_t child_id = tree.first_child(root_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id)) - CHECK(tree.key(child_id) == expected_keys[count++]); - } - // iterate siblings using the lower-level tree index API: - // (notice the only difference from above is in the loop - // preamble, which calls tree.first_sibling(bar_id) instead of - // tree.first_child(root_id)) - { - size_t count = 0; - for(size_t child_id = tree.first_sibling(bar_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id)) - CHECK(tree.key(child_id) == expected_keys[count++]); - } -} - - -//------------------------------------------------------------------ -// Gotchas: -CHECK(!tree["bar"].has_val()); // seq is a container, so no val -CHECK(!tree["bar"][0].has_key()); // belongs to a seq, so no key -CHECK(!tree["bar"][1].has_key()); // belongs to a seq, so no key -//CHECK(tree["bar"].val() == BOOM!); // ... so attempting to get a val is undefined behavior -//CHECK(tree["bar"][0].key() == BOOM!); // ... so attempting to get a key is undefined behavior -//CHECK(tree["bar"][1].key() == BOOM!); // ... so attempting to get a key is undefined behavior - - -//------------------------------------------------------------------ -// Deserializing: use operator>> -{ - int foo = 0, bar0 = 0, bar1 = 0; - std::string john; - root["foo"] >> foo; - root["bar"][0] >> bar0; - root["bar"][1] >> bar1; - root["john"] >> john; // requires from_chars(std::string). see serialization samples below. - CHECK(foo == 1); - CHECK(bar0 == 2); - CHECK(bar1 == 3); - CHECK(john == "doe"); -} - - -//------------------------------------------------------------------ -// Modifying existing nodes: operator<< vs operator= - -// As implied by its name, ConstNodeRef is a reference to a const -// node. It can be used to read from the node, but not write to it -// or modify the hierarchy of the node. If any modification is -// desired then a NodeRef must be used instead: -ryml::NodeRef wroot = tree.rootref(); - -// operator= assigns an existing string to the receiving node. -// This pointer will be in effect until the tree goes out of scope -// so beware to only assign from strings outliving the tree. -wroot["foo"] = "says you"; -wroot["bar"][0] = "-2"; -wroot["bar"][1] = "-3"; -wroot["john"] = "ron"; -// Now the tree is _pointing_ at the memory of the strings above. -// That is OK because those are static strings and will outlive -// the tree. -CHECK(root["foo"].val() == "says you"); -CHECK(root["bar"][0].val() == "-2"); -CHECK(root["bar"][1].val() == "-3"); -CHECK(root["john"].val() == "ron"); -// WATCHOUT: do not assign from temporary objects: -// { -// std::string crash("will dangle"); -// root["john"] = ryml::to_csubstr(crash); -// } -// CHECK(root["john"] == "dangling"); // CRASH! the string was deallocated - -// operator<< first serializes the input to the tree's arena, then -// assigns the serialized string to the receiving node. This avoids -// constraints with the lifetime, since the arena lives with the tree. -CHECK(tree.arena().empty()); -wroot["foo"] << "says who"; // requires to_chars(). see serialization samples below. -wroot["bar"][0] << 20; -wroot["bar"][1] << 30; -wroot["john"] << "deere"; -CHECK(root["foo"].val() == "says who"); -CHECK(root["bar"][0].val() == "20"); -CHECK(root["bar"][1].val() == "30"); -CHECK(root["john"].val() == "deere"); -CHECK(tree.arena() == "says who2030deere"); // the result of serializations to the tree arena -// using operator<< instead of operator=, the crash above is avoided: -{ - std::string ok("in_scope"); - // root["john"] = ryml::to_csubstr(ok); // don't, will dangle - wroot["john"] << ryml::to_csubstr(ok); // OK, copy to the tree's arena -} -CHECK(root["john"] == "in_scope"); // OK! -CHECK(tree.arena() == "says who2030deerein_scope"); // the result of serializations to the tree arena - - -//------------------------------------------------------------------ -// Adding new nodes: - -// adding a keyval node to a map: -CHECK(root.num_children() == 3); -wroot["newkeyval"] = "shiny and new"; // using these strings -wroot.append_child() << ryml::key("newkeyval (serialized)") << "shiny and new (serialized)"; // serializes and assigns the serialization -CHECK(root.num_children() == 5); -CHECK(root["newkeyval"].key() == "newkeyval"); -CHECK(root["newkeyval"].val() == "shiny and new"); -CHECK(root["newkeyval (serialized)"].key() == "newkeyval (serialized)"); -CHECK(root["newkeyval (serialized)"].val() == "shiny and new (serialized)"); -CHECK( ! tree.in_arena(root["newkeyval"].key())); // it's using directly the static string above -CHECK( ! tree.in_arena(root["newkeyval"].val())); // it's using directly the static string above -CHECK( tree.in_arena(root["newkeyval (serialized)"].key())); // it's using a serialization of the string above -CHECK( tree.in_arena(root["newkeyval (serialized)"].val())); // it's using a serialization of the string above -// adding a val node to a seq: -CHECK(root["bar"].num_children() == 2); -wroot["bar"][2] = "oh so nice"; -wroot["bar"][3] << "oh so nice (serialized)"; -CHECK(root["bar"].num_children() == 4); -CHECK(root["bar"][2].val() == "oh so nice"); -CHECK(root["bar"][3].val() == "oh so nice (serialized)"); -// adding a seq node: -CHECK(root.num_children() == 5); -wroot["newseq"] |= ryml::SEQ; -wroot.append_child() << ryml::key("newseq (serialized)") |= ryml::SEQ; -CHECK(root.num_children() == 7); -CHECK(root["newseq"].num_children() == 0); -CHECK(root["newseq (serialized)"].num_children() == 0); -// adding a map node: -CHECK(root.num_children() == 7); -wroot["newmap"] |= ryml::MAP; -wroot.append_child() << ryml::key("newmap (serialized)") |= ryml::SEQ; -CHECK(root.num_children() == 9); -CHECK(root["newmap"].num_children() == 0); -CHECK(root["newmap (serialized)"].num_children() == 0); -// -// When the tree is mutable, operator[] does not mutate the tree -// until the returned node is written to. -// -// Until such time, the NodeRef object keeps in itself the required -// information to write to the proper place in the tree. This is -// called being in a "seed" state. -// -// This means that passing a key/index which does not exist will -// not mutate the tree, but will instead store (in the node) the -// proper place of the tree to be able to do so, if and when it is -// required. -// -// This is a significant difference from eg, the behavior of -// std::map, which mutates the map immediately within the call to -// operator[]. -// -// All of the points above apply only if the tree is mutable. If -// the tree is const, then a NodeRef cannot be obtained from it; -// only a ConstNodeRef, which can never be used to mutate the -// tree. -CHECK(!root.has_child("I am not nothing")); -ryml::NodeRef nothing = wroot["I am nothing"]; -CHECK(nothing.valid()); // points at the tree, and a specific place in the tree -CHECK(nothing.is_seed()); // ... but nothing is there yet. -CHECK(!root.has_child("I am nothing")); // same as above -ryml::NodeRef something = wroot["I am something"]; -ryml::ConstNodeRef constsomething = wroot["I am something"]; -CHECK(!root.has_child("I am something")); // same as above -CHECK(something.valid()); -CHECK(something.is_seed()); // same as above -CHECK(!constsomething.valid()); // NOTE: because a ConstNodeRef - // cannot be used to mutate a - // tree, it is only valid() if it - // is pointing at an existing - // node. -something = "indeed"; // this will commit to the tree, mutating at the proper place -CHECK(root.has_child("I am something")); -CHECK(root["I am something"].val() == "indeed"); -CHECK(something.valid()); -CHECK(!something.is_seed()); // now the tree has this node, so the - // ref is no longer a seed -// now the constref is also valid (but it needs to be reassigned): -ryml::ConstNodeRef constsomethingnew = wroot["I am something"]; -CHECK(constsomethingnew.valid()); -// note that the old constref is now stale, because it only keeps -// the state at creation: -CHECK(!constsomething.valid()); - - -//------------------------------------------------------------------ -// Emitting: // emit to a FILE* -ryml::emit_yaml(tree, stdout); // there is also emit_json() +ryml::emit_yaml(tree, stdout); // emit to a stream std::stringstream ss; ss << tree; std::string stream_result = ss.str(); // emit to a buffer: -std::string str_result = ryml::emitrs_yaml(tree); // there is also emitrs_json() +std::string str_result = ryml::emitrs_yaml(tree); // can emit to any given buffer: char buf[1024]; ryml::csubstr buf_result = ryml::emit_yaml(tree, buf); // now check -ryml::csubstr expected_result = R"(foo: says who -bar: -- 20 -- 30 -- oh so nice -- oh so nice (serialized) -john: in_scope -newkeyval: shiny and new -newkeyval (serialized): shiny and new (serialized) -newseq: [] -newseq (serialized): [] -newmap: {} -newmap (serialized): [] -I am something: indeed -)"; CHECK(buf_result == expected_result); CHECK(str_result == expected_result); CHECK(stream_result == expected_result); // There are many possibilities to emit to buffer; // please look at the emit sample functions below. + //------------------------------------------------------------------ // ConstNodeRef vs NodeRef ryml::NodeRef noderef = tree["bar"][0]; ryml::ConstNodeRef constnoderef = tree["bar"][0]; -// ConstNodeRef cannot be used to mutate the tree, but a NodeRef can: +// ConstNodeRef cannot be used to mutate the tree: //constnoderef = "21"; // compile error //constnoderef << "22"; // compile error +// ... but a NodeRef can: noderef = "21"; // ok, can assign because it's not const CHECK(tree["bar"][0].val() == "21"); noderef << "22"; // ok, can serialize and assign because it's not const @@ -1164,10 +752,12 @@ fr: Planète (Gazeuse) ru: Планета (Газ) ja: 惑星(ガス) zh: 行星(气体) -# UTF8 decoding only happens in double-quoted strings,\ +# UTF8 decoding only happens in double-quoted strings, # as per the YAML standard decode this: "\u263A \xE2\x98\xBA" and this as well: "\u2705 \U0001D11E" +not decoded: '\u263A \xE2\x98\xBA' +neither this: '\u2705 \U0001D11E' )"); // in-place UTF8 just works: CHECK(langs["en"].val() == "Planet (Gas)"); @@ -1175,20 +765,28 @@ CHECK(langs["fr"].val() == "Planète (Gazeuse)"); CHECK(langs["ru"].val() == "Планета (Газ)"); CHECK(langs["ja"].val() == "惑星(ガス)"); CHECK(langs["zh"].val() == "行星(气体)"); -// and \x \u \U codepoints are decoded (but only when they appear +// and \x \u \U codepoints are decoded, but only when they appear // inside double-quoted strings, as dictated by the YAML -// standard): +// standard: CHECK(langs["decode this"].val() == "☺ ☺"); CHECK(langs["and this as well"].val() == "✅ 𝄞"); +CHECK(langs["not decoded"].val() == "\\u263A \\xE2\\x98\\xBA"); +CHECK(langs["neither this"].val() == "\\u2705 \\U0001D11E"); //------------------------------------------------------------------ // Getting the location of nodes in the source: -ryml::Parser parser; +// +// Location tracking is opt-in: +ryml::Parser parser(ryml::ParserOptions().locations(true)); +// Now the parser will start by building the accelerator structure: ryml::Tree tree2 = parser.parse_in_arena("expected.yml", expected_result); +// ... and use it when querying ryml::Location loc = parser.location(tree2["bar"][1]); CHECK(parser.location_contents(loc).begins_with("30")); CHECK(loc.line == 3u); CHECK(loc.col == 4u); +// For further details in location tracking, +// refer to the sample function below. ``` The [quickstart.cpp sample](./samples/quickstart.cpp) (from which the diff --git a/changelog/current.md b/changelog/current.md index 587333f0e..099f3d067 100644 --- a/changelog/current.md +++ b/changelog/current.md @@ -23,7 +23,7 @@ Fix major error handling problem reported in [#389](https://github.com/biojppm/r * callback instead of directly raising an exception. */ ConstNodeRef at(csubstr key) const; /** Likewise, but return a seed node when the key is not found */ - ConstNodeRef at(csubstr key); + NodeRef at(csubstr key); /** Get a child by position, with error checking; complexity is * O(pos). @@ -36,7 +36,7 @@ Fix major error handling problem reported in [#389](https://github.com/biojppm/r * callback instead of directly raising an exception. */ ConstNodeRef at(size_t pos) const; /** Likewise, but return a seed node when pos is not found */ - ConstNodeRef at(csubstr key); + NodeRef at(csubstr key); ``` - Added macros and respective cmake options to control error handling: - `RYML_USE_ASSERT` - enable assertions regardless of build type. This is disabled by default. diff --git a/samples/quickstart.cpp b/samples/quickstart.cpp index 12ee529a2..64b89fcf4 100644 --- a/samples/quickstart.cpp +++ b/samples/quickstart.cpp @@ -528,7 +528,8 @@ void sample_quick_overview() // This means that passing a key/index which does not exist will // not mutate the tree, but will instead store (in the node) the // proper place of the tree to be able to do so, if and when it is - // required. + // required. This is why the node is said to be in "seed" state - + // it allows creating the entry in the tree in the future. // // This is a significant difference from eg, the behavior of // std::map, which mutates the map immediately within the call to @@ -538,50 +539,67 @@ void sample_quick_overview() // the tree is const, then a NodeRef cannot be obtained from it; // only a ConstNodeRef, which can never be used to mutate the // tree. + // CHECK(!root.has_child("I am not nothing")); ryml::NodeRef nothing = wroot["I am nothing"]; CHECK(nothing.valid()); // points at the tree, and a specific place in the tree CHECK(nothing.is_seed()); // ... but nothing is there yet. CHECK(!root.has_child("I am nothing")); // same as above + CHECK(!nothing.readable()); // ... and this node cannot be used to + // read anything from the tree ryml::NodeRef something = wroot["I am something"]; ryml::ConstNodeRef constsomething = wroot["I am something"]; CHECK(!root.has_child("I am something")); // same as above CHECK(something.valid()); CHECK(something.is_seed()); // same as above - CHECK(!constsomething.valid()); // NOTE: because a ConstNodeRef - // cannot be used to mutate a - // tree, it is only valid() if it - // is pointing at an existing - // node. - something = "indeed"; // this will commit to the tree, mutating at the proper place + CHECK(!something.readable()); // same as above + CHECK(!constsomething.valid()); // NOTE: because a ConstNodeRef cannot be + // used to mutate a tree, it is only valid() + // if it is pointing at an existing node. + something = "indeed"; // this will commit the seed to the tree, mutating at the proper place CHECK(root.has_child("I am something")); CHECK(root["I am something"].val() == "indeed"); - CHECK(something.valid()); + CHECK(something.valid()); // it was already valid CHECK(!something.is_seed()); // now the tree has this node, so the // ref is no longer a seed + CHECK(something.readable()); // and it is now readable + // // now the constref is also valid (but it needs to be reassigned): ryml::ConstNodeRef constsomethingnew = wroot["I am something"]; CHECK(constsomethingnew.valid()); + CHECK(constsomethingnew.readable()); // note that the old constref is now stale, because it only keeps // the state at creation: CHECK(!constsomething.valid()); + CHECK(!constsomething.readable()); + // + // ----------------------------------------------------------- + // Remember: a seed node cannot be used to read from the tree! + // ----------------------------------------------------------- + // + // The seed node needs to be created and become readable first. + // + // Trying to invoke any tree-reading method on a node that is not + // readable will cause an assertion (see RYML_USE_ASSERT). + // + // It is your responsibility to verify that the preconditions are + // met. If you are not sure about the structure of your data, + // write your code defensively to signify your full intent: + // + ryml::NodeRef wbar = wroot["bar"]; + if(wbar.readable() && wbar.is_seq()) // .is_seq() requires .readable() + { + CHECK(wbar[0].readable() && wbar[0].val() == "20"); + CHECK( ! wbar[100].readable() || wbar[100].val() == "100"); // <- no crash because it is not .readable(), so never tries to call .val() + // this would work as well: + CHECK( ! wbar[0].is_seed() && wbar[0].val() == "20"); + CHECK(wbar[100].is_seed() || wbar[100].val() == "100"); + } //------------------------------------------------------------------ // Emitting: - // emit to a FILE* - ryml::emit_yaml(tree, stdout); - // emit to a stream - std::stringstream ss; - ss << tree; - std::string stream_result = ss.str(); - // emit to a buffer: - std::string str_result = ryml::emitrs_yaml(tree); - // can emit to any given buffer: - char buf[1024]; - ryml::csubstr buf_result = ryml::emit_yaml(tree, buf); - // now check ryml::csubstr expected_result = R"(foo: says who bar: - 20 @@ -599,12 +617,26 @@ newmap: {} newmap (serialized): {} I am something: indeed )"; + + // emit to a FILE* + ryml::emit_yaml(tree, stdout); + // emit to a stream + std::stringstream ss; + ss << tree; + std::string stream_result = ss.str(); + // emit to a buffer: + std::string str_result = ryml::emitrs_yaml(tree); + // can emit to any given buffer: + char buf[1024]; + ryml::csubstr buf_result = ryml::emit_yaml(tree, buf); + // now check CHECK(buf_result == expected_result); CHECK(str_result == expected_result); CHECK(stream_result == expected_result); // There are many possibilities to emit to buffer; // please look at the emit sample functions below. + //------------------------------------------------------------------ // ConstNodeRef vs NodeRef @@ -650,6 +682,8 @@ zh: 行星(气体) # as per the YAML standard decode this: "\u263A \xE2\x98\xBA" and this as well: "\u2705 \U0001D11E" +not decoded: '\u263A \xE2\x98\xBA' +neither this: '\u2705 \U0001D11E' )"); // in-place UTF8 just works: CHECK(langs["en"].val() == "Planet (Gas)"); @@ -657,11 +691,13 @@ and this as well: "\u2705 \U0001D11E" CHECK(langs["ru"].val() == "Планета (Газ)"); CHECK(langs["ja"].val() == "惑星(ガス)"); CHECK(langs["zh"].val() == "行星(气体)"); - // and \x \u \U codepoints are decoded (but only when they appear + // and \x \u \U codepoints are decoded, but only when they appear // inside double-quoted strings, as dictated by the YAML - // standard): + // standard: CHECK(langs["decode this"].val() == "☺ ☺"); CHECK(langs["and this as well"].val() == "✅ 𝄞"); + CHECK(langs["not decoded"].val() == "\\u263A \\xE2\\x98\\xBA"); + CHECK(langs["neither this"].val() == "\\u2705 \\U0001D11E"); //------------------------------------------------------------------ // Getting the location of nodes in the source: diff --git a/src/c4/yml/node.hpp b/src/c4/yml/node.hpp index e1d234171..12bc6d259 100644 --- a/src/c4/yml/node.hpp +++ b/src/c4/yml/node.hpp @@ -424,8 +424,12 @@ struct RoNodeMethods /** @name at * * These functions are the analogue to operator[], with the - * difference that they */ - /** @{ */ + * difference that they emit an error instead of an + * assertion. That is, if any of the pre or post conditions is + * violated, an error is always emitted (resulting in a call to + * the error callback). + * + * @{ */ /** Find child by key; complexity is O(num_children). * @@ -440,14 +444,14 @@ struct RoNodeMethods * to read from the tree. * * @warning This method will call the error callback (regardless - * of build type) whenever any of the following preconditions is - * violated: a) the object is valid (points at a tree and a node), - * b) the calling object must be readable (must not be in seed - * state), c) the calling object must be pointing at a MAP - * node. The preconditions are similar to the non-const - * operator[](csubstr), but instead of using assertions, this - * function directly checks those conditions and calls the error - * callback if any of the checks fail. + * of build type or of the value of RYML_USE_ASSERT) whenever any + * of the following preconditions is violated: a) the object is + * valid (points at a tree and a node), b) the calling object must + * be readable (must not be in seed state), c) the calling object + * must be pointing at a MAP node. The preconditions are similar + * to the non-const operator[](csubstr), but instead of using + * assertions, this function directly checks those conditions and + * calls the error callback if any of the checks fail. * * @note since it is valid behavior for the returned node to be in * seed state, the error callback is not invoked when this @@ -476,14 +480,14 @@ struct RoNodeMethods * from the tree. * * @warning This method will call the error callback (regardless - * of build type) whenever any of the following preconditions is - * violated: a) the object is valid (points at a tree and a node), - * b) the calling object must be readable (must not be in seed - * state), c) the calling object must be pointing at a MAP - * node. The preconditions are similar to the non-const - * operator[](size_t), but instead of using assertions, this - * function directly checks those conditions and calls the error - * callback if any of the checks fail. + * of build type or of the value of RYML_USE_ASSERT) whenever any + * of the following preconditions is violated: a) the object is + * valid (points at a tree and a node), b) the calling object must + * be readable (must not be in seed state), c) the calling object + * must be pointing at a MAP node. The preconditions are similar + * to the non-const operator[](size_t), but instead of using + * assertions, this function directly checks those conditions and + * calls the error callback if any of the checks fail. * * @note since it is valid behavior for the returned node to be in * seed state, the error callback is not invoked when this @@ -751,8 +755,6 @@ struct RoNodeMethods /** This object holds a pointer to an existing tree, and a node id. It * can be used only to read from the tree. * - * - * * @warning The lifetime of the tree must be larger than that of this * object. It is up to the user to ensure that this happens. */ class RYML_EXPORT ConstNodeRef : public detail::RoNodeMethods @@ -810,7 +812,8 @@ class RYML_EXPORT ConstNodeRef : public detail::RoNodeMethods { public: @@ -932,11 +956,14 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods public: - /** @name state queries */ - /** @{ */ + /** @name state_queries + * @{ */ + /** true if the object is pointing at a tree and id. @see the doc for the NodeRef */ inline bool valid() const { return m_tree != nullptr && m_id != NONE; } - inline bool is_seed() const { return m_seed.str != nullptr || m_seed.len != NONE; } + /** true if the object is valid() and in seed state. @see the doc for the NodeRef */ + inline bool is_seed() const { return valid() && (m_seed.str != nullptr || m_seed.len != NONE); } + /** true if the object is valid() and NOT in seed state. @see the doc for the NodeRef */ inline bool readable() const { return valid() && !is_seed(); } inline void _clear_seed() { /*do the following manually or an assert is triggered: */ m_seed.str = nullptr; m_seed.len = NONE; } From 2c9c4b5e0a8eeeb497572bd92b45c29e59ed327c Mon Sep 17 00:00:00 2001 From: Joao Paulo Magalhaes Date: Fri, 5 Apr 2024 03:10:42 +0200 Subject: [PATCH 14/15] Improve the node API: - improve explanation of states - deprecate .valid() - add .readable() state - deprecate operator==(nullptr) - deprecate operator==(csubstr) --- README.md | 71 ++------- changelog/current.md | 21 ++- samples/quickstart.cpp | 36 +++-- src/c4/yml/emit.def.hpp | 2 +- src/c4/yml/emit.hpp | 8 +- src/c4/yml/node.hpp | 287 +++++++++++++++++++----------------- src/c4/yml/parse.cpp | 2 +- test/test_case.cpp | 18 +-- test/test_github_issues.cpp | 5 +- test/test_group.hpp | 3 + test/test_noderef.cpp | 28 ++-- test/test_tree.cpp | 90 +---------- 12 files changed, 235 insertions(+), 336 deletions(-) diff --git a/README.md b/README.md index d4d1a18b4..8be614084 100644 --- a/README.md +++ b/README.md @@ -541,7 +541,7 @@ CHECK(tree.arena() == "says who2030deere"); // the result of serializations to t // root["john"] = ryml::to_csubstr(ok); // don't, will dangle wroot["john"] << ryml::to_csubstr(ok); // OK, copy to the tree's arena } -CHECK(root["john"] == "in_scope"); // OK! +CHECK(root["john"].val() == "in_scope"); // OK! // serializing floating points: wroot["float"] << 2.4; // to force a particular precision or float format: @@ -592,8 +592,9 @@ CHECK(root["newmap"].is_map()); CHECK(root["newmap (serialized)"].num_children() == 0); CHECK(root["newmap (serialized)"].is_map()); // -// When the tree is mutable, operator[] does not mutate the tree -// until the returned node is written to. +// When the tree is mutable, operator[] first searches the tree +// for the does not mutate the tree until the returned node is +// written to. // // Until such time, the NodeRef object keeps in itself the required // information to write to the proper place in the tree. This is @@ -615,8 +616,10 @@ CHECK(root["newmap (serialized)"].is_map()); // tree. // CHECK(!root.has_child("I am not nothing")); -ryml::NodeRef nothing = wroot["I am nothing"]; -CHECK(nothing.valid()); // points at the tree, and a specific place in the tree +ryml::NodeRef nothing; +CHECK(nothing.invalid()); // invalid because it points at nothing +nothing = wroot["I am nothing"]; +CHECK(!nothing.invalid()); // points at the tree, and a specific place in the tree CHECK(nothing.is_seed()); // ... but nothing is there yet. CHECK(!root.has_child("I am nothing")); // same as above CHECK(!nothing.readable()); // ... and this node cannot be used to @@ -624,27 +627,27 @@ CHECK(!nothing.readable()); // ... and this node cannot be used to ryml::NodeRef something = wroot["I am something"]; ryml::ConstNodeRef constsomething = wroot["I am something"]; CHECK(!root.has_child("I am something")); // same as above -CHECK(something.valid()); +CHECK(!something.invalid()); CHECK(something.is_seed()); // same as above CHECK(!something.readable()); // same as above -CHECK(!constsomething.valid()); // NOTE: because a ConstNodeRef cannot be - // used to mutate a tree, it is only valid() - // if it is pointing at an existing node. +CHECK(constsomething.invalid()); // NOTE: because a ConstNodeRef cannot be + // used to mutate a tree, it is only valid() + // if it is pointing at an existing node. something = "indeed"; // this will commit the seed to the tree, mutating at the proper place CHECK(root.has_child("I am something")); CHECK(root["I am something"].val() == "indeed"); -CHECK(something.valid()); // it was already valid +CHECK(!something.invalid()); // it was already valid CHECK(!something.is_seed()); // now the tree has this node, so the // ref is no longer a seed CHECK(something.readable()); // and it is now readable // // now the constref is also valid (but it needs to be reassigned): ryml::ConstNodeRef constsomethingnew = wroot["I am something"]; -CHECK(constsomethingnew.valid()); +CHECK(!constsomethingnew.invalid()); CHECK(constsomethingnew.readable()); // note that the old constref is now stale, because it only keeps // the state at creation: -CHECK(!constsomething.valid()); +CHECK(constsomething.invalid()); CHECK(!constsomething.readable()); // // ----------------------------------------------------------- @@ -664,6 +667,7 @@ ryml::NodeRef wbar = wroot["bar"]; if(wbar.readable() && wbar.is_seq()) // .is_seq() requires .readable() { CHECK(wbar[0].readable() && wbar[0].val() == "20"); + CHECK( ! wbar[100].readable()); CHECK( ! wbar[100].readable() || wbar[100].val() == "100"); // <- no crash because it is not .readable(), so never tries to call .val() // this would work as well: CHECK( ! wbar[0].is_seed() && wbar[0].val() == "20"); @@ -785,49 +789,6 @@ ryml::Location loc = parser.location(tree2["bar"][1]); CHECK(parser.location_contents(loc).begins_with("30")); CHECK(loc.line == 3u); CHECK(loc.col == 4u); -// For further details in location tracking, -// refer to the sample function below. -``` - -The [quickstart.cpp sample](./samples/quickstart.cpp) (from which the -above overview was taken) has many more detailed examples, and should -be your first port of call to find out any particular point about -ryml's API. It is tested in the CI, and thus has the correct behavior. -There you can find the following subjects being addressed: - -```c++ -sample_quick_overview(); ///< briefly skim over most of the features -sample_substr(); ///< about ryml's string views (from c4core) -sample_parse_file(); ///< ready-to-go example of parsing a file from disk -sample_parse_in_place(); ///< parse a mutable YAML source buffer -sample_parse_in_arena(); ///< parse a read-only YAML source buffer -sample_parse_reuse_tree(); ///< parse into an existing tree, maybe into a node -sample_parse_reuse_parser(); ///< reuse an existing parser -sample_parse_reuse_tree_and_parser(); ///< how to reuse existing trees and parsers -sample_iterate_trees(); ///< visit individual nodes and iterate through trees -sample_create_trees(); ///< programatically create trees -sample_tree_arena(); ///< interact with the tree's serialization arena -sample_fundamental_types(); ///< serialize/deserialize fundamental types -sample_formatting(); ///< control formatting when serializing/deserializing -sample_base64(); ///< encode/decode base64 -sample_user_scalar_types(); ///< serialize/deserialize scalar (leaf/string) types -sample_user_container_types(); ///< serialize/deserialize container (map or seq) types -sample_std_types(); ///< serialize/deserialize STL containers -sample_float_precision(); ///< control precision of serialized floats -sample_emit_to_container(); ///< emit to memory, eg a string or vector-like container -sample_emit_to_stream(); ///< emit to a stream, eg std::ostream -sample_emit_to_file(); ///< emit to a FILE* -sample_emit_nested_node(); ///< pick a nested node as the root when emitting -sample_emit_style(); ///< set the nodes to FLOW/BLOCK format -sample_json(); ///< JSON parsing and emitting -sample_anchors_and_aliases(); ///< deal with YAML anchors and aliases -sample_tags(); ///< deal with YAML type tags -sample_docs(); ///< deal with YAML docs -sample_error_handler(); ///< set a custom error handler -sample_global_allocator(); ///< set a global allocator for ryml -sample_per_tree_allocator(); ///< set per-tree allocators -sample_static_trees(); ///< how to use static trees in ryml -sample_location_tracking(); ///< track node locations in the parsed source tree ``` diff --git a/changelog/current.md b/changelog/current.md index 099f3d067..afad6d057 100644 --- a/changelog/current.md +++ b/changelog/current.md @@ -2,10 +2,10 @@ Fix major error handling problem reported in [#389](https://github.com/biojppm/rapidyaml/issues/389) ([PR#411](https://github.com/biojppm/rapidyaml/pull/411)): - - The `NodeRef` and `ConstNodeRef` classes had many methods marked `noexcept` that were doing assertions which could throw exceptions, causing an abort instead of a throw whenever the assertion called an exception-throwing error callback. + - The `NodeRef` and `ConstNodeRef` classes are now conditional noexcept using `RYML_NOEXCEPT`, which evaluates either to nothing when assertions are enabled, and to `noexcept` otherwise. The problem was that these classes had many methods explicitly marked `noexcept`, but were doing assertions which could throw exceptions, causing an abort instead of a throw whenever the assertion called an exception-throwing error callback. - Also, this problem was compounded by exceptions being enabled in every build type -- despite the intention to have them only in debug builds. There was a problem in the preprocessor code to enable assertions which led to assertions being enabled in release builds even when `RYML_USE_ASSERT` was defined to 0. Thanks to @jdrouhard for reporting this. - Although the code is and was extensively tested, the testing was addressing mostly the happy path. Tests were added to ensure that the error behavior is as intended. - - Together with this changeset, a major revision was carried out of the asserting/checking status of each function in the node classes. In most cases, assertions were added to functions cases that were missing them. So **beware** - user code that was invalid will now assert or error out. Also, assertions and checks are now directed as much as possible to the callbacks of the closest scope, ie if a tree has custom callbacks, errors should go through those callbacks. + - Together with this changeset, a major revision was carried out of the asserting/checking status of each function in the node classes. In most cases, assertions were added to functions cases that were missing them. So **beware** - user code that was invalid will now assert or error out. Also, assertions and checks are now directed as much as possible to the callbacks of the closest scope, ie if a tree has custom callbacks, errors within the tree class should go through those callbacks. - Also, the intended assertion behavior is now in place: *no assertions in release builds*. **Beware** as well - user code which was relying on this will now silently succeed and return garbage in release builds. See the next points, which may help: - Added new methods to the `NodeRef`/`ConstNodeRef` classes: ```c++ @@ -38,9 +38,21 @@ Fix major error handling problem reported in [#389](https://github.com/biojppm/r /** Likewise, but return a seed node when pos is not found */ NodeRef at(csubstr key); ``` + - The state for `NodeRef` was refined, and now there are three mutually exclusive states (and class predicates) for an object of this class: + - `.invalid()` when the object was not initialized to any node + - `.readable()` when the object points at an existing tree+node + - `.is_seed()` when the object points at an hypotethic tree+node + - The previous state `.valid()` was deprecated: its semantics were confusing as it actually could be any of `.readable()` or `.is_seed()` + - Deprecated also the following methods for `NodeRef`/`ConstNodeRef`: + ```c++ + RYML_DEPRECATED() bool operator== (std::nullptr_t) const; + RYML_DEPRECATED() bool operator!= (std::nullptr_t) const; + RYML_DEPRECATED() bool operator== (csubstr val) const; + RYML_DEPRECATED() bool operator!= (csubstr val) const; + ``` - Added macros and respective cmake options to control error handling: - - `RYML_USE_ASSERT` - enable assertions regardless of build type. This is disabled by default. - - `RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS` - defines the same macro, which will make the default error handler provided by ryml throw exceptions instead of calling `std::abort()`. This is disabled by default. + - `RYML_USE_ASSERT` - enable assertions regardless of build type. This is disabled by default. This macro was already defined; the current PR adds the cmake option. + - `RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS` - make the default error handler provided by ryml throw exceptions instead of calling `std::abort()`. This is disabled by default. - Also, `RYML_DEBUG_BREAK()` is now enabled only if `RYML_DBG` is defined, as reported in [#362](https://github.com/biojppm/rapidyaml/issues/362). @@ -62,6 +74,7 @@ Fix major error handling problem reported in [#389](https://github.com/biojppm/r - Fix [#356](https://github.com/biojppm/rapidyaml/issues/356) - fix overzealous check in `emit_as()`. An id may be larger than the tree's size, eg when nodes were removed. ([PR#357](https://github.com/biojppm/rapidyaml/pull/357)). - Fix [#417](https://github.com/biojppm/rapidyaml/issues/417)) - add quickstart example explaining how to avoid precision loss while serializing floats ([PR#420](https://github.com/biojppm/rapidyaml/pull/420)). + ### Thanks - @Neko-Box-Coder diff --git a/samples/quickstart.cpp b/samples/quickstart.cpp index 64b89fcf4..889cff178 100644 --- a/samples/quickstart.cpp +++ b/samples/quickstart.cpp @@ -467,7 +467,7 @@ void sample_quick_overview() // root["john"] = ryml::to_csubstr(ok); // don't, will dangle wroot["john"] << ryml::to_csubstr(ok); // OK, copy to the tree's arena } - CHECK(root["john"] == "in_scope"); // OK! + CHECK(root["john"].val() == "in_scope"); // OK! // serializing floating points: wroot["float"] << 2.4; // to force a particular precision or float format: @@ -518,8 +518,9 @@ void sample_quick_overview() CHECK(root["newmap (serialized)"].num_children() == 0); CHECK(root["newmap (serialized)"].is_map()); // - // When the tree is mutable, operator[] does not mutate the tree - // until the returned node is written to. + // When the tree is mutable, operator[] first searches the tree + // for the does not mutate the tree until the returned node is + // written to. // // Until such time, the NodeRef object keeps in itself the required // information to write to the proper place in the tree. This is @@ -541,8 +542,10 @@ void sample_quick_overview() // tree. // CHECK(!root.has_child("I am not nothing")); - ryml::NodeRef nothing = wroot["I am nothing"]; - CHECK(nothing.valid()); // points at the tree, and a specific place in the tree + ryml::NodeRef nothing; + CHECK(nothing.invalid()); // invalid because it points at nothing + nothing = wroot["I am nothing"]; + CHECK(!nothing.invalid()); // points at the tree, and a specific place in the tree CHECK(nothing.is_seed()); // ... but nothing is there yet. CHECK(!root.has_child("I am nothing")); // same as above CHECK(!nothing.readable()); // ... and this node cannot be used to @@ -550,27 +553,27 @@ void sample_quick_overview() ryml::NodeRef something = wroot["I am something"]; ryml::ConstNodeRef constsomething = wroot["I am something"]; CHECK(!root.has_child("I am something")); // same as above - CHECK(something.valid()); + CHECK(!something.invalid()); CHECK(something.is_seed()); // same as above CHECK(!something.readable()); // same as above - CHECK(!constsomething.valid()); // NOTE: because a ConstNodeRef cannot be - // used to mutate a tree, it is only valid() - // if it is pointing at an existing node. + CHECK(constsomething.invalid()); // NOTE: because a ConstNodeRef cannot be + // used to mutate a tree, it is only valid() + // if it is pointing at an existing node. something = "indeed"; // this will commit the seed to the tree, mutating at the proper place CHECK(root.has_child("I am something")); CHECK(root["I am something"].val() == "indeed"); - CHECK(something.valid()); // it was already valid + CHECK(!something.invalid()); // it was already valid CHECK(!something.is_seed()); // now the tree has this node, so the // ref is no longer a seed CHECK(something.readable()); // and it is now readable // // now the constref is also valid (but it needs to be reassigned): ryml::ConstNodeRef constsomethingnew = wroot["I am something"]; - CHECK(constsomethingnew.valid()); + CHECK(!constsomethingnew.invalid()); CHECK(constsomethingnew.readable()); // note that the old constref is now stale, because it only keeps // the state at creation: - CHECK(!constsomething.valid()); + CHECK(constsomething.invalid()); CHECK(!constsomething.readable()); // // ----------------------------------------------------------- @@ -590,6 +593,7 @@ void sample_quick_overview() if(wbar.readable() && wbar.is_seq()) // .is_seq() requires .readable() { CHECK(wbar[0].readable() && wbar[0].val() == "20"); + CHECK( ! wbar[100].readable()); CHECK( ! wbar[100].readable() || wbar[100].val() == "100"); // <- no crash because it is not .readable(), so never tries to call .val() // this would work as well: CHECK( ! wbar[0].is_seed() && wbar[0].val() == "20"); @@ -1994,13 +1998,13 @@ cars: GTO void sample_create_trees() { ryml::NodeRef doe; - CHECK(!doe.valid()); // it's pointing at nowhere + CHECK(doe.invalid()); // it's pointing at nowhere ryml::Tree tree; ryml::NodeRef root = tree.rootref(); root |= ryml::MAP; // mark root as a map doe = root["doe"]; - CHECK(doe.valid()); // it's now pointing at the tree + CHECK(!doe.invalid()); // it's now pointing at the tree CHECK(doe.is_seed()); // but the tree has nothing there, so this is only a seed // set the value of the node @@ -3957,8 +3961,8 @@ ship_to: *id001 CHECK( ! tree["val"].is_key_ref()); // notice *valref is now turned to val CHECK( ! tree["val"].is_val_ref()); // notice *valref is now turned to val - CHECK(tree["ship_to"]["city"] == "East Centerville"); - CHECK(tree["ship_to"]["state"] == "KS"); + CHECK(tree["ship_to"]["city"].val() == "East Centerville"); + CHECK(tree["ship_to"]["state"].val() == "KS"); } diff --git a/src/c4/yml/emit.def.hpp b/src/c4/yml/emit.def.hpp index cf184ad77..8f904bcc2 100644 --- a/src/c4/yml/emit.def.hpp +++ b/src/c4/yml/emit.def.hpp @@ -38,7 +38,7 @@ substr Emitter::emit_as(EmitType_e type, Tree const& t, bool error_on_ex template substr Emitter::emit_as(EmitType_e type, ConstNodeRef const& n, bool error_on_excess) { - _RYML_CB_CHECK(n.tree()->callbacks(), n.valid()); + _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); return this->emit_as(type, *n.tree(), n.id(), error_on_excess); } diff --git a/src/c4/yml/emit.hpp b/src/c4/yml/emit.hpp index c7cdd2a1a..09ae9fc3c 100644 --- a/src/c4/yml/emit.hpp +++ b/src/c4/yml/emit.hpp @@ -433,7 +433,7 @@ CharOwningContainer emitrs_json(Tree const& t) template substr emitrs_yaml(ConstNodeRef const& n, CharOwningContainer * cont) { - _RYML_CB_CHECK(n.tree()->callbacks(), n.valid()); + _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); return emitrs_yaml(*n.tree(), n.id(), cont); } template @@ -447,7 +447,7 @@ RYML_DEPRECATE_EMITRS substr emitrs(ConstNodeRef const& n, CharOwningContainer * template substr emitrs_json(ConstNodeRef const& n, CharOwningContainer * cont) { - _RYML_CB_CHECK(n.tree()->callbacks(), n.valid()); + _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); return emitrs_json(*n.tree(), n.id(), cont); } @@ -457,7 +457,7 @@ substr emitrs_json(ConstNodeRef const& n, CharOwningContainer * cont) template CharOwningContainer emitrs_yaml(ConstNodeRef const& n) { - _RYML_CB_CHECK(n.tree()->callbacks(), n.valid()); + _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); CharOwningContainer c; emitrs_yaml(*n.tree(), n.id(), &c); return c; @@ -473,7 +473,7 @@ RYML_DEPRECATE_EMITRS CharOwningContainer emitrs(ConstNodeRef const& n) template CharOwningContainer emitrs_json(ConstNodeRef const& n) { - _RYML_CB_CHECK(n.tree()->callbacks(), n.valid()); + _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); CharOwningContainer c; emitrs_json(*n.tree(), n.id(), &c); return c; diff --git a/src/c4/yml/node.hpp b/src/c4/yml/node.hpp index 12bc6d259..f4672d69b 100644 --- a/src/c4/yml/node.hpp +++ b/src/c4/yml/node.hpp @@ -160,10 +160,10 @@ struct RoNodeMethods #define id__ ((Impl const* C4_RESTRICT)this)->m_id // require readable: this is a precondition for reading from the // tree using this object. - #define _C4RV() \ + #define _C4RR() \ RYML_ASSERT(tree_ != nullptr); \ _RYML_CB_ASSERT(tree_->m_callbacks, id_ != NONE); \ - _RYML_CB_ASSERT(tree_->m_callbacks, (!(((Impl const* C4_RESTRICT)this)->is_seed()))); + _RYML_CB_ASSERT(tree_->m_callbacks, (((Impl const* C4_RESTRICT)this)->readable())); #define _C4_IF_MUTABLE(ty) typename std::enable_if::value, ty>::type public: @@ -177,24 +177,24 @@ struct RoNodeMethods template C4_ALWAYS_INLINE auto get() RYML_NOEXCEPT -> _C4_IF_MUTABLE(NodeData*) { return ((Impl const*)this)->readable() ? tree__->get(id__) : nullptr; } - C4_ALWAYS_INLINE NodeType type() const RYML_NOEXCEPT { _C4RV(); return tree_->type(id_); } - C4_ALWAYS_INLINE const char* type_str() const RYML_NOEXCEPT { _C4RV(); return tree_->type_str(id_); } + C4_ALWAYS_INLINE NodeType type() const RYML_NOEXCEPT { _C4RR(); return tree_->type(id_); } + C4_ALWAYS_INLINE const char* type_str() const RYML_NOEXCEPT { _C4RR(); return tree_->type_str(id_); } - C4_ALWAYS_INLINE csubstr key() const RYML_NOEXCEPT { _C4RV(); return tree_->key(id_); } - C4_ALWAYS_INLINE csubstr key_tag() const RYML_NOEXCEPT { _C4RV(); return tree_->key_tag(id_); } - C4_ALWAYS_INLINE csubstr key_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->key_ref(id_); } - C4_ALWAYS_INLINE csubstr key_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->key_anchor(id_); } + C4_ALWAYS_INLINE csubstr key() const RYML_NOEXCEPT { _C4RR(); return tree_->key(id_); } + C4_ALWAYS_INLINE csubstr key_tag() const RYML_NOEXCEPT { _C4RR(); return tree_->key_tag(id_); } + C4_ALWAYS_INLINE csubstr key_ref() const RYML_NOEXCEPT { _C4RR(); return tree_->key_ref(id_); } + C4_ALWAYS_INLINE csubstr key_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->key_anchor(id_); } - C4_ALWAYS_INLINE csubstr val() const RYML_NOEXCEPT { _C4RV(); return tree_->val(id_); } - C4_ALWAYS_INLINE csubstr val_tag() const RYML_NOEXCEPT { _C4RV(); return tree_->val_tag(id_); } - C4_ALWAYS_INLINE csubstr val_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->val_ref(id_); } - C4_ALWAYS_INLINE csubstr val_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->val_anchor(id_); } + C4_ALWAYS_INLINE csubstr val() const RYML_NOEXCEPT { _C4RR(); return tree_->val(id_); } + C4_ALWAYS_INLINE csubstr val_tag() const RYML_NOEXCEPT { _C4RR(); return tree_->val_tag(id_); } + C4_ALWAYS_INLINE csubstr val_ref() const RYML_NOEXCEPT { _C4RR(); return tree_->val_ref(id_); } + C4_ALWAYS_INLINE csubstr val_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->val_anchor(id_); } - C4_ALWAYS_INLINE NodeScalar const& keysc() const RYML_NOEXCEPT { _C4RV(); return tree_->keysc(id_); } - C4_ALWAYS_INLINE NodeScalar const& valsc() const RYML_NOEXCEPT { _C4RV(); return tree_->valsc(id_); } + C4_ALWAYS_INLINE NodeScalar const& keysc() const RYML_NOEXCEPT { _C4RR(); return tree_->keysc(id_); } + C4_ALWAYS_INLINE NodeScalar const& valsc() const RYML_NOEXCEPT { _C4RR(); return tree_->valsc(id_); } - C4_ALWAYS_INLINE bool key_is_null() const RYML_NOEXCEPT { _C4RV(); return tree_->key_is_null(id_); } - C4_ALWAYS_INLINE bool val_is_null() const RYML_NOEXCEPT { _C4RV(); return tree_->val_is_null(id_); } + C4_ALWAYS_INLINE bool key_is_null() const RYML_NOEXCEPT { _C4RR(); return tree_->key_is_null(id_); } + C4_ALWAYS_INLINE bool val_is_null() const RYML_NOEXCEPT { _C4RR(); return tree_->val_is_null(id_); } /** @} */ @@ -203,33 +203,33 @@ struct RoNodeMethods /** @name node property predicates */ /** @{ */ - C4_ALWAYS_INLINE bool empty() const RYML_NOEXCEPT { _C4RV(); return tree_->empty(id_); } - C4_ALWAYS_INLINE bool is_stream() const RYML_NOEXCEPT { _C4RV(); return tree_->is_stream(id_); } - C4_ALWAYS_INLINE bool is_doc() const RYML_NOEXCEPT { _C4RV(); return tree_->is_doc(id_); } - C4_ALWAYS_INLINE bool is_container() const RYML_NOEXCEPT { _C4RV(); return tree_->is_container(id_); } - C4_ALWAYS_INLINE bool is_map() const RYML_NOEXCEPT { _C4RV(); return tree_->is_map(id_); } - C4_ALWAYS_INLINE bool is_seq() const RYML_NOEXCEPT { _C4RV(); return tree_->is_seq(id_); } - C4_ALWAYS_INLINE bool has_val() const RYML_NOEXCEPT { _C4RV(); return tree_->has_val(id_); } - C4_ALWAYS_INLINE bool has_key() const RYML_NOEXCEPT { _C4RV(); return tree_->has_key(id_); } - C4_ALWAYS_INLINE bool is_val() const RYML_NOEXCEPT { _C4RV(); return tree_->is_val(id_); } - C4_ALWAYS_INLINE bool is_keyval() const RYML_NOEXCEPT { _C4RV(); return tree_->is_keyval(id_); } - C4_ALWAYS_INLINE bool has_key_tag() const RYML_NOEXCEPT { _C4RV(); return tree_->has_key_tag(id_); } - C4_ALWAYS_INLINE bool has_val_tag() const RYML_NOEXCEPT { _C4RV(); return tree_->has_val_tag(id_); } - C4_ALWAYS_INLINE bool has_key_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->has_key_anchor(id_); } - C4_ALWAYS_INLINE bool is_key_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->is_key_anchor(id_); } - C4_ALWAYS_INLINE bool has_val_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->has_val_anchor(id_); } - C4_ALWAYS_INLINE bool is_val_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->is_val_anchor(id_); } - C4_ALWAYS_INLINE bool has_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->has_anchor(id_); } - C4_ALWAYS_INLINE bool is_anchor() const RYML_NOEXCEPT { _C4RV(); return tree_->is_anchor(id_); } - C4_ALWAYS_INLINE bool is_key_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->is_key_ref(id_); } - C4_ALWAYS_INLINE bool is_val_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->is_val_ref(id_); } - C4_ALWAYS_INLINE bool is_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->is_ref(id_); } - C4_ALWAYS_INLINE bool is_anchor_or_ref() const RYML_NOEXCEPT { _C4RV(); return tree_->is_anchor_or_ref(id_); } - C4_ALWAYS_INLINE bool is_key_quoted() const RYML_NOEXCEPT { _C4RV(); return tree_->is_key_quoted(id_); } - C4_ALWAYS_INLINE bool is_val_quoted() const RYML_NOEXCEPT { _C4RV(); return tree_->is_val_quoted(id_); } - C4_ALWAYS_INLINE bool is_quoted() const RYML_NOEXCEPT { _C4RV(); return tree_->is_quoted(id_); } - C4_ALWAYS_INLINE bool parent_is_seq() const RYML_NOEXCEPT { _C4RV(); return tree_->parent_is_seq(id_); } - C4_ALWAYS_INLINE bool parent_is_map() const RYML_NOEXCEPT { _C4RV(); return tree_->parent_is_map(id_); } + C4_ALWAYS_INLINE bool empty() const RYML_NOEXCEPT { _C4RR(); return tree_->empty(id_); } + C4_ALWAYS_INLINE bool is_stream() const RYML_NOEXCEPT { _C4RR(); return tree_->is_stream(id_); } + C4_ALWAYS_INLINE bool is_doc() const RYML_NOEXCEPT { _C4RR(); return tree_->is_doc(id_); } + C4_ALWAYS_INLINE bool is_container() const RYML_NOEXCEPT { _C4RR(); return tree_->is_container(id_); } + C4_ALWAYS_INLINE bool is_map() const RYML_NOEXCEPT { _C4RR(); return tree_->is_map(id_); } + C4_ALWAYS_INLINE bool is_seq() const RYML_NOEXCEPT { _C4RR(); return tree_->is_seq(id_); } + C4_ALWAYS_INLINE bool has_val() const RYML_NOEXCEPT { _C4RR(); return tree_->has_val(id_); } + C4_ALWAYS_INLINE bool has_key() const RYML_NOEXCEPT { _C4RR(); return tree_->has_key(id_); } + C4_ALWAYS_INLINE bool is_val() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val(id_); } + C4_ALWAYS_INLINE bool is_keyval() const RYML_NOEXCEPT { _C4RR(); return tree_->is_keyval(id_); } + C4_ALWAYS_INLINE bool has_key_tag() const RYML_NOEXCEPT { _C4RR(); return tree_->has_key_tag(id_); } + C4_ALWAYS_INLINE bool has_val_tag() const RYML_NOEXCEPT { _C4RR(); return tree_->has_val_tag(id_); } + C4_ALWAYS_INLINE bool has_key_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->has_key_anchor(id_); } + C4_ALWAYS_INLINE bool is_key_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_anchor(id_); } + C4_ALWAYS_INLINE bool has_val_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->has_val_anchor(id_); } + C4_ALWAYS_INLINE bool is_val_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_anchor(id_); } + C4_ALWAYS_INLINE bool has_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->has_anchor(id_); } + C4_ALWAYS_INLINE bool is_anchor() const RYML_NOEXCEPT { _C4RR(); return tree_->is_anchor(id_); } + C4_ALWAYS_INLINE bool is_key_ref() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_ref(id_); } + C4_ALWAYS_INLINE bool is_val_ref() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_ref(id_); } + C4_ALWAYS_INLINE bool is_ref() const RYML_NOEXCEPT { _C4RR(); return tree_->is_ref(id_); } + C4_ALWAYS_INLINE bool is_anchor_or_ref() const RYML_NOEXCEPT { _C4RR(); return tree_->is_anchor_or_ref(id_); } + C4_ALWAYS_INLINE bool is_key_quoted() const RYML_NOEXCEPT { _C4RR(); return tree_->is_key_quoted(id_); } + C4_ALWAYS_INLINE bool is_val_quoted() const RYML_NOEXCEPT { _C4RR(); return tree_->is_val_quoted(id_); } + C4_ALWAYS_INLINE bool is_quoted() const RYML_NOEXCEPT { _C4RR(); return tree_->is_quoted(id_); } + C4_ALWAYS_INLINE bool parent_is_seq() const RYML_NOEXCEPT { _C4RR(); return tree_->parent_is_seq(id_); } + C4_ALWAYS_INLINE bool parent_is_map() const RYML_NOEXCEPT { _C4RR(); return tree_->parent_is_map(id_); } /** @} */ @@ -238,22 +238,22 @@ struct RoNodeMethods /** @name hierarchy predicates */ /** @{ */ - C4_ALWAYS_INLINE bool is_root() const RYML_NOEXCEPT { _C4RV(); return tree_->is_root(id_); } - C4_ALWAYS_INLINE bool has_parent() const RYML_NOEXCEPT { _C4RV(); return tree_->has_parent(id_); } + C4_ALWAYS_INLINE bool is_root() const RYML_NOEXCEPT { _C4RR(); return tree_->is_root(id_); } + C4_ALWAYS_INLINE bool has_parent() const RYML_NOEXCEPT { _C4RR(); return tree_->has_parent(id_); } - C4_ALWAYS_INLINE bool has_child(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); return n.readable() ? tree_->has_child(id_, n.m_id) : false; } - C4_ALWAYS_INLINE bool has_child(size_t node) const RYML_NOEXCEPT { _C4RV(); return tree_->has_child(id_, node); } - C4_ALWAYS_INLINE bool has_child(csubstr name) const RYML_NOEXCEPT { _C4RV(); return tree_->has_child(id_, name); } - C4_ALWAYS_INLINE bool has_children() const RYML_NOEXCEPT { _C4RV(); return tree_->has_children(id_); } + C4_ALWAYS_INLINE bool has_child(ConstImpl const& n) const RYML_NOEXCEPT { _C4RR(); return n.readable() ? tree_->has_child(id_, n.m_id) : false; } + C4_ALWAYS_INLINE bool has_child(size_t node) const RYML_NOEXCEPT { _C4RR(); return tree_->has_child(id_, node); } + C4_ALWAYS_INLINE bool has_child(csubstr name) const RYML_NOEXCEPT { _C4RR(); return tree_->has_child(id_, name); } + C4_ALWAYS_INLINE bool has_children() const RYML_NOEXCEPT { _C4RR(); return tree_->has_children(id_); } - C4_ALWAYS_INLINE bool has_sibling(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); return n.readable() ? tree_->has_sibling(id_, n.m_id) : false; } - C4_ALWAYS_INLINE bool has_sibling(size_t node) const RYML_NOEXCEPT { _C4RV(); return tree_->has_sibling(id_, node); } - C4_ALWAYS_INLINE bool has_sibling(csubstr name) const RYML_NOEXCEPT { _C4RV(); return tree_->has_sibling(id_, name); } + C4_ALWAYS_INLINE bool has_sibling(ConstImpl const& n) const RYML_NOEXCEPT { _C4RR(); return n.readable() ? tree_->has_sibling(id_, n.m_id) : false; } + C4_ALWAYS_INLINE bool has_sibling(size_t node) const RYML_NOEXCEPT { _C4RR(); return tree_->has_sibling(id_, node); } + C4_ALWAYS_INLINE bool has_sibling(csubstr name) const RYML_NOEXCEPT { _C4RR(); return tree_->has_sibling(id_, name); } /** does not count with this */ - C4_ALWAYS_INLINE bool has_other_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->has_other_siblings(id_); } + C4_ALWAYS_INLINE bool has_other_siblings() const RYML_NOEXCEPT { _C4RR(); return tree_->has_other_siblings(id_); } /** counts with this */ - RYML_DEPRECATED("use has_other_siblings()") bool has_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->has_siblings(id_); } + RYML_DEPRECATED("use has_other_siblings()") bool has_siblings() const RYML_NOEXCEPT { _C4RR(); return tree_->has_siblings(id_); } /** @} */ @@ -269,59 +269,59 @@ struct RoNodeMethods C4_ALWAYS_INLINE ConstImpl doc(size_t num) const RYML_NOEXCEPT { RYML_ASSERT(tree_); return {tree_, tree_->doc(num)}; } template - C4_ALWAYS_INLINE auto parent() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->parent(id__)}; } - C4_ALWAYS_INLINE ConstImpl parent() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->parent(id_)}; } + C4_ALWAYS_INLINE auto parent() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->parent(id__)}; } + C4_ALWAYS_INLINE ConstImpl parent() const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->parent(id_)}; } template - C4_ALWAYS_INLINE auto first_child() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->first_child(id__)}; } - C4_ALWAYS_INLINE ConstImpl first_child() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->first_child(id_)}; } + C4_ALWAYS_INLINE auto first_child() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->first_child(id__)}; } + C4_ALWAYS_INLINE ConstImpl first_child() const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->first_child(id_)}; } template - C4_ALWAYS_INLINE auto last_child() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->last_child(id__)}; } - C4_ALWAYS_INLINE ConstImpl last_child () const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->last_child (id_)}; } + C4_ALWAYS_INLINE auto last_child() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->last_child(id__)}; } + C4_ALWAYS_INLINE ConstImpl last_child () const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->last_child (id_)}; } template - C4_ALWAYS_INLINE auto child(size_t pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->child(id__, pos)}; } - C4_ALWAYS_INLINE ConstImpl child(size_t pos) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->child(id_, pos)}; } + C4_ALWAYS_INLINE auto child(size_t pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->child(id__, pos)}; } + C4_ALWAYS_INLINE ConstImpl child(size_t pos) const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->child(id_, pos)}; } template - C4_ALWAYS_INLINE auto find_child(csubstr name) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->find_child(id__, name)}; } - C4_ALWAYS_INLINE ConstImpl find_child(csubstr name) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->find_child(id_, name)}; } + C4_ALWAYS_INLINE auto find_child(csubstr name) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->find_child(id__, name)}; } + C4_ALWAYS_INLINE ConstImpl find_child(csubstr name) const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->find_child(id_, name)}; } template - C4_ALWAYS_INLINE auto prev_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->prev_sibling(id__)}; } - C4_ALWAYS_INLINE ConstImpl prev_sibling() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->prev_sibling(id_)}; } + C4_ALWAYS_INLINE auto prev_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->prev_sibling(id__)}; } + C4_ALWAYS_INLINE ConstImpl prev_sibling() const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->prev_sibling(id_)}; } template - C4_ALWAYS_INLINE auto next_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->next_sibling(id__)}; } - C4_ALWAYS_INLINE ConstImpl next_sibling() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->next_sibling(id_)}; } + C4_ALWAYS_INLINE auto next_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->next_sibling(id__)}; } + C4_ALWAYS_INLINE ConstImpl next_sibling() const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->next_sibling(id_)}; } template - C4_ALWAYS_INLINE auto first_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->first_sibling(id__)}; } - C4_ALWAYS_INLINE ConstImpl first_sibling() const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->first_sibling(id_)}; } + C4_ALWAYS_INLINE auto first_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->first_sibling(id__)}; } + C4_ALWAYS_INLINE ConstImpl first_sibling() const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->first_sibling(id_)}; } template - C4_ALWAYS_INLINE auto last_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->last_sibling(id__)}; } - C4_ALWAYS_INLINE ConstImpl last_sibling () const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->last_sibling(id_)}; } + C4_ALWAYS_INLINE auto last_sibling() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->last_sibling(id__)}; } + C4_ALWAYS_INLINE ConstImpl last_sibling () const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->last_sibling(id_)}; } template - C4_ALWAYS_INLINE auto sibling(size_t pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->sibling(id__, pos)}; } - C4_ALWAYS_INLINE ConstImpl sibling(size_t pos) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->sibling(id_, pos)}; } + C4_ALWAYS_INLINE auto sibling(size_t pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->sibling(id__, pos)}; } + C4_ALWAYS_INLINE ConstImpl sibling(size_t pos) const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->sibling(id_, pos)}; } template - C4_ALWAYS_INLINE auto find_sibling(csubstr name) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RV(); return {tree__, tree__->find_sibling(id__, name)}; } - C4_ALWAYS_INLINE ConstImpl find_sibling(csubstr name) const RYML_NOEXCEPT { _C4RV(); return {tree_, tree_->find_sibling(id_, name)}; } + C4_ALWAYS_INLINE auto find_sibling(csubstr name) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->find_sibling(id__, name)}; } + C4_ALWAYS_INLINE ConstImpl find_sibling(csubstr name) const RYML_NOEXCEPT { _C4RR(); return {tree_, tree_->find_sibling(id_, name)}; } /** O(#num_children) */ - C4_ALWAYS_INLINE size_t child_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); _RYML_CB_ASSERT(tree_->m_callbacks, n.readable()); return tree_->child_pos(id_, n.m_id); } + C4_ALWAYS_INLINE size_t child_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RR(); _RYML_CB_ASSERT(tree_->m_callbacks, n.readable()); return tree_->child_pos(id_, n.m_id); } /** O(#num_children) */ - C4_ALWAYS_INLINE size_t num_children() const RYML_NOEXCEPT { _C4RV(); return tree_->num_children(id_); } + C4_ALWAYS_INLINE size_t num_children() const RYML_NOEXCEPT { _C4RR(); return tree_->num_children(id_); } /** O(#num_siblings) */ - C4_ALWAYS_INLINE size_t num_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->num_siblings(id_); } - C4_ALWAYS_INLINE size_t num_other_siblings() const RYML_NOEXCEPT { _C4RV(); return tree_->num_other_siblings(id_); } - C4_ALWAYS_INLINE size_t sibling_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RV(); _RYML_CB_ASSERT(tree_->callbacks(), n.readable()); return tree_->child_pos(tree_->parent(id_), n.m_id); } + C4_ALWAYS_INLINE size_t num_siblings() const RYML_NOEXCEPT { _C4RR(); return tree_->num_siblings(id_); } + C4_ALWAYS_INLINE size_t num_other_siblings() const RYML_NOEXCEPT { _C4RR(); return tree_->num_other_siblings(id_); } + C4_ALWAYS_INLINE size_t sibling_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RR(); _RYML_CB_ASSERT(tree_->callbacks(), n.readable()); return tree_->child_pos(tree_->parent(id_), n.m_id); } /** @} */ @@ -352,7 +352,7 @@ struct RoNodeMethods template C4_ALWAYS_INLINE auto operator[] (csubstr key) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { - _C4RV(); + _C4RR(); size_t ch = tree__->find_child(id__, key); return ch != NONE ? Impl(tree__, ch) : Impl(tree__, id__, key); } @@ -378,7 +378,7 @@ struct RoNodeMethods template C4_ALWAYS_INLINE auto operator[] (size_t pos) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { - _C4RV(); + _C4RR(); size_t ch = tree__->child(id__, pos); return ch != NONE ? Impl(tree__, ch) : Impl(tree__, id__, pos); } @@ -394,7 +394,7 @@ struct RoNodeMethods * @see https://github.com/biojppm/rapidyaml/issues/389 */ C4_ALWAYS_INLINE ConstImpl operator[] (csubstr key) const RYML_NOEXCEPT { - _C4RV(); + _C4RR(); size_t ch = tree_->find_child(id_, key); _RYML_CB_ASSERT(tree_->m_callbacks, ch != NONE); return {tree_, ch}; @@ -411,7 +411,7 @@ struct RoNodeMethods * @see https://github.com/biojppm/rapidyaml/issues/389 */ C4_ALWAYS_INLINE ConstImpl operator[] (size_t pos) const RYML_NOEXCEPT { - _C4RV(); + _C4RR(); size_t ch = tree_->child(id_, pos); _RYML_CB_ASSERT(tree_->m_callbacks, ch != NONE); return {tree_, ch}; @@ -557,7 +557,7 @@ struct RoNodeMethods template ConstImpl const& operator>> (T &v) const { - _C4RV(); + _C4RR(); if( ! read((ConstImpl const&)*this, &v)) _RYML_CB_ERR(tree_->m_callbacks, "could not deserialize value"); return *((ConstImpl const*)this); @@ -567,7 +567,7 @@ struct RoNodeMethods template ConstImpl const& operator>> (Key v) const { - _C4RV(); + _C4RR(); if( ! from_chars(key(), &v.k)) _RYML_CB_ERR(tree_->m_callbacks, "could not deserialize key"); return *((ConstImpl const*)this); @@ -592,7 +592,7 @@ struct RoNodeMethods * @return the size of base64-decoded blob */ size_t deserialize_key(fmt::base64_wrapper v) const { - _C4RV(); + _C4RR(); return from_chars(key(), &v); } /** decode the base64-encoded key and assign the @@ -600,16 +600,16 @@ struct RoNodeMethods * @return the size of base64-decoded blob */ size_t deserialize_val(fmt::base64_wrapper v) const { - _C4RV(); + _C4RR(); return from_chars(val(), &v); }; template bool get_if(csubstr name, T *var) const { - _C4RV(); + _C4RR(); ConstImpl ch = find_child(name); - if(!ch.valid()) + if(!ch.readable()) return false; ch >> *var; return true; @@ -618,9 +618,9 @@ struct RoNodeMethods template bool get_if(csubstr name, T *var, T const& fallback) const { - _C4RV(); + _C4RR(); ConstImpl ch = find_child(name); - if(ch.valid()) + if(ch.readable()) { ch >> *var; return true; @@ -655,28 +655,28 @@ struct RoNodeMethods using const_children_view = detail::children_view_; template - C4_ALWAYS_INLINE auto begin() RYML_NOEXCEPT -> _C4_IF_MUTABLE(iterator) { _C4RV(); return iterator(tree__, tree__->first_child(id__)); } - C4_ALWAYS_INLINE const_iterator begin() const RYML_NOEXCEPT { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } - C4_ALWAYS_INLINE const_iterator cbegin() const RYML_NOEXCEPT { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } + C4_ALWAYS_INLINE auto begin() RYML_NOEXCEPT -> _C4_IF_MUTABLE(iterator) { _C4RR(); return iterator(tree__, tree__->first_child(id__)); } + C4_ALWAYS_INLINE const_iterator begin() const RYML_NOEXCEPT { _C4RR(); return const_iterator(tree_, tree_->first_child(id_)); } + C4_ALWAYS_INLINE const_iterator cbegin() const RYML_NOEXCEPT { _C4RR(); return const_iterator(tree_, tree_->first_child(id_)); } template - C4_ALWAYS_INLINE auto end() RYML_NOEXCEPT -> _C4_IF_MUTABLE(iterator) { _C4RV(); return iterator(tree__, NONE); } - C4_ALWAYS_INLINE const_iterator end() const RYML_NOEXCEPT { _C4RV(); return const_iterator(tree_, NONE); } - C4_ALWAYS_INLINE const_iterator cend() const RYML_NOEXCEPT { _C4RV(); return const_iterator(tree_, tree_->first_child(id_)); } + C4_ALWAYS_INLINE auto end() RYML_NOEXCEPT -> _C4_IF_MUTABLE(iterator) { _C4RR(); return iterator(tree__, NONE); } + C4_ALWAYS_INLINE const_iterator end() const RYML_NOEXCEPT { _C4RR(); return const_iterator(tree_, NONE); } + C4_ALWAYS_INLINE const_iterator cend() const RYML_NOEXCEPT { _C4RR(); return const_iterator(tree_, tree_->first_child(id_)); } /** get an iterable view over children */ template - C4_ALWAYS_INLINE auto children() RYML_NOEXCEPT -> _C4_IF_MUTABLE(children_view) { _C4RV(); return children_view(begin(), end()); } + C4_ALWAYS_INLINE auto children() RYML_NOEXCEPT -> _C4_IF_MUTABLE(children_view) { _C4RR(); return children_view(begin(), end()); } /** get an iterable view over children */ - C4_ALWAYS_INLINE const_children_view children() const RYML_NOEXCEPT { _C4RV(); return const_children_view(begin(), end()); } + C4_ALWAYS_INLINE const_children_view children() const RYML_NOEXCEPT { _C4RR(); return const_children_view(begin(), end()); } /** get an iterable view over children */ - C4_ALWAYS_INLINE const_children_view cchildren() const RYML_NOEXCEPT { _C4RV(); return const_children_view(begin(), end()); } + C4_ALWAYS_INLINE const_children_view cchildren() const RYML_NOEXCEPT { _C4RR(); return const_children_view(begin(), end()); } /** get an iterable view over all siblings (including the calling node) */ template C4_ALWAYS_INLINE auto siblings() RYML_NOEXCEPT -> _C4_IF_MUTABLE(children_view) { - _C4RV(); + _C4RR(); NodeData const *nd = tree__->get(id__); return (nd->m_parent != NONE) ? // does it have a parent? children_view(iterator(tree__, tree_->get(nd->m_parent)->m_first_child), iterator(tree__, NONE)) @@ -686,7 +686,7 @@ struct RoNodeMethods /** get an iterable view over all siblings (including the calling node) */ C4_ALWAYS_INLINE const_children_view siblings() const RYML_NOEXCEPT { - _C4RV(); + _C4RR(); NodeData const *nd = tree_->get(id_); return (nd->m_parent != NONE) ? // does it have a parent? const_children_view(const_iterator(tree_, tree_->get(nd->m_parent)->m_first_child), const_iterator(tree_, NONE)) @@ -700,7 +700,7 @@ struct RoNodeMethods template bool visit(Visitor fn, size_t indentation_level=0, bool skip_root=true) const RYML_NOEXCEPT { - _C4RV(); + _C4RR(); return detail::_visit(*(ConstImpl const*)this, fn, indentation_level, skip_root); } /** visit every child node calling fn(node) */ @@ -708,7 +708,7 @@ struct RoNodeMethods auto visit(Visitor fn, size_t indentation_level=0, bool skip_root=true) RYML_NOEXCEPT -> _C4_IF_MUTABLE(bool) { - _C4RV(); + _C4RR(); return detail::_visit(*(Impl*)this, fn, indentation_level, skip_root); } @@ -716,7 +716,7 @@ struct RoNodeMethods template bool visit_stacked(Visitor fn, size_t indentation_level=0, bool skip_root=true) const RYML_NOEXCEPT { - _C4RV(); + _C4RR(); return detail::_visit_stacked(*(ConstImpl const*)this, fn, indentation_level, skip_root); } /** visit every child node calling fn(node, level) */ @@ -724,7 +724,7 @@ struct RoNodeMethods auto visit_stacked(Visitor fn, size_t indentation_level=0, bool skip_root=true) RYML_NOEXCEPT -> _C4_IF_MUTABLE(bool) { - _C4RV(); + _C4RR(); return detail::_visit_stacked(*(Impl*)this, fn, indentation_level, skip_root); } @@ -737,7 +737,7 @@ struct RoNodeMethods #endif #undef _C4_IF_MUTABLE - #undef _C4RV + #undef _C4RR #undef tree_ #undef tree__ #undef id_ @@ -808,16 +808,20 @@ class RYML_EXPORT ConstNodeRef : public detail::RoNodeMethodsoperator== (that); } - C4_ALWAYS_INLINE C4_PURE bool operator== (std::nullptr_t) const noexcept { return m_tree == nullptr || m_id == NONE; } - C4_ALWAYS_INLINE C4_PURE bool operator!= (std::nullptr_t) const noexcept { return ! this->operator== (nullptr); } + RYML_DEPRECATED("use invalid()") bool operator== (std::nullptr_t) const noexcept { return m_tree == nullptr || m_id == NONE; } + RYML_DEPRECATED("use !invalid()") bool operator!= (std::nullptr_t) const noexcept { return !(m_tree == nullptr || m_id == NONE); } - C4_ALWAYS_INLINE bool operator== (csubstr val) const RYML_NOEXCEPT { RYML_ASSERT(m_tree); _RYML_CB_ASSERT(m_tree->m_callbacks, m_id != NONE); return m_tree->val(m_id) == val; } - C4_ALWAYS_INLINE bool operator!= (csubstr val) const RYML_NOEXCEPT { RYML_ASSERT(m_tree); _RYML_CB_ASSERT(m_tree->m_callbacks, m_id != NONE); return m_tree->val(m_id) != val; } + RYML_DEPRECATED("use (this->val() == s)") bool operator== (csubstr s) const RYML_NOEXCEPT { RYML_ASSERT(m_tree); _RYML_CB_ASSERT(m_tree->m_callbacks, m_id != NONE); return m_tree->val(m_id) == s; } + RYML_DEPRECATED("use (this->val() != s)") bool operator!= (csubstr s) const RYML_NOEXCEPT { RYML_ASSERT(m_tree); _RYML_CB_ASSERT(m_tree->m_callbacks, m_id != NONE); return m_tree->val(m_id) != s; } /** @} */ @@ -863,10 +867,10 @@ class RYML_EXPORT ConstNodeRef : public detail::RoNodeMethods /** @name state_queries * @{ */ - /** true if the object is pointing at a tree and id. @see the doc for the NodeRef */ - inline bool valid() const { return m_tree != nullptr && m_id != NONE; } - /** true if the object is valid() and in seed state. @see the doc for the NodeRef */ - inline bool is_seed() const { return valid() && (m_seed.str != nullptr || m_seed.len != NONE); } - /** true if the object is valid() and NOT in seed state. @see the doc for the NodeRef */ - inline bool readable() const { return valid() && !is_seed(); } + /** true if the object is not referring to any existing or seed node @see the doc for the NodeRef */ + inline bool invalid() const { return m_tree == nullptr || m_id == NONE; } + /** true if the object is not invalid and in seed state. @see the doc for the NodeRef */ + inline bool is_seed() const { return (m_tree != NULL && m_id != NONE) && (m_seed.str != nullptr || m_seed.len != (size_t)NONE); } + /** true if the object is not invalid and not in seed state. @see the doc for the NodeRef */ + inline bool readable() const { return (m_tree != NULL && m_id != NONE) && (m_seed.str == nullptr && m_seed.len == (size_t)NONE); } + + RYML_DEPRECATED("use one of readable(), is_seed() or !invalid()") inline bool valid() const { return m_tree != nullptr && m_id != NONE; } inline void _clear_seed() { /*do the following manually or an assert is triggered: */ m_seed.str = nullptr; m_seed.len = NONE; } @@ -975,15 +981,18 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods /** @name comparisons */ /** @{ */ - inline bool operator== (NodeRef const& that) const + bool operator== (NodeRef const& that) const { if(m_tree == that.m_tree && m_id == that.m_id) { - if(is_seed() == that.is_seed()) + bool seed = is_seed(); + if(seed == that.is_seed()) { - if(is_seed()) + if(seed) { - return (m_seed.len == that.m_seed.len) && (m_seed.str == that.m_seed.str || m_seed == that.m_seed); + return (m_seed.len == that.m_seed.len) + && (m_seed.str == that.m_seed.str + || m_seed == that.m_seed); // do strcmp only in the last resort } return true; } @@ -995,11 +1004,11 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods inline bool operator== (ConstNodeRef const& that) const { return m_tree == that.m_tree && m_id == that.m_id && !is_seed(); } inline bool operator!= (ConstNodeRef const& that) const { return ! this->operator==(that); } - inline bool operator== (std::nullptr_t) const { return m_tree == nullptr || m_id == NONE || is_seed(); } - inline bool operator!= (std::nullptr_t) const { return ! this->operator==(nullptr); } + RYML_DEPRECATED("use !readable()") bool operator== (std::nullptr_t) const { return m_tree == nullptr || m_id == NONE || is_seed(); } + RYML_DEPRECATED("use readable()") bool operator!= (std::nullptr_t) const { return !(m_tree == nullptr || m_id == NONE || is_seed()); } - inline bool operator== (csubstr val) const { _C4RV(); _RYML_CB_ASSERT(m_tree->m_callbacks, has_val()); return m_tree->val(m_id) == val; } - inline bool operator!= (csubstr val) const { _C4RV(); _RYML_CB_ASSERT(m_tree->m_callbacks, has_val()); return m_tree->val(m_id) != val; } + RYML_DEPRECATED("use (this->val() == s)") bool operator== (csubstr s) const { _C4RV(); _RYML_CB_ASSERT(m_tree->m_callbacks, has_val()); return m_tree->val(m_id) == s; } + RYML_DEPRECATED("use (this->val() != s)") bool operator!= (csubstr s) const { _C4RV(); _RYML_CB_ASSERT(m_tree->m_callbacks, has_val()); return m_tree->val(m_id) != s; } /** @} */ @@ -1229,7 +1238,7 @@ class RYML_EXPORT NodeRef : public detail::RoNodeMethods } else { - _RYML_CB_ASSERT(m_tree->m_callbacks, valid()); + _RYML_CB_ASSERT(m_tree->m_callbacks, readable()); } } diff --git a/src/c4/yml/parse.cpp b/src/c4/yml/parse.cpp index 5e52a9697..0007e4d50 100644 --- a/src/c4/yml/parse.cpp +++ b/src/c4/yml/parse.cpp @@ -5528,7 +5528,7 @@ csubstr Parser::location_contents(Location const& loc) const Location Parser::location(ConstNodeRef node) const { - _RYML_CB_ASSERT(m_stack.m_callbacks, node.valid()); + _RYML_CB_ASSERT(m_stack.m_callbacks, node.readable()); return location(*node.tree(), node.id()); } diff --git a/test/test_case.cpp b/test/test_case.cpp index bdde31463..d317a51b0 100644 --- a/test/test_case.cpp +++ b/test/test_case.cpp @@ -362,8 +362,8 @@ void CaseNode::compare_child(yml::ConstNodeRef const& n, size_t pos) const if(type & MAP) { - auto actualch = n.find_child(expectedch.key); - if(actualch != nullptr) + ConstNodeRef actualch = n.find_child(expectedch.key); + if(!actualch.invalid()) { // there may be duplicate keys. if(actualch.id() != n[pos].id()) @@ -475,7 +475,7 @@ void CaseNode::compare(yml::ConstNodeRef const& actual, bool ignore_quote) const compare_child(actual, ic++); } - if(actual.first_child() != nullptr) + if(!actual.first_child().invalid()) { ic = 0; for(auto const ch : actual.first_child().siblings()) @@ -522,7 +522,7 @@ void print_path(ConstNodeRef const& n) size_t len = 0; char buf[1024]; ConstNodeRef p = n; - while(p != nullptr) + while(p.readable()) { if(p.has_key()) { @@ -539,7 +539,7 @@ void print_path(ConstNodeRef const& n) C4_ASSERT(len < sizeof(buf)); size_t pos = len; p = n; - while(p.valid() && p != nullptr) + while(p.readable()) { if(p.has_key()) { @@ -683,7 +683,7 @@ void test_invariants(ConstNodeRef const& n) EXPECT_TRUE(s.has_sibling(n)) _MORE_INFO; EXPECT_EQ(s.parent().get(), n.parent().get()) _MORE_INFO; } - if(n.parent() != nullptr) + if(n.parent().readable()) { EXPECT_EQ(n.parent().num_children() > 1, n.has_other_siblings()) _MORE_INFO; EXPECT_TRUE(n.parent().has_child(n)) _MORE_INFO; @@ -755,7 +755,7 @@ size_t test_tree_invariants(ConstNodeRef const& n) if(n.get()->m_prev_sibling == NONE) { - if(parent != nullptr) + if(parent.readable()) { EXPECT_EQ(parent.first_child().get(), n.get()); EXPECT_EQ(parent.first_child().id(), n.id()); @@ -764,14 +764,14 @@ size_t test_tree_invariants(ConstNodeRef const& n) if(n.get()->m_next_sibling == NONE) { - if(parent != nullptr) + if(parent.readable()) { EXPECT_EQ(parent.last_child().get(), n.get()); EXPECT_EQ(parent.last_child().id(), n.id()); } } - if(parent == nullptr) + if(!parent.readable()) { EXPECT_TRUE(n.is_root()); EXPECT_EQ(n.prev_sibling().get(), nullptr); diff --git a/test/test_github_issues.cpp b/test/test_github_issues.cpp index 3c12307fc..df68fd578 100644 --- a/test/test_github_issues.cpp +++ b/test/test_github_issues.cpp @@ -93,10 +93,11 @@ TEST(github, 60) ASSERT_TRUE(root.is_map()); ASSERT_TRUE(root.has_child("traits")); auto rb = root["traits"]["roleBonuses"][0]; - ASSERT_TRUE(rb.valid()); + ASSERT_FALSE(rb.invalid()); + ASSERT_TRUE(rb.readable()); EXPECT_EQ(rb["bonus"].val(), "5"); auto txt = rb["bonusText"]; - ASSERT_TRUE(txt.valid()); + ASSERT_TRUE(txt.readable()); ASSERT_TRUE(txt.is_map()); EXPECT_TRUE(txt.has_child("de")); EXPECT_TRUE(txt.has_child("en")); diff --git a/test/test_group.hpp b/test/test_group.hpp index f661ec9b5..fe0b4550a 100644 --- a/test/test_group.hpp +++ b/test/test_group.hpp @@ -15,6 +15,9 @@ #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wunknown-pragmas" +# if __GNUC__ > 5 +# pragma GCC diagnostic ignored "-Wunused-const-variable" +# endif //# pragma GCC diagnostic ignored "-Wpragma-system-header-outside-header" #endif diff --git a/test/test_noderef.cpp b/test/test_noderef.cpp index 121ef2a87..017b0f518 100644 --- a/test/test_noderef.cpp +++ b/test/test_noderef.cpp @@ -196,27 +196,27 @@ TEST(NodeRef, valid_vs_seed_vs_readable) Tree tree = parse_in_arena("foo: bar"); NodeRef foo = tree["foo"]; ConstNodeRef const_foo = tree["foo"]; - EXPECT_TRUE(foo.valid()); + EXPECT_FALSE(foo.invalid()); EXPECT_FALSE(foo.is_seed()); EXPECT_TRUE(foo.readable()); - EXPECT_TRUE(const_foo.valid()); + EXPECT_FALSE(const_foo.invalid()); EXPECT_FALSE(const_foo.is_seed()); EXPECT_TRUE(const_foo.readable()); NodeRef none; - EXPECT_FALSE(none.valid()); + EXPECT_TRUE(none.invalid()); EXPECT_FALSE(none.is_seed()); EXPECT_FALSE(none.readable()); ConstNodeRef const_none; - EXPECT_FALSE(const_none.valid()); + EXPECT_TRUE(const_none.invalid()); EXPECT_FALSE(const_none.is_seed()); EXPECT_FALSE(const_none.readable()); none = tree["none"]; - EXPECT_TRUE(none.valid()); + EXPECT_FALSE(none.invalid()); EXPECT_TRUE(none.is_seed()); EXPECT_FALSE(none.readable()); ASSERT_FALSE(tree.rootref().has_child(none)); const_none = tree["none"]; - EXPECT_FALSE(const_none.valid()); + EXPECT_TRUE(const_none.invalid()); EXPECT_FALSE(const_none.is_seed()); EXPECT_TRUE(none.is_seed()); EXPECT_FALSE(none.readable()); @@ -339,10 +339,6 @@ void test_fail_read(Tree *tree, NodeT node) //_TEST_FAIL(const_node.visit([](const NodeT &n, size_t level){ (void)n; (void)level; return false; })); _TEST_SUCCEED(const_node == node); _TEST_SUCCEED(const_node != node); - _TEST_SUCCEED(const_node == nullptr); - _TEST_SUCCEED(const_node != nullptr); - _TEST_FAIL(const_node == "val"); - _TEST_FAIL(const_node != "val"); if(std::is_same::value) { ConstNodeRef other; @@ -375,7 +371,7 @@ TEST(NodeRef, cannot_read_from_invalid) NodeRef none; ASSERT_EQ(none.tree(), nullptr); ASSERT_EQ(none.id(), NONE); - EXPECT_FALSE(none.valid()); + EXPECT_TRUE(none.invalid()); EXPECT_FALSE(none.is_seed()); EXPECT_FALSE(none.readable()); test_fail_read(nullptr, none); @@ -396,7 +392,7 @@ TEST(ConstNodeRef, cannot_read_from_invalid) ConstNodeRef const_none; ASSERT_EQ(const_none.tree(), nullptr); ASSERT_EQ(const_none.id(), NONE); - EXPECT_FALSE(const_none.valid()); + EXPECT_TRUE(const_none.invalid()); EXPECT_FALSE(const_none.is_seed()); EXPECT_FALSE(const_none.readable()); test_fail_read(nullptr, const_none); @@ -422,7 +418,7 @@ TEST(NodeRef, cannot_read_from_seed) NodeRef none = tree["none"]; ASSERT_EQ(none.tree(), &tree); ASSERT_EQ(none.id(), 0); - EXPECT_TRUE(none.valid()); + EXPECT_FALSE(none.invalid()); EXPECT_TRUE(none.is_seed()); EXPECT_FALSE(none.readable()); test_fail_read(&tree, none); @@ -436,7 +432,7 @@ TEST(NodeRef, cannot_read_from_seed_subject) NodeRef none = tree["none"]; ASSERT_EQ(none.tree(), &tree); ASSERT_EQ(none.id(), 0); - EXPECT_TRUE(none.valid()); + EXPECT_FALSE(none.invalid()); EXPECT_TRUE(none.is_seed()); EXPECT_FALSE(none.readable()); test_fail_read(&tree, none); @@ -450,7 +446,7 @@ TEST(ConstNodeRef, cannot_read_from_seed_subject) ConstNodeRef const_none = tree["none"]; ASSERT_EQ(const_none.tree(), &tree); ASSERT_EQ(const_none.id(), NONE); - EXPECT_FALSE(const_none.valid()); + EXPECT_TRUE(const_none.invalid()); EXPECT_FALSE(const_none.is_seed()); EXPECT_FALSE(const_none.readable()); test_fail_read(&tree, const_none); @@ -732,7 +728,7 @@ TEST(NodeRef, remove_child__issue_356) SCOPED_TRACE(id); NodeRef formats = root["formats"]; std::cout << id << " id=" << formats.id() << "\n"; - EXPECT_TRUE(formats.valid()); + EXPECT_TRUE(formats.readable()); print_tree(tree); check_invariants(tree); EXPECT_EQ(emitrs_yaml(formats), expected); diff --git a/test/test_tree.cpp b/test/test_tree.cpp index 04fce801a..def97b834 100644 --- a/test/test_tree.cpp +++ b/test/test_tree.cpp @@ -967,28 +967,6 @@ TEST(Tree, operator_square_brackets_seq) EXPECT_EQ(cm[3].val(), "3"); EXPECT_EQ(cm[4].val(), "4"); // - EXPECT_TRUE(m[0] == "0"); - EXPECT_TRUE(m[1] == "1"); - EXPECT_TRUE(m[2] == "2"); - EXPECT_TRUE(m[3] == "3"); - EXPECT_TRUE(m[4] == "4"); - EXPECT_TRUE(cm[0] == "0"); - EXPECT_TRUE(cm[1] == "1"); - EXPECT_TRUE(cm[2] == "2"); - EXPECT_TRUE(cm[3] == "3"); - EXPECT_TRUE(cm[4] == "4"); - // - EXPECT_FALSE(m[0] != "0"); - EXPECT_FALSE(m[1] != "1"); - EXPECT_FALSE(m[2] != "2"); - EXPECT_FALSE(m[3] != "3"); - EXPECT_FALSE(m[4] != "4"); - EXPECT_FALSE(cm[0] != "0"); - EXPECT_FALSE(cm[1] != "1"); - EXPECT_FALSE(cm[2] != "2"); - EXPECT_FALSE(cm[3] != "3"); - EXPECT_FALSE(cm[4] != "4"); - // verify_assertion(t, [&](Tree const&){ return cm[m.capacity()]; }); verify_assertion(t, [&](Tree const&){ return cm[NONE]; }); verify_assertion(t, [&](Tree const&){ return cm[0][0]; }); @@ -1011,28 +989,6 @@ TEST(Tree, operator_square_brackets_map) EXPECT_EQ(cm["d"].val(), "3"); EXPECT_EQ(cm["e"].val(), "4"); // - EXPECT_TRUE(m["a"] == "0"); - EXPECT_TRUE(m["b"] == "1"); - EXPECT_TRUE(m["c"] == "2"); - EXPECT_TRUE(m["d"] == "3"); - EXPECT_TRUE(m["e"] == "4"); - EXPECT_TRUE(cm["a"] == "0"); - EXPECT_TRUE(cm["b"] == "1"); - EXPECT_TRUE(cm["c"] == "2"); - EXPECT_TRUE(cm["d"] == "3"); - EXPECT_TRUE(cm["e"] == "4"); - // - EXPECT_FALSE(m["a"] != "0"); - EXPECT_FALSE(m["b"] != "1"); - EXPECT_FALSE(m["c"] != "2"); - EXPECT_FALSE(m["d"] != "3"); - EXPECT_FALSE(m["e"] != "4"); - EXPECT_FALSE(cm["a"] != "0"); - EXPECT_FALSE(cm["b"] != "1"); - EXPECT_FALSE(cm["c"] != "2"); - EXPECT_FALSE(cm["d"] != "3"); - EXPECT_FALSE(cm["e"] != "4"); - // verify_assertion(t, [&](Tree const&){ return cm["f"]; }); verify_assertion(t, [&](Tree const&){ return cm["g"]["h"]; }); } @@ -1053,28 +1009,6 @@ TEST(Tree, noderef_at_seq) EXPECT_EQ(cm.at(3).val(), "3"); EXPECT_EQ(cm.at(4).val(), "4"); // - EXPECT_TRUE(m.at(0) == "0"); - EXPECT_TRUE(m.at(1) == "1"); - EXPECT_TRUE(m.at(2) == "2"); - EXPECT_TRUE(m.at(3) == "3"); - EXPECT_TRUE(m.at(4) == "4"); - EXPECT_TRUE(cm.at(0) == "0"); - EXPECT_TRUE(cm.at(1) == "1"); - EXPECT_TRUE(cm.at(2) == "2"); - EXPECT_TRUE(cm.at(3) == "3"); - EXPECT_TRUE(cm.at(4) == "4"); - // - EXPECT_FALSE(m.at(0) != "0"); - EXPECT_FALSE(m.at(1) != "1"); - EXPECT_FALSE(m.at(2) != "2"); - EXPECT_FALSE(m.at(3) != "3"); - EXPECT_FALSE(m.at(4) != "4"); - EXPECT_FALSE(cm.at(0) != "0"); - EXPECT_FALSE(cm.at(1) != "1"); - EXPECT_FALSE(cm.at(2) != "2"); - EXPECT_FALSE(cm.at(3) != "3"); - EXPECT_FALSE(cm.at(4) != "4"); - // EXPECT_EQ(cm.num_children(), 5); EXPECT_EQ(cm.num_children(), m.num_children()); // @@ -1125,28 +1059,6 @@ TEST(Tree, noderef_at_map) EXPECT_EQ(cm.at("d").val(), "3"); EXPECT_EQ(cm.at("e").val(), "4"); // - EXPECT_TRUE(m.at("a") == "0"); - EXPECT_TRUE(m.at("b") == "1"); - EXPECT_TRUE(m.at("c") == "2"); - EXPECT_TRUE(m.at("d") == "3"); - EXPECT_TRUE(m.at("e") == "4"); - EXPECT_TRUE(cm.at("a") == "0"); - EXPECT_TRUE(cm.at("b") == "1"); - EXPECT_TRUE(cm.at("c") == "2"); - EXPECT_TRUE(cm.at("d") == "3"); - EXPECT_TRUE(cm.at("e") == "4"); - // - EXPECT_FALSE(m.at("a") != "0"); - EXPECT_FALSE(m.at("b") != "1"); - EXPECT_FALSE(m.at("c") != "2"); - EXPECT_FALSE(m.at("d") != "3"); - EXPECT_FALSE(m.at("e") != "4"); - EXPECT_FALSE(cm.at("a") != "0"); - EXPECT_FALSE(cm.at("b") != "1"); - EXPECT_FALSE(cm.at("c") != "2"); - EXPECT_FALSE(cm.at("d") != "3"); - EXPECT_FALSE(cm.at("e") != "4"); - // verify_error(t, [&](Tree const&){ return cm.at(t.capacity()); }); verify_error(t, [&](Tree const&){ return cm.at(NONE); }); verify_error(t, [&](Tree const&){ return cm.at(cm.num_children()); }); @@ -1304,7 +1216,7 @@ foo: bar EXPECT_EQ(doc.is_stream(), doc.get()->m_type.is_stream()); EXPECT_EQ(keyval.is_stream(), keyval.get()->m_type.is_stream()); // - ASSERT_TRUE(t.docref(0)["none"].valid()); + ASSERT_TRUE(!t.docref(0)["none"].invalid()); ASSERT_TRUE(t.docref(0)["none"].is_seed()); ASSERT_FALSE(t.docref(0)["none"].readable()); verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].is_stream(); }); From 4193d930f2e1d3ade68fb79e8cd173be36fe7426 Mon Sep 17 00:00:00 2001 From: Joao Paulo Magalhaes Date: Fri, 5 Apr 2024 17:28:02 +0100 Subject: [PATCH 15/15] Tree: use the trees's callbacks for asserts and checks --- src/c4/yml/tree.hpp | 114 ++++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/src/c4/yml/tree.hpp b/src/c4/yml/tree.hpp index 093d3b8a6..28ec2a10f 100644 --- a/src/c4/yml/tree.hpp +++ b/src/c4/yml/tree.hpp @@ -498,7 +498,7 @@ class RYML_EXPORT Tree { return NONE; } - RYML_ASSERT(n >= m_buf && n < m_buf + m_cap); + _RYML_CB_ASSERT(m_callbacks, n >= m_buf && n < m_buf + m_cap); return static_cast(n - m_buf); } @@ -508,7 +508,7 @@ class RYML_EXPORT Tree { if(i == NONE) return nullptr; - RYML_ASSERT(i >= 0 && i < m_cap); + _RYML_CB_ASSERT(m_callbacks, i >= 0 && i < m_cap); return m_buf + i; } //! get a pointer to a node's NodeData. @@ -517,21 +517,21 @@ class RYML_EXPORT Tree { if(i == NONE) return nullptr; - RYML_ASSERT(i >= 0 && i < m_cap); + _RYML_CB_ASSERT(m_callbacks, i >= 0 && i < m_cap); return m_buf + i; } //! An if-less form of get() that demands a valid node index. //! This function is implementation only; use at your own risk. - inline NodeData * _p(size_t i) { RYML_ASSERT(i != NONE && i >= 0 && i < m_cap); return m_buf + i; } + inline NodeData * _p(size_t i) { _RYML_CB_ASSERT(m_callbacks, i != NONE && i >= 0 && i < m_cap); return m_buf + i; } //! An if-less form of get() that demands a valid node index. //! This function is implementation only; use at your own risk. - inline NodeData const * _p(size_t i) const { RYML_ASSERT(i != NONE && i >= 0 && i < m_cap); return m_buf + i; } + inline NodeData const * _p(size_t i) const { _RYML_CB_ASSERT(m_callbacks, i != NONE && i >= 0 && i < m_cap); return m_buf + i; } //! Get the id of the root node - size_t root_id() { if(m_cap == 0) { reserve(16); } RYML_ASSERT(m_cap > 0 && m_size > 0); return 0; } + size_t root_id() { if(m_cap == 0) { reserve(16); } _RYML_CB_ASSERT(m_callbacks, m_cap > 0 && m_size > 0); return 0; } //! Get the id of the root node - size_t root_id() const { RYML_ASSERT(m_cap > 0 && m_size > 0); return 0; } + size_t root_id() const { _RYML_CB_ASSERT(m_callbacks, m_cap > 0 && m_size > 0); return 0; } //! Get a NodeRef of a node by id NodeRef ref(size_t id); @@ -578,17 +578,17 @@ class RYML_EXPORT Tree NodeType type(size_t node) const { return _p(node)->m_type; } const char* type_str(size_t node) const { return NodeType::type_str(_p(node)->m_type); } - csubstr const& key (size_t node) const { RYML_ASSERT(has_key(node)); return _p(node)->m_key.scalar; } - csubstr const& key_tag (size_t node) const { RYML_ASSERT(has_key_tag(node)); return _p(node)->m_key.tag; } - csubstr const& key_ref (size_t node) const { RYML_ASSERT(is_key_ref(node) && ! has_key_anchor(node)); return _p(node)->m_key.anchor; } - csubstr const& key_anchor(size_t node) const { RYML_ASSERT( ! is_key_ref(node) && has_key_anchor(node)); return _p(node)->m_key.anchor; } - NodeScalar const& keysc (size_t node) const { RYML_ASSERT(has_key(node)); return _p(node)->m_key; } + csubstr const& key (size_t node) const { _RYML_CB_ASSERT(m_callbacks, has_key(node)); return _p(node)->m_key.scalar; } + csubstr const& key_tag (size_t node) const { _RYML_CB_ASSERT(m_callbacks, has_key_tag(node)); return _p(node)->m_key.tag; } + csubstr const& key_ref (size_t node) const { _RYML_CB_ASSERT(m_callbacks, is_key_ref(node) && ! has_key_anchor(node)); return _p(node)->m_key.anchor; } + csubstr const& key_anchor(size_t node) const { _RYML_CB_ASSERT(m_callbacks, ! is_key_ref(node) && has_key_anchor(node)); return _p(node)->m_key.anchor; } + NodeScalar const& keysc (size_t node) const { _RYML_CB_ASSERT(m_callbacks, has_key(node)); return _p(node)->m_key; } - csubstr const& val (size_t node) const { RYML_ASSERT(has_val(node)); return _p(node)->m_val.scalar; } - csubstr const& val_tag (size_t node) const { RYML_ASSERT(has_val_tag(node)); return _p(node)->m_val.tag; } - csubstr const& val_ref (size_t node) const { RYML_ASSERT(is_val_ref(node) && ! has_val_anchor(node)); return _p(node)->m_val.anchor; } - csubstr const& val_anchor(size_t node) const { RYML_ASSERT( ! is_val_ref(node) && has_val_anchor(node)); return _p(node)->m_val.anchor; } - NodeScalar const& valsc (size_t node) const { RYML_ASSERT(has_val(node)); return _p(node)->m_val; } + csubstr const& val (size_t node) const { _RYML_CB_ASSERT(m_callbacks, has_val(node)); return _p(node)->m_val.scalar; } + csubstr const& val_tag (size_t node) const { _RYML_CB_ASSERT(m_callbacks, has_val_tag(node)); return _p(node)->m_val.tag; } + csubstr const& val_ref (size_t node) const { _RYML_CB_ASSERT(m_callbacks, is_val_ref(node) && ! has_val_anchor(node)); return _p(node)->m_val.anchor; } + csubstr const& val_anchor(size_t node) const { _RYML_CB_ASSERT(m_callbacks, ! is_val_ref(node) && has_val_anchor(node)); return _p(node)->m_val.anchor; } + NodeScalar const& valsc (size_t node) const { _RYML_CB_ASSERT(m_callbacks, has_val(node)); return _p(node)->m_val; } /** @} */ @@ -622,16 +622,16 @@ class RYML_EXPORT Tree C4_ALWAYS_INLINE bool is_val_quoted(size_t node) const { return _p(node)->m_type.is_val_quoted(); } C4_ALWAYS_INLINE bool is_quoted(size_t node) const { return _p(node)->m_type.is_quoted(); } - C4_ALWAYS_INLINE bool parent_is_seq(size_t node) const { RYML_ASSERT(has_parent(node)); return is_seq(_p(node)->m_parent); } - C4_ALWAYS_INLINE bool parent_is_map(size_t node) const { RYML_ASSERT(has_parent(node)); return is_map(_p(node)->m_parent); } + C4_ALWAYS_INLINE bool parent_is_seq(size_t node) const { _RYML_CB_ASSERT(m_callbacks, has_parent(node)); return is_seq(_p(node)->m_parent); } + C4_ALWAYS_INLINE bool parent_is_map(size_t node) const { _RYML_CB_ASSERT(m_callbacks, has_parent(node)); return is_map(_p(node)->m_parent); } /** true when key and val are empty, and has no children */ C4_ALWAYS_INLINE bool empty(size_t node) const { return ! has_children(node) && _p(node)->m_key.empty() && (( ! (_p(node)->m_type & VAL)) || _p(node)->m_val.empty()); } /** true when the node has an anchor named a */ C4_ALWAYS_INLINE bool has_anchor(size_t node, csubstr a) const { return _p(node)->m_key.anchor == a || _p(node)->m_val.anchor == a; } - C4_ALWAYS_INLINE bool key_is_null(size_t node) const { RYML_ASSERT(has_key(node)); NodeData const* C4_RESTRICT n = _p(node); return !n->m_type.is_key_quoted() && scalar_is_null(n->m_key.scalar); } - C4_ALWAYS_INLINE bool val_is_null(size_t node) const { RYML_ASSERT(has_val(node)); NodeData const* C4_RESTRICT n = _p(node); return !n->m_type.is_val_quoted() && scalar_is_null(n->m_val.scalar); } + C4_ALWAYS_INLINE bool key_is_null(size_t node) const { _RYML_CB_ASSERT(m_callbacks, has_key(node)); NodeData const* C4_RESTRICT n = _p(node); return !n->m_type.is_key_quoted() && scalar_is_null(n->m_key.scalar); } + C4_ALWAYS_INLINE bool val_is_null(size_t node) const { _RYML_CB_ASSERT(m_callbacks, has_val(node)); NodeData const* C4_RESTRICT n = _p(node); return !n->m_type.is_val_quoted() && scalar_is_null(n->m_val.scalar); } /** @todo move this function to node_type.hpp */ static bool scalar_is_null(csubstr s) noexcept @@ -650,7 +650,7 @@ class RYML_EXPORT Tree /** @name hierarchy predicates */ /** @{ */ - bool is_root(size_t node) const { RYML_ASSERT(_p(node)->m_parent != NONE || node == 0); return _p(node)->m_parent == NONE; } + bool is_root(size_t node) const { _RYML_CB_ASSERT(m_callbacks, _p(node)->m_parent != NONE || node == 0); return _p(node)->m_parent == NONE; } bool has_parent(size_t node) const { return _p(node)->m_parent != NONE; } @@ -703,14 +703,14 @@ class RYML_EXPORT Tree /** counts with this */ size_t num_siblings(size_t node) const { return is_root(node) ? 1 : num_children(_p(node)->m_parent); } /** does not count with this */ - size_t num_other_siblings(size_t node) const { size_t ns = num_siblings(node); RYML_ASSERT(ns > 0); return ns-1; } - size_t sibling_pos(size_t node, size_t sib) const { RYML_ASSERT( ! is_root(node) || node == root_id()); return child_pos(_p(node)->m_parent, sib); } + size_t num_other_siblings(size_t node) const { size_t ns = num_siblings(node); _RYML_CB_ASSERT(m_callbacks, ns > 0); return ns-1; } + size_t sibling_pos(size_t node, size_t sib) const { _RYML_CB_ASSERT(m_callbacks, ! is_root(node) || node == root_id()); return child_pos(_p(node)->m_parent, sib); } size_t first_sibling(size_t node) const { return is_root(node) ? node : _p(_p(node)->m_parent)->m_first_child; } size_t last_sibling(size_t node) const { return is_root(node) ? node : _p(_p(node)->m_parent)->m_last_child; } size_t sibling(size_t node, size_t pos) const { return child(_p(node)->m_parent, pos); } size_t find_sibling(size_t node, csubstr const& key) const { return find_child(_p(node)->m_parent, key); } - size_t doc(size_t i) const { size_t rid = root_id(); RYML_ASSERT(is_stream(rid)); return child(rid, i); } //!< gets the @p i document node index. requires that the root node is a stream. + size_t doc(size_t i) const { size_t rid = root_id(); _RYML_CB_ASSERT(m_callbacks, is_stream(rid)); return child(rid, i); } //!< gets the @p i document node index. requires that the root node is a stream. /** @} */ @@ -728,16 +728,16 @@ class RYML_EXPORT Tree void to_doc(size_t node, type_bits more_flags=0); void to_stream(size_t node, type_bits more_flags=0); - void set_key(size_t node, csubstr key) { RYML_ASSERT(has_key(node)); _p(node)->m_key.scalar = key; } - void set_val(size_t node, csubstr val) { RYML_ASSERT(has_val(node)); _p(node)->m_val.scalar = val; } + void set_key(size_t node, csubstr key) { _RYML_CB_ASSERT(m_callbacks, has_key(node)); _p(node)->m_key.scalar = key; } + void set_val(size_t node, csubstr val) { _RYML_CB_ASSERT(m_callbacks, has_val(node)); _p(node)->m_val.scalar = val; } - void set_key_tag(size_t node, csubstr tag) { RYML_ASSERT(has_key(node)); _p(node)->m_key.tag = tag; _add_flags(node, KEYTAG); } - void set_val_tag(size_t node, csubstr tag) { RYML_ASSERT(has_val(node) || is_container(node)); _p(node)->m_val.tag = tag; _add_flags(node, VALTAG); } + void set_key_tag(size_t node, csubstr tag) { _RYML_CB_ASSERT(m_callbacks, has_key(node)); _p(node)->m_key.tag = tag; _add_flags(node, KEYTAG); } + void set_val_tag(size_t node, csubstr tag) { _RYML_CB_ASSERT(m_callbacks, has_val(node) || is_container(node)); _p(node)->m_val.tag = tag; _add_flags(node, VALTAG); } - void set_key_anchor(size_t node, csubstr anchor) { RYML_ASSERT( ! is_key_ref(node)); _p(node)->m_key.anchor = anchor.triml('&'); _add_flags(node, KEYANCH); } - void set_val_anchor(size_t node, csubstr anchor) { RYML_ASSERT( ! is_val_ref(node)); _p(node)->m_val.anchor = anchor.triml('&'); _add_flags(node, VALANCH); } - void set_key_ref (size_t node, csubstr ref ) { RYML_ASSERT( ! has_key_anchor(node)); NodeData* C4_RESTRICT n = _p(node); n->m_key.set_ref_maybe_replacing_scalar(ref, n->m_type.has_key()); _add_flags(node, KEY|KEYREF); } - void set_val_ref (size_t node, csubstr ref ) { RYML_ASSERT( ! has_val_anchor(node)); NodeData* C4_RESTRICT n = _p(node); n->m_val.set_ref_maybe_replacing_scalar(ref, n->m_type.has_val()); _add_flags(node, VAL|VALREF); } + void set_key_anchor(size_t node, csubstr anchor) { _RYML_CB_ASSERT(m_callbacks, ! is_key_ref(node)); _p(node)->m_key.anchor = anchor.triml('&'); _add_flags(node, KEYANCH); } + void set_val_anchor(size_t node, csubstr anchor) { _RYML_CB_ASSERT(m_callbacks, ! is_val_ref(node)); _p(node)->m_val.anchor = anchor.triml('&'); _add_flags(node, VALANCH); } + void set_key_ref (size_t node, csubstr ref ) { _RYML_CB_ASSERT(m_callbacks, ! has_key_anchor(node)); NodeData* C4_RESTRICT n = _p(node); n->m_key.set_ref_maybe_replacing_scalar(ref, n->m_type.has_key()); _add_flags(node, KEY|KEYREF); } + void set_val_ref (size_t node, csubstr ref ) { _RYML_CB_ASSERT(m_callbacks, ! has_val_anchor(node)); NodeData* C4_RESTRICT n = _p(node); n->m_val.set_ref_maybe_replacing_scalar(ref, n->m_type.has_val()); _add_flags(node, VAL|VALREF); } void rem_key_anchor(size_t node) { _p(node)->m_key.anchor.clear(); _rem_flags(node, KEYANCH); } void rem_val_anchor(size_t node) { _p(node)->m_val.anchor.clear(); _rem_flags(node, VALANCH); } @@ -823,9 +823,9 @@ class RYML_EXPORT Tree * first child, set after to NONE */ C4_ALWAYS_INLINE size_t insert_child(size_t parent, size_t after) { - RYML_ASSERT(parent != NONE); - RYML_ASSERT(is_container(parent) || is_root(parent)); - RYML_ASSERT(after == NONE || (_p(after)->m_parent == parent)); + _RYML_CB_ASSERT(m_callbacks, parent != NONE); + _RYML_CB_ASSERT(m_callbacks, is_container(parent) || is_root(parent)); + _RYML_CB_ASSERT(m_callbacks, after == NONE || (_p(after)->m_parent == parent)); size_t child = _claim(); _set_hierarchy(child, parent, after); return child; @@ -973,7 +973,7 @@ class RYML_EXPORT Tree /** get the current capacity of the tree's internal arena */ inline size_t arena_capacity() const { return m_arena.len; } /** get the current slack of the tree's internal arena */ - inline size_t arena_slack() const { RYML_ASSERT(m_arena.len >= m_arena_pos); return m_arena.len - m_arena_pos; } + inline size_t arena_slack() const { _RYML_CB_ASSERT(m_callbacks, m_arena.len >= m_arena_pos); return m_arena.len - m_arena_pos; } /** get the current arena */ substr arena() const { return m_arena.first(m_arena_pos); } @@ -1004,7 +1004,7 @@ class RYML_EXPORT Tree { rem = _grow_arena(num); num = to_chars_float(rem, a); - RYML_ASSERT(num <= rem.len); + _RYML_CB_ASSERT(m_callbacks, num <= rem.len); } rem = _request_span(num); return rem; @@ -1030,7 +1030,7 @@ class RYML_EXPORT Tree { rem = _grow_arena(num); num = to_chars(rem, a); - RYML_ASSERT(num <= rem.len); + _RYML_CB_ASSERT(m_callbacks, num <= rem.len); } rem = _request_span(num); return rem; @@ -1056,7 +1056,7 @@ class RYML_EXPORT Tree { rem = _grow_arena(num); num = to_chars(rem, a); - RYML_ASSERT(num <= rem.len); + _RYML_CB_ASSERT(m_callbacks, num <= rem.len); } return _request_span(num); } @@ -1099,8 +1099,8 @@ class RYML_EXPORT Tree substr copy_to_arena(csubstr s) { substr cp = alloc_arena(s.len); - RYML_ASSERT(cp.len == s.len); - RYML_ASSERT(!s.overlaps(cp)); + _RYML_CB_ASSERT(m_callbacks, cp.len == s.len); + _RYML_CB_ASSERT(m_callbacks, !s.overlaps(cp)); #if (!defined(__clang__)) && (defined(__GNUC__) && __GNUC__ >= 10) C4_SUPPRESS_WARNING_GCC_PUSH C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow=") // no need for terminating \0 @@ -1145,7 +1145,7 @@ class RYML_EXPORT Tree buf.len = arena_cap; if(m_arena.str) { - RYML_ASSERT(m_arena.len >= 0); + _RYML_CB_ASSERT(m_callbacks, m_arena.len >= 0); _relocate(buf); // does a memcpy and changes nodes using the arena m_callbacks.m_free(m_arena.str, m_arena.len, m_callbacks.m_user_data); } @@ -1176,12 +1176,12 @@ class RYML_EXPORT Tree substr _relocated(csubstr s, substr next_arena) const { - RYML_ASSERT(m_arena.is_super(s)); - RYML_ASSERT(m_arena.sub(0, m_arena_pos).is_super(s)); + _RYML_CB_ASSERT(m_callbacks, m_arena.is_super(s)); + _RYML_CB_ASSERT(m_callbacks, m_arena.sub(0, m_arena_pos).is_super(s)); auto pos = (s.str - m_arena.str); substr r(next_arena.str + pos, s.len); - RYML_ASSERT(r.str - next_arena.str == pos); - RYML_ASSERT(next_arena.sub(0, m_arena_pos).is_super(r)); + _RYML_CB_ASSERT(m_callbacks, r.str - next_arena.str == pos); + _RYML_CB_ASSERT(m_callbacks, next_arena.sub(0, m_arena_pos).is_super(r)); return r; } @@ -1284,14 +1284,14 @@ class RYML_EXPORT Tree } if(f & KEY) { - RYML_ASSERT(!is_root(node)); + _RYML_CB_ASSERT(m_callbacks, !is_root(node)); auto pid = parent(node); C4_UNUSED(pid); - RYML_ASSERT(is_map(pid)); + _RYML_CB_ASSERT(m_callbacks, is_map(pid)); } if((f & VAL) && !is_root(node)) { auto pid = parent(node); C4_UNUSED(pid); - RYML_ASSERT(is_map(pid) || is_seq(pid)); + _RYML_CB_ASSERT(m_callbacks, is_map(pid) || is_seq(pid)); } } #endif @@ -1318,24 +1318,24 @@ class RYML_EXPORT Tree void _set_val(size_t node, csubstr val, type_bits more_flags=0) { - RYML_ASSERT(num_children(node) == 0); - RYML_ASSERT(!is_seq(node) && !is_map(node)); + _RYML_CB_ASSERT(m_callbacks, num_children(node) == 0); + _RYML_CB_ASSERT(m_callbacks, !is_seq(node) && !is_map(node)); _p(node)->m_val.scalar = val; _add_flags(node, VAL|more_flags); } void _set_val(size_t node, NodeScalar const& val, type_bits more_flags=0) { - RYML_ASSERT(num_children(node) == 0); - RYML_ASSERT( ! is_container(node)); + _RYML_CB_ASSERT(m_callbacks, num_children(node) == 0); + _RYML_CB_ASSERT(m_callbacks, ! is_container(node)); _p(node)->m_val = val; _add_flags(node, VAL|more_flags); } void _set(size_t node, NodeInit const& i) { - RYML_ASSERT(i._check()); + _RYML_CB_ASSERT(m_callbacks, i._check()); NodeData *n = _p(node); - RYML_ASSERT(n->m_key.scalar.empty() || i.key.scalar.empty() || i.key.scalar == n->m_key.scalar); + _RYML_CB_ASSERT(m_callbacks, n->m_key.scalar.empty() || i.key.scalar.empty() || i.key.scalar == n->m_key.scalar); _add_flags(node, i.type); if(n->m_key.scalar.empty()) { @@ -1373,7 +1373,7 @@ class RYML_EXPORT Tree void _seq2map(size_t node) { - RYML_ASSERT(is_seq(node)); + _RYML_CB_ASSERT(m_callbacks, is_seq(node)); for(size_t i = first_child(node); i != NONE; i = next_sibling(i)) { NodeData *C4_RESTRICT ch = _p(i);