diff --git a/changelog/current.md b/changelog/current.md index 587333f0..099f3d06 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 12ee529a..64b89fcf 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 e1d23417..12bc6d25 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; }