diff --git a/.github/workflows/infra.yml b/.github/workflows/infra.yml index 7a4f1d09..22a3d884 100644 --- a/.github/workflows/infra.yml +++ b/.github/workflows/infra.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: log github event - run: echo "${{toJSON(github.event)}}" + run: echo "${{toJSON(github.event)}}" || echo >/dev/null check_workflows: if: always() continue-on-error: false diff --git a/.github/workflows/infra.ys b/.github/workflows/infra.ys index bb01720c..6cf56e80 100644 --- a/.github/workflows/infra.ys +++ b/.github/workflows/infra.ys @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: log github event - run: echo "${{toJSON(github.event)}}" + run: echo "${{toJSON(github.event)}}" || echo >/dev/null check_workflows: :: setup-job('infra' 'check_workflows') diff --git a/changelog/current.md b/changelog/current.md index a5428836..d980b3fa 100644 --- a/changelog/current.md +++ b/changelog/current.md @@ -1,6 +1,18 @@ ## Fixes +- Fix [#480](https://github.com/biojppm/rapidyaml/issues/480) ([PR#489](https://github.com/biojppm/rapidyaml/pull/489)): + - Deserializing an empty quoted string *will not* cause an error. + - Deserializing an empty string *will* cause an error. + - Ensure keys are deserialized using all the rules applying to vals. + - Added `KEYNIL` and `VALNIL` to `NodeType_e`, used by the parser to mark the key or val as empty. This changed the values of the `NodeType_e` enumeration. + - Added `NodeType::key_is_null()` and `NodeType::val_is_null()`. - [PR#488](https://github.com/biojppm/rapidyaml/pull/488): - add workarounds for problems with codegen of gcc 11,12,13 - improve CI coverage of gcc and clang optimization levels -- [BREAKING] Fix [#477](https://github.com/biojppm/rapidyaml/issues/477): changed `read()` to overwrite existing entries. The provided implementations had an inconsistency between `std::map` (which wasn't overwriting) and `std::vector` (which *was* overwriting). +- [BREAKING] Fix [#477](https://github.com/biojppm/rapidyaml/issues/477) ([PR#479](https://github.com/biojppm/rapidyaml/pull/479)): changed `read()` to overwrite existing entries. The provided implementations had an inconsistency between `std::map` (which wasn't overwriting) and `std::vector` (which *was* overwriting). + + +## Thanks + +- @Delian0 +- @perlpunk diff --git a/ext/c4core b/ext/c4core index c4d060fd..d9c6b3f1 160000 --- a/ext/c4core +++ b/ext/c4core @@ -1 +1 @@ -Subproject commit c4d060fd4b12ac3ab952e92182364afb6f4157c5 +Subproject commit d9c6b3f1604e962cad476bd040c564422aa3f2bc diff --git a/src/c4/yml/emit.def.hpp b/src/c4/yml/emit.def.hpp index f41243a1..fa0402a7 100644 --- a/src/c4/yml/emit.def.hpp +++ b/src/c4/yml/emit.def.hpp @@ -147,18 +147,19 @@ void Emitter::_emit_yaml(id_type id) template void Emitter::_write_doc(id_type id) { - RYML_ASSERT(m_tree->is_doc(id)); - RYML_ASSERT(!m_tree->has_key(id)); + const NodeType ty = m_tree->type(id); + RYML_ASSERT(ty.is_doc()); + RYML_ASSERT(!ty.has_key()); if(!m_tree->is_root(id)) { RYML_ASSERT(m_tree->is_stream(m_tree->parent(id))); this->Writer::_do_write("---"); } // - if(!m_tree->has_val(id)) // this is more frequent + if(!ty.has_val()) // this is more frequent { - const bool tag = m_tree->has_val_tag(id); - const bool anchor = m_tree->has_val_anchor(id); + const bool tag = ty.has_val_tag(); + const bool anchor = ty.has_val_anchor(); if(!tag && !anchor) { ; @@ -199,20 +200,20 @@ void Emitter::_write_doc(id_type id) } else // docval { - _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->has_val(id)); + _RYML_CB_ASSERT(m_tree->callbacks(), ty.has_val()); // some plain scalars such as '...' and '---' must not // appear at 0-indentation const csubstr val = m_tree->val(id); const bool preceded_by_3_dashes = !m_tree->is_root(id); - const type_bits style_marks = m_tree->type(id) & (KEY_STYLE|VAL_STYLE); - const bool is_plain = m_tree->type(id).is_val_plain(); + const type_bits style_marks = ty & VAL_STYLE; + const bool is_plain = ty.is_val_plain(); const bool is_ambiguous = (is_plain || !style_marks) && ((val.begins_with("...") || val.begins_with("---")) || (val.find('\n') != npos)); if(preceded_by_3_dashes) { - if(val.len == 0 && !m_tree->has_val_anchor(id) && !m_tree->has_val_tag(id)) + if(is_plain && val.len == 0 && !ty.has_val_anchor() && !ty.has_val_tag()) { this->Writer::_do_write('\n'); return; @@ -607,7 +608,7 @@ void Emitter::_write(NodeScalar const& C4_RESTRICT sc, NodeType flags, i } // ensure the style flags only have one of KEY or VAL - _RYML_CB_ASSERT(m_tree->callbacks(), ((flags & SCALAR_STYLE) == 0) || (((flags&KEY_STYLE) == 0) != ((flags&VAL_STYLE) == 0))); + _RYML_CB_ASSERT(m_tree->callbacks(), ((flags & SCALAR_STYLE) == 0) || (((flags & KEY_STYLE) == 0) != ((flags & VAL_STYLE) == 0))); type_bits style_marks = flags & SCALAR_STYLE; if(!style_marks) style_marks = scalar_style_choose(sc.scalar); diff --git a/src/c4/yml/emit.hpp b/src/c4/yml/emit.hpp index 1f5dad3d..3cd38b41 100644 --- a/src/c4/yml/emit.hpp +++ b/src/c4/yml/emit.hpp @@ -472,7 +472,6 @@ inline OStream& operator<< (OStream& s, as_yaml const& y) * @param id the node where to start emitting. * @param opts emit options. * @param buf the output buffer. - * @param opts emit options. * @param error_on_excess Raise an error if the space in the buffer is insufficient. * @return a substr trimmed to the result in the output buffer. If the buffer is * insufficient (when error_on_excess is false), the string pointer of the @@ -493,7 +492,6 @@ inline substr emit_yaml(Tree const& t, id_type id, substr buf, bool error_on_exc * @param id the node where to start emitting. * @param opts emit options. * @param buf the output buffer. - * @param opts emit options. * @param error_on_excess Raise an error if the space in the buffer is insufficient. * @return a substr trimmed to the result in the output buffer. If the buffer is * insufficient (when error_on_excess is false), the string pointer of the @@ -515,6 +513,7 @@ inline substr emit_json(Tree const& t, id_type id, substr buf, bool error_on_exc /** (1) emit YAML to the given buffer. Return a substr trimmed to the emitted YAML. * @param t the tree; will be emitted from the root node. + * @param opts emit options. * @param buf the output buffer. * @param error_on_excess Raise an error if the space in the buffer is insufficient. * @return a substr trimmed to the result in the output buffer. If the buffer is @@ -533,6 +532,7 @@ inline substr emit_yaml(Tree const& t, substr buf, bool error_on_excess=true) } /** (1) emit JSON to the given buffer. Return a substr trimmed to the emitted JSON. * @param t the tree; will be emitted from the root node. + * @param opts emit options. * @param buf the output buffer. * @param error_on_excess Raise an error if the space in the buffer is insufficient. * @return a substr trimmed to the result in the output buffer. If the buffer is diff --git a/src/c4/yml/event_handler_tree.hpp b/src/c4/yml/event_handler_tree.hpp index 7dc940fc..d41aa6e7 100644 --- a/src/c4/yml/event_handler_tree.hpp +++ b/src/c4/yml/event_handler_tree.hpp @@ -360,6 +360,19 @@ struct EventHandlerTree : public EventHandlerStacknode_id); + m_curr->tr_data->m_key.scalar = {}; + _enable_(KEY|KEY_PLAIN|KEYNIL); + } + C4_ALWAYS_INLINE void set_val_scalar_plain_empty() noexcept + { + _c4dbgpf("node[{}]: set val scalar plain as empty", m_curr->node_id); + m_curr->tr_data->m_val.scalar = {}; + _enable_(VAL|VAL_PLAIN|VALNIL); + } + C4_ALWAYS_INLINE void set_key_scalar_plain(csubstr scalar) noexcept { _c4dbgpf("node[{}]: set key scalar plain: [{}]~~~{}~~~ ({})", m_curr->node_id, scalar.len, scalar, reinterpret_cast(scalar.str)); diff --git a/src/c4/yml/node.hpp b/src/c4/yml/node.hpp index 6d637021..a8f0c997 100644 --- a/src/c4/yml/node.hpp +++ b/src/c4/yml/node.hpp @@ -44,15 +44,13 @@ template C4_ALWAYS_INLINE Key key(K & k) { return Key{k}; } C4_ALWAYS_INLINE Key key(fmt::const_base64_wrapper w) { return {w}; } C4_ALWAYS_INLINE Key key(fmt::base64_wrapper w) { return {w}; } -template void write(NodeRef *n, T const& v); -template -typename std::enable_if< ! std::is_floating_point::value, bool>::type -read(NodeRef const& n, T *v); +template void write(NodeRef *n, T const& v); -template -typename std::enable_if< std::is_floating_point::value, bool>::type -read(NodeRef const& n, T *v); +template inline bool read(ConstNodeRef const& C4_RESTRICT n, T *v); +template inline bool read(NodeRef const& C4_RESTRICT n, T *v); +template inline bool readkey(ConstNodeRef const& C4_RESTRICT n, T *v); +template inline bool readkey(NodeRef const& C4_RESTRICT n, T *v); /** @} */ @@ -328,8 +326,7 @@ struct RoNodeMethods template C4_ALWAYS_INLINE auto doc(id_type i) RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { RYML_ASSERT(tree_); return {tree__, tree__->doc(i)}; } /**< Forward to @ref Tree::doc(). Node must be readable. */ - /** succeeds even when the node may have invalid or seed id */ - C4_ALWAYS_INLINE ConstImpl doc(id_type i) const RYML_NOEXCEPT { RYML_ASSERT(tree_); return {tree_, tree_->doc(i)}; } /**< Forward to @ref Tree::doc(). Node must be readable. */ + C4_ALWAYS_INLINE ConstImpl doc(id_type i) const RYML_NOEXCEPT { RYML_ASSERT(tree_); return {tree_, tree_->doc(i)}; } /**< Forward to @ref Tree::doc(). Node must be readable. succeeds even when the node may have invalid or seed id */ template C4_ALWAYS_INLINE auto parent() RYML_NOEXCEPT -> _C4_IF_MUTABLE(Impl) { _C4RR(); return {tree__, tree__->parent(id__)}; } /**< Forward to @ref Tree::parent(). Node must be readable. */ @@ -633,42 +630,11 @@ struct RoNodeMethods ConstImpl const& operator>> (Key v) const { _C4RR(); - if(key().empty() || ! from_chars(key(), &v.k)) + if( ! readkey((ConstImpl const&)*this, &v.k)) _RYML_CB_ERR(tree_->m_callbacks, "could not deserialize key"); return *((ConstImpl const*)this); } - /** deserialize the node's key as base64. lightweight wrapper over @ref deserialize_key() */ - ConstImpl const& operator>> (Key w) const - { - deserialize_key(w.wrapper); - return *((ConstImpl const*)this); - } - - /** deserialize the node's val as base64. lightweight wrapper over @ref deserialize_val() */ - ConstImpl const& operator>> (fmt::base64_wrapper w) const - { - deserialize_val(w); - return *((ConstImpl const*)this); - } - - /** decode the base64-encoded key and assign the - * decoded blob to the given buffer/ - * @return the size of base64-decoded blob */ - size_t deserialize_key(fmt::base64_wrapper v) const - { - _C4RR(); - return from_chars(key(), &v); - } - /** decode the base64-encoded key and assign the - * decoded blob to the given buffer/ - * @return the size of base64-decoded blob */ - size_t deserialize_val(fmt::base64_wrapper v) const - { - _C4RR(); - return from_chars(val(), &v); - }; - /** look for a child by name, if it exists assign to var. return * true if the child existed. */ template @@ -702,6 +668,42 @@ struct RoNodeMethods } } + /** @name deserialization_base64 */ + /** @{ */ + + /** deserialize the node's key as base64. lightweight wrapper over @ref deserialize_key() */ + ConstImpl const& operator>> (Key w) const + { + deserialize_key(w.wrapper); + return *((ConstImpl const*)this); + } + + /** deserialize the node's val as base64. lightweight wrapper over @ref deserialize_val() */ + ConstImpl const& operator>> (fmt::base64_wrapper w) const + { + deserialize_val(w); + return *((ConstImpl const*)this); + } + + /** decode the base64-encoded key and assign the + * decoded blob to the given buffer/ + * @return the size of base64-decoded blob */ + size_t deserialize_key(fmt::base64_wrapper v) const + { + _C4RR(); + return from_chars(key(), &v); + } + /** decode the base64-encoded key and assign the + * decoded blob to the given buffer/ + * @return the size of base64-decoded blob */ + size_t deserialize_val(fmt::base64_wrapper v) const + { + _C4RR(); + return from_chars(val(), &v); + }; + + /** @} */ + /** @} */ public: @@ -1603,117 +1605,33 @@ inline ConstNodeRef& ConstNodeRef::operator= (NodeRef && that) noexcept // NOLIN */ template -inline void write(NodeRef *n, T const& v) +C4_ALWAYS_INLINE void write(NodeRef *n, T const& v) { n->set_val_serialized(v); } -namespace detail { -// SFINAE overloads for skipping leading + which cannot be read by the charconv functions template -C4_ALWAYS_INLINE auto read_skip_plus(csubstr val, T *v) - -> typename std::enable_if::value, bool>::type +C4_ALWAYS_INLINE bool read(ConstNodeRef const& C4_RESTRICT n, T *v) { - if(val.begins_with('+')) - val = val.sub(1); - return from_chars(val, v); + return read(n.m_tree, n.m_id, v); } -template -C4_ALWAYS_INLINE auto read_skip_plus(csubstr val, T *v) - -> typename std::enable_if< ! std::is_arithmetic::value, bool>::type -{ - return from_chars(val, v); -} -} // namespace detail -/** convert the val of a scalar node to a particular type, by - * forwarding its val to @ref from_chars(). The full string is - * used. - * @return false if the conversion failed */ -template -inline auto read(NodeRef const& n, T *v) - -> typename std::enable_if< ! std::is_floating_point::value, bool>::type -{ - csubstr val = n.val(); - if(val.empty()) - return false; - return detail::read_skip_plus(val, v); -} -/** convert the val of a scalar node to a particular type, by - * forwarding its val to @ref from_chars(). The full string is - * used. - * @return false if the conversion failed */ template -inline auto read(ConstNodeRef const& n, T *v) - -> typename std::enable_if< ! std::is_floating_point::value, bool>::type +C4_ALWAYS_INLINE bool read(NodeRef const& C4_RESTRICT n, T *v) { - csubstr val = n.val(); - if(val.empty()) - return false; - return detail::read_skip_plus(val, v); + return read(n.tree(), n.id(), v); } -/** convert the val of a scalar node to a floating point type, by - * forwarding its val to @ref from_chars_float(). - * - * @return false if the conversion failed - * - * @warning Unlike non-floating types, only the leading part of the - * string that may constitute a number is processed. This happens - * because the float parsing is delegated to fast_float, which is - * implemented that way. Consequently, for example, all of `"34"`, - * `"34 "` `"34hg"` `"34 gh"` will be read as 34. If you are not sure - * about the contents of the data, you can use - * csubstr::first_real_span() to check before calling `>>`, for - * example like this: - * - * ```cpp - * csubstr val = node.val(); - * if(val.first_real_span() == val) - * node >> v; - * else - * ERROR("not a real") - * ``` - */ template -typename std::enable_if::value, bool>::type -inline read(NodeRef const& n, T *v) +C4_ALWAYS_INLINE bool readkey(ConstNodeRef const& C4_RESTRICT n, T *v) { - csubstr val = n.val(); - if(val.empty()) - return false; - return from_chars_float(val, v); + return readkey(n.m_tree, n.m_id, v); } -/** convert the val of a scalar node to a floating point type, by - * forwarding its val to @ref from_chars_float(). - * - * @return false if the conversion failed - * - * @warning Unlike non-floating types, only the leading part of the - * string that may constitute a number is processed. This happens - * because the float parsing is delegated to fast_float, which is - * implemented that way. Consequently, for example, all of `"34"`, - * `"34 "` `"34hg"` `"34 gh"` will be read as 34. If you are not sure - * about the contents of the data, you can use - * csubstr::first_real_span() to check before calling `>>`, for - * example like this: - * - * ```cpp - * csubstr val = node.val(); - * if(val.first_real_span() == val) - * node >> v; - * else - * ERROR("not a real") - * ``` - */ + template -typename std::enable_if::value, bool>::type -inline read(ConstNodeRef const& n, T *v) +C4_ALWAYS_INLINE bool readkey(NodeRef const& C4_RESTRICT n, T *v) { - csubstr val = n.val(); - if(val.empty()) - return false; - return from_chars_float(val, v); + return readkey(n.tree(), n.id(), v); } /** @} */ diff --git a/src/c4/yml/node_type.cpp b/src/c4/yml/node_type.cpp index 903b0be1..c31fee9b 100644 --- a/src/c4/yml/node_type.cpp +++ b/src/c4/yml/node_type.cpp @@ -88,6 +88,7 @@ csubstr NodeType::type_str(substr buf, NodeType_e flags) noexcept _prflag(DOC, "DOC"); // key properties _prflag(KEY, "KEY"); + _prflag(KEYNIL, "KNIL"); _prflag(KEYTAG, "KTAG"); _prflag(KEYANCH, "KANCH"); _prflag(KEYREF, "KREF"); @@ -99,6 +100,7 @@ csubstr NodeType::type_str(substr buf, NodeType_e flags) noexcept _prflag(KEY_UNFILT, "KUNFILT"); // val properties _prflag(VAL, "VAL"); + _prflag(VALNIL, "VNIL"); _prflag(VALTAG, "VTAG"); _prflag(VALANCH, "VANCH"); _prflag(VALREF, "VREF"); diff --git a/src/c4/yml/node_type.hpp b/src/c4/yml/node_type.hpp index 5ce82c38..03e428c3 100644 --- a/src/c4/yml/node_type.hpp +++ b/src/c4/yml/node_type.hpp @@ -30,7 +30,7 @@ using type_bits = uint32_t; typedef enum : type_bits { #define __(v) (type_bits(1) << v) // a convenience define, undefined below // NOLINT NOTYPE = 0, ///< no node type or style is set - KEY = __(0), ///< is member of a map, must have non-empty key + KEY = __(0), ///< is member of a map VAL = __(1), ///< a scalar: has a scalar (ie string) value, possibly empty. must be a leaf node, and cannot be MAP or SEQ MAP = __(2), ///< a map: a parent of KEYVAL/KEYSEQ/KEYMAP nodes SEQ = __(3), ///< a seq: a parent of VAL/SEQ/MAP nodes @@ -42,28 +42,30 @@ typedef enum : type_bits { VALANCH = __(9), ///< the val has an &anchor KEYTAG = __(10), ///< the key has a tag VALTAG = __(11), ///< the val has a tag - _TYMASK = __(12)-1, ///< all the bits up to here + KEYNIL = __(12), ///< the key is null (eg `{ : b}` results in a null key) + VALNIL = __(13), ///< the val is null (eg `{a : }` results in a null val) + _TYMASK = __(14)-1, ///< all the bits up to here // // unfiltered flags: // - KEY_UNFILT = __(12), ///< the key scalar was left unfiltered; the parser was set not to filter. @see ParserOptions - VAL_UNFILT = __(13), ///< the val scalar was left unfiltered; the parser was set not to filter. @see ParserOptions + KEY_UNFILT = __(14), ///< the key scalar was left unfiltered; the parser was set not to filter. @see ParserOptions + VAL_UNFILT = __(15), ///< the val scalar was left unfiltered; the parser was set not to filter. @see ParserOptions // // style flags: // - FLOW_SL = __(14), ///< mark container with single-line flow style (seqs as '[val1,val2], maps as '{key: val,key2: val2}') - FLOW_ML = __(15), ///< (NOT IMPLEMENTED, work in progress) mark container with multi-line flow style (seqs as '[\n val1,\n val2\n], maps as '{\n key: val,\n key2: val2\n}') - BLOCK = __(16), ///< mark container with block style (seqs as '- val\n', maps as 'key: val') - KEY_LITERAL = __(17), ///< mark key scalar as multiline, block literal | - VAL_LITERAL = __(18), ///< mark val scalar as multiline, block literal | - KEY_FOLDED = __(19), ///< mark key scalar as multiline, block folded > - VAL_FOLDED = __(20), ///< mark val scalar as multiline, block folded > - KEY_SQUO = __(21), ///< mark key scalar as single quoted ' - VAL_SQUO = __(22), ///< mark val scalar as single quoted ' - KEY_DQUO = __(23), ///< mark key scalar as double quoted " - VAL_DQUO = __(24), ///< mark val scalar as double quoted " - KEY_PLAIN = __(25), ///< mark key scalar as plain scalar (unquoted, even when multiline) - VAL_PLAIN = __(26), ///< mark val scalar as plain scalar (unquoted, even when multiline) + FLOW_SL = __(16), ///< mark container with single-line flow style (seqs as '[val1,val2], maps as '{key: val,key2: val2}') + FLOW_ML = __(17), ///< (NOT IMPLEMENTED, work in progress) mark container with multi-line flow style (seqs as '[\n val1,\n val2\n], maps as '{\n key: val,\n key2: val2\n}') + BLOCK = __(18), ///< mark container with block style (seqs as '- val\n', maps as 'key: val') + KEY_LITERAL = __(19), ///< mark key scalar as multiline, block literal | + VAL_LITERAL = __(20), ///< mark val scalar as multiline, block literal | + KEY_FOLDED = __(21), ///< mark key scalar as multiline, block folded > + VAL_FOLDED = __(22), ///< mark val scalar as multiline, block folded > + KEY_SQUO = __(23), ///< mark key scalar as single quoted ' + VAL_SQUO = __(24), ///< mark val scalar as single quoted ' + KEY_DQUO = __(25), ///< mark key scalar as double quoted " + VAL_DQUO = __(26), ///< mark val scalar as double quoted " + KEY_PLAIN = __(27), ///< mark key scalar as plain scalar (unquoted, even when multiline) + VAL_PLAIN = __(28), ///< mark val scalar as plain scalar (unquoted, even when multiline) // // type combination masks: // @@ -169,6 +171,8 @@ struct RYML_EXPORT NodeType C4_ALWAYS_INLINE bool has_val() const noexcept { return (type & VAL) != 0; } C4_ALWAYS_INLINE bool is_val() const noexcept { return (type & KEYVAL) == VAL; } C4_ALWAYS_INLINE bool is_keyval() const noexcept { return (type & KEYVAL) == KEYVAL; } + C4_ALWAYS_INLINE bool key_is_null() const noexcept { return (type & KEYNIL) != 0; } + C4_ALWAYS_INLINE bool val_is_null() const noexcept { return (type & VALNIL) != 0; } C4_ALWAYS_INLINE bool has_key_tag() const noexcept { return (type & KEYTAG) != 0; } C4_ALWAYS_INLINE bool has_val_tag() const noexcept { return (type & VALTAG) != 0; } C4_ALWAYS_INLINE bool has_key_anchor() const noexcept { return (type & KEYANCH) != 0; } diff --git a/src/c4/yml/parse_engine.def.hpp b/src/c4/yml/parse_engine.def.hpp index 2f1ef3a0..9300c785 100644 --- a/src/c4/yml/parse_engine.def.hpp +++ b/src/c4/yml/parse_engine.def.hpp @@ -1039,7 +1039,7 @@ bool ParseEngine::_scan_scalar_plain_map_flow(ScannedScalar *C4_RE _c4dbgpf("scalar was [{}]~~~{}~~~", sc->scalar.len, sc->scalar); - return true; + return sc->scalar.len > 0u; } template @@ -1509,15 +1509,15 @@ void ParseEngine::_end_map_blck() { _c4dbgp("mapblck: set missing val"); _handle_annotations_before_blck_val_scalar(); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); } else if(has_any(QMRK)) { _c4dbgp("mapblck: set missing keyval"); _handle_annotations_before_blck_key_scalar(); - m_evt_handler->set_key_scalar_plain({}); + m_evt_handler->set_key_scalar_plain_empty(); _handle_annotations_before_blck_val_scalar(); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); } m_evt_handler->end_map(); } @@ -1529,7 +1529,7 @@ void ParseEngine::_end_seq_blck() { _c4dbgp("seqblck: set missing val"); _handle_annotations_before_blck_val_scalar(); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); } m_evt_handler->end_seq(); } @@ -1594,7 +1594,7 @@ void ParseEngine::_end2_doc() if(m_doc_empty) { _c4dbgp("doc was empty; add empty val"); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); } m_evt_handler->end_doc(); } @@ -1606,7 +1606,7 @@ void ParseEngine::_end2_doc_expl() if(m_doc_empty) { _c4dbgp("doc: no children; add empty val"); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); } m_evt_handler->end_doc_expl(); } @@ -1693,7 +1693,7 @@ void ParseEngine::_end_stream() { m_evt_handler->begin_doc(); _handle_annotations_before_blck_val_scalar(); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->end_doc(); } } @@ -4734,7 +4734,7 @@ void ParseEngine::_handle_seq_imap() else if(first == ',' || first == ']') { _c4dbgp("seqimap[RVAL]: finish without val."); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->end_map(); goto seqimap_finish; } @@ -4836,8 +4836,8 @@ void ParseEngine::_handle_seq_imap() else if(first == ',' || first == ']') { _c4dbgp("seqimap[QMRK]: finish without key."); - m_evt_handler->set_key_scalar_plain({}); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_key_scalar_plain_empty(); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->end_map(); goto seqimap_finish; } @@ -4877,7 +4877,7 @@ void ParseEngine::_handle_seq_imap() else if(first == ',' || first == ']') { _c4dbgp("seqimap[RKCL]: found ','. finish without val"); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->end_map(); goto seqimap_finish; } @@ -4999,7 +4999,7 @@ void ParseEngine::_handle_seq_flow() if(_maybe_scan_following_comma()) { _c4dbgp("seqflow[RVAL]: empty scalar!"); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); } } @@ -5012,7 +5012,7 @@ void ParseEngine::_handle_seq_flow() if(_maybe_scan_following_comma()) { _c4dbgp("seqflow[RVAL]: empty scalar!"); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); } } @@ -5022,7 +5022,7 @@ void ParseEngine::_handle_seq_flow() addrem_flags(RNXT, RVAL); m_evt_handler->begin_map_val_flow(); _set_indentation(m_evt_handler->m_parent->indref); - m_evt_handler->set_key_scalar_plain({}); + m_evt_handler->set_key_scalar_plain_empty(); addrem_flags(RSEQIMAP|RVAL, RSEQ|RNXT); _line_progressed(1); goto seqflow_finish; @@ -5161,11 +5161,19 @@ void ParseEngine::_handle_map_flow() else if(first == ':') { _c4dbgp("mapflow[RKEY]: setting empty key"); - m_evt_handler->set_key_scalar_plain({}); + m_evt_handler->set_key_scalar_plain_empty(); addrem_flags(RVAL, RKEY|QMRK); _line_progressed(1); _maybe_skip_whitespace_tokens(); } + else if(first == ',') + { + _c4dbgp("mapflow[RKEY]: empty key+val!"); + m_evt_handler->set_key_scalar_plain_empty(); + m_evt_handler->set_val_scalar_plain_empty(); + addrem_flags(RNXT, RKEY|QMRK); + // keep going in this function + } else if(first == '}') // this happens on a trailing comma like ", }" { _c4dbgp("mapflow[RKEY]: end!"); @@ -5242,7 +5250,7 @@ void ParseEngine::_handle_map_flow() { _c4dbgp("mapflow[RKCL]: end with missing val!"); addrem_flags(RVAL, RKCL); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->end_map(); _line_progressed(1); goto mapflow_finish; @@ -5250,7 +5258,7 @@ void ParseEngine::_handle_map_flow() else if(first == ',') { _c4dbgp("mapflow[RKCL]: got comma. val is missing"); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); addrem_flags(RKEY, RKCL); _line_progressed(1); @@ -5316,11 +5324,18 @@ void ParseEngine::_handle_map_flow() else if(first == '}') { _c4dbgp("mapflow[RVAL]: end!"); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->end_map(); _line_progressed(1); goto mapflow_finish; } + else if(first == ',') + { + _c4dbgp("mapflow[RVAL]: empty val!"); + m_evt_handler->set_val_scalar_plain_empty(); + addrem_flags(RNXT, RVAL); + // keep going in this function + } else if(first == '*') { csubstr ref = _scan_ref_map(); @@ -5408,7 +5423,7 @@ void ParseEngine::_handle_map_flow() else if(first == ':') { _c4dbgp("mapflow[QMRK]: setting empty key"); - m_evt_handler->set_key_scalar_plain({}); + m_evt_handler->set_key_scalar_plain_empty(); addrem_flags(RVAL, QMRK); _line_progressed(1); _maybe_skip_whitespace_tokens(); @@ -5416,12 +5431,19 @@ void ParseEngine::_handle_map_flow() else if(first == '}') // this happens on a trailing comma like ", }" { _c4dbgp("mapflow[QMRK]: end!"); - m_evt_handler->set_key_scalar_plain({}); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_key_scalar_plain_empty(); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->end_map(); _line_progressed(1); goto mapflow_finish; } + else if(first == ',') + { + _c4dbgp("mapflow[QMRK]: empty key+val!"); + m_evt_handler->set_key_scalar_plain_empty(); + m_evt_handler->set_val_scalar_plain_empty(); + addrem_flags(RNXT, QMRK); + } else if(first == '&') { csubstr anchor = _scan_anchor(); @@ -5674,7 +5696,7 @@ void ParseEngine::_handle_seq_block() else if(m_evt_handler->m_parent && m_evt_handler->m_parent->indref == startindent && has_any(RMAP|BLCK, m_evt_handler->m_parent)) { _c4dbgp("seqblck[RVAL]: empty val + end indentless seq + set key"); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->end_seq(); m_evt_handler->add_sibling(); csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, m_evt_handler->m_curr->indref); // KEY! @@ -5716,7 +5738,7 @@ void ParseEngine::_handle_seq_block() { _c4dbgp("seqblck[RVAL]: prev val was empty"); _handle_annotations_before_blck_val_scalar(); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); // keep in RVAL, but for the next sibling m_evt_handler->add_sibling(); } @@ -5741,7 +5763,7 @@ void ParseEngine::_handle_seq_block() _handle_annotations_before_start_mapblck(startline); m_evt_handler->begin_map_val_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); - m_evt_handler->set_key_scalar_plain({}); + m_evt_handler->set_key_scalar_plain_empty(); addrem_flags(RMAP|RVAL, RSEQ|RNXT); _line_progressed(1); _maybe_skip_whitespace_tokens(); @@ -6088,7 +6110,7 @@ void ParseEngine::_handle_map_block() { _c4dbgp("mapblck[RKEY]: setting empty key"); _handle_annotations_before_blck_key_scalar(); - m_evt_handler->set_key_scalar_plain({}); + m_evt_handler->set_key_scalar_plain_empty(); addrem_flags(RVAL, RKEY); _line_progressed(1); _maybe_skip_whitespace_tokens(); @@ -6224,7 +6246,7 @@ void ParseEngine::_handle_map_block() { _c4dbgp("mapblck[RKCL]: got '?'. val was empty"); _RYML_CB_CHECK(m_evt_handler->m_stack.m_callbacks, m_was_inside_qmrk); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); addrem_flags(QMRK, RKCL); _line_progressed(1); @@ -6266,7 +6288,7 @@ void ParseEngine::_handle_map_block() { _RYML_CB_CHECK(m_evt_handler->m_stack.m_callbacks, m_evt_handler->m_curr->indentation_eq()); _c4dbgp("mapblck[RKCL]: missing :"); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); m_was_inside_qmrk = false; addrem_flags(RKEY, RKCL); @@ -6407,7 +6429,7 @@ void ParseEngine::_handle_map_block() else { _c4dbgp("mapblck[RVAL]: prev val empty+this is a key"); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); csubstr maybe_filtered = _maybe_filter_key_scalar_squot(sc); // KEY! m_evt_handler->set_key_scalar_squoted(maybe_filtered); @@ -6447,7 +6469,7 @@ void ParseEngine::_handle_map_block() else { _c4dbgp("mapblck[RVAL]: prev val empty+this is a key"); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); csubstr maybe_filtered = _maybe_filter_key_scalar_dquot(sc); // KEY! m_evt_handler->set_key_scalar_dquoted(maybe_filtered); @@ -6509,7 +6531,7 @@ void ParseEngine::_handle_map_block() { _c4dbgp("mapblck[RVAL]: prev val empty+this is a key"); _handle_annotations_before_blck_val_scalar(); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); csubstr maybe_filtered = _maybe_filter_key_scalar_plain(sc, m_evt_handler->m_curr->indref); // KEY! m_evt_handler->set_key_scalar_plain(maybe_filtered); @@ -6611,7 +6633,7 @@ void ParseEngine::_handle_map_block() if(startindent == m_evt_handler->m_curr->indref) { _c4dbgp("mapblck[RVAL]: anchor for next key. val is missing!"); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); addrem_flags(RKEY, RVAL); } @@ -6627,7 +6649,7 @@ void ParseEngine::_handle_map_block() { _c4dbgp("mapblck[RVAL]: tag for next key. val is missing!"); _handle_annotations_before_blck_val_scalar(); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); addrem_flags(RKEY, RVAL); } @@ -6641,7 +6663,7 @@ void ParseEngine::_handle_map_block() { _c4dbgp("mapblck[RVAL]: got '?'. val was empty"); _handle_annotations_before_blck_val_scalar(); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); addrem_flags(QMRK, RVAL); } @@ -6668,9 +6690,9 @@ void ParseEngine::_handle_map_block() if(startindent == m_evt_handler->m_curr->indref) { _c4dbgp("mapblck[RVAL]: got ':'. val was empty, next key as well"); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); - m_evt_handler->set_key_scalar_plain({}); + m_evt_handler->set_key_scalar_plain_empty(); _line_progressed(1); _maybe_skip_whitespace_tokens(); goto mapblck_again; @@ -6944,7 +6966,7 @@ void ParseEngine::_handle_map_block() _c4dbgp("mapblck[QMRK]: empty key"); addrem_flags(RVAL, QMRK); _handle_annotations_before_blck_key_scalar(); - m_evt_handler->set_key_scalar_plain({}); + m_evt_handler->set_key_scalar_plain_empty(); _line_progressed(1); _maybe_skip_whitespace_tokens(); } @@ -6955,7 +6977,7 @@ void ParseEngine::_handle_map_block() _handle_annotations_before_start_mapblck_as_key(); m_evt_handler->begin_map_key_block(); _handle_annotations_and_indentation_after_start_mapblck(startindent, startline); - m_evt_handler->set_key_scalar_plain({}); + m_evt_handler->set_key_scalar_plain_empty(); _line_progressed(1); _maybe_skip_whitespace_tokens(); _set_indentation(startindent); @@ -7044,8 +7066,8 @@ void ParseEngine::_handle_map_block() else if(first == '?') { _c4dbgp("mapblck[QMRK]: another QMRK '?'"); - m_evt_handler->set_key_scalar_plain({}); - m_evt_handler->set_val_scalar_plain({}); + m_evt_handler->set_key_scalar_plain_empty(); + m_evt_handler->set_val_scalar_plain_empty(); m_evt_handler->add_sibling(); _line_progressed(1); } @@ -7367,7 +7389,7 @@ void ParseEngine::_handle_unk() _maybe_begin_doc(); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_map_val_block(); - m_evt_handler->set_key_scalar_plain({}); + m_evt_handler->set_key_scalar_plain_empty(); m_doc_empty = false; _save_indentation(); } @@ -7670,7 +7692,7 @@ C4_COLD void ParseEngine::_handle_usty() add_flags(RNXT); _handle_annotations_before_blck_val_scalar(); m_evt_handler->_push(); - m_evt_handler->set_key_scalar_plain({}); + m_evt_handler->set_key_scalar_plain_empty(); addrem_flags(RMAP|BLCK|RVAL, RNXT|USTY); _save_indentation(); _line_progressed(1); @@ -7862,7 +7884,7 @@ C4_COLD void ParseEngine::_handle_usty() add_flags(RNXT); _handle_annotations_before_blck_val_scalar(); m_evt_handler->begin_map_val_block(); - m_evt_handler->set_key_scalar_plain({}); + m_evt_handler->set_key_scalar_plain_empty(); addrem_flags(RMAP|BLCK|RVAL, RNXT|USTY); _save_indentation(); _line_progressed(1); diff --git a/src/c4/yml/tree.hpp b/src/c4/yml/tree.hpp index 68ed8296..410b829b 100644 --- a/src/c4/yml/tree.hpp +++ b/src/c4/yml/tree.hpp @@ -37,57 +37,16 @@ C4_SUPPRESS_WARNING_GCC("-Wtype-limits") namespace c4 { namespace yml { -/** encode a floating point value to a string. */ -template -size_t to_chars_float(substr buf, T val) -{ - C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wfloat-equal"); - static_assert(std::is_floating_point::value, "must be floating point"); - if(C4_UNLIKELY(std::isnan(val))) - return to_chars(buf, csubstr(".nan")); - else if(C4_UNLIKELY(val == std::numeric_limits::infinity())) - return to_chars(buf, csubstr(".inf")); - else if(C4_UNLIKELY(val == -std::numeric_limits::infinity())) - return to_chars(buf, csubstr("-.inf")); - return to_chars(buf, val); - C4_SUPPRESS_WARNING_GCC_CLANG_POP -} +template inline auto read(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value, bool>::type; +template inline auto read(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value && !std::is_floating_point::value, bool>::type; +template inline auto read(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value, bool>::type; +template inline auto readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value, bool>::type; +template inline auto readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value && !std::is_floating_point::value, bool>::type; +template inline auto readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) -> typename std::enable_if::value, bool>::type; -/** decode a floating point from string. Accepts special values: .nan, - * .inf, -.inf */ -template -bool from_chars_float(csubstr buf, T *C4_RESTRICT val) -{ - static_assert(std::is_floating_point::value, "must be floating point"); - if(C4_LIKELY(from_chars(buf, val))) - { - return true; - } - else if(C4_UNLIKELY(buf.begins_with('+'))) - { - return from_chars(buf.sub(1), val); - } - else if(C4_UNLIKELY(buf == ".nan" || buf == ".NaN" || buf == ".NAN")) - { - *val = std::numeric_limits::quiet_NaN(); - return true; - } - else if(C4_UNLIKELY(buf == ".inf" || buf == ".Inf" || buf == ".INF")) - { - *val = std::numeric_limits::infinity(); - return true; - } - else if(C4_UNLIKELY(buf == "-.inf" || buf == "-.Inf" || buf == "-.INF")) - { - *val = -std::numeric_limits::infinity(); - return true; - } - else - { - return false; - } -} +template size_t to_chars_float(substr buf, T val); +template bool from_chars_float(csubstr buf, T *C4_RESTRICT val); //----------------------------------------------------------------------------- @@ -425,14 +384,14 @@ class RYML_EXPORT Tree /** true when the node has an anchor named a */ C4_ALWAYS_INLINE bool has_anchor(id_type node, csubstr a) const { return _p(node)->m_key.anchor == a || _p(node)->m_val.anchor == a; } - /** true if the node key does not have any KEYQUO flags, and its scalar verifies scalar_is_null(). - * @warning the node must verify .has_key() (asserted) (ie must be a member of a map) + /** true if the node key is empty, or its scalar verifies @ref scalar_is_null(). + * @warning the node must verify @ref Tree::has_key() (asserted) (ie must be a member of a map) * @see https://github.com/biojppm/rapidyaml/issues/413 */ - C4_ALWAYS_INLINE bool key_is_null(id_type 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); } - /** true if the node key does not have any VALQUO flags, and its scalar verifies scalar_is_null(). - * @warning the node must verify .has_val() (asserted) (ie must be a scalar / must not be a container) + C4_ALWAYS_INLINE bool key_is_null(id_type node) const { _RYML_CB_ASSERT(m_callbacks, has_key(node)); NodeData const* C4_RESTRICT n = _p(node); return !n->m_type.is_key_quoted() && (n->m_type.key_is_null() || scalar_is_null(n->m_key.scalar)); } + /** true if the node val is empty, or its scalar verifies @ref scalar_is_null(). + * @warning the node must verify @ref Tree::has_val() (asserted) (ie must be a scalar / must not be a container) * @see https://github.com/biojppm/rapidyaml/issues/413 */ - C4_ALWAYS_INLINE bool val_is_null(id_type 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); } + C4_ALWAYS_INLINE bool val_is_null(id_type node) const { _RYML_CB_ASSERT(m_callbacks, has_val(node)); NodeData const* C4_RESTRICT n = _p(node); return !n->m_type.is_val_quoted() && (n->m_type.val_is_null() || scalar_is_null(n->m_val.scalar)); } /// true if the key was a scalar requiring filtering and was left /// unfiltered during the parsing (see ParserOptions) @@ -846,12 +805,12 @@ class RYML_EXPORT Tree * existing arena, and thus change the contents of individual * nodes, and thus cost O(numnodes)+O(arenasize). To avoid this * cost, ensure that the arena is reserved to an appropriate size - * using .reserve_arena() + * using @ref Tree::reserve_arena(). * * @see alloc_arena() */ template - typename std::enable_if::value, csubstr>::type - to_arena(T const& C4_RESTRICT a) + auto to_arena(T const& C4_RESTRICT a) + -> typename std::enable_if::value, csubstr>::type { substr rem(m_arena.sub(m_arena_pos)); size_t num = to_chars_float(rem, a); @@ -872,12 +831,12 @@ class RYML_EXPORT Tree * existing arena, and thus change the contents of individual * nodes, and thus cost O(numnodes)+O(arenasize). To avoid this * cost, ensure that the arena is reserved to an appropriate size - * using .reserve_arena() + * using @ref Tree::reserve_arena(). * * @see alloc_arena() */ template - typename std::enable_if::value, csubstr>::type - to_arena(T const& C4_RESTRICT a) + auto to_arena(T const& C4_RESTRICT a) + -> typename std::enable_if::value, csubstr>::type { substr rem(m_arena.sub(m_arena_pos)); size_t num = to_chars(rem, a); @@ -898,7 +857,7 @@ class RYML_EXPORT Tree * existing arena, and thus change the contents of individual * nodes, and thus cost O(numnodes)+O(arenasize). To avoid this * cost, ensure that the arena is reserved to an appropriate size - * using .reserve_arena() + * using @ref Tree::reserve_arena(). * * @see alloc_arena() */ csubstr to_arena(csubstr a) @@ -948,9 +907,11 @@ class RYML_EXPORT Tree * existing arena, and thus change the contents of individual * nodes, and thus cost O(numnodes)+O(arenasize). To avoid this * cost, ensure that the arena is reserved to an appropriate size - * using .reserve_arena() + * before using @ref Tree::reserve_arena() * - * @see alloc_arena() */ + * @see reserve_arena() + * @see alloc_arena() + */ substr copy_to_arena(csubstr s) { substr cp = alloc_arena(s.len); @@ -959,7 +920,7 @@ class RYML_EXPORT Tree #if (!defined(__clang__)) && (defined(__GNUC__) && __GNUC__ >= 10) C4_SUPPRESS_WARNING_GCC_PUSH C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow=") // no need for terminating \0 - C4_SUPPRESS_WARNING_GCC( "-Wrestrict") // there's an assert to ensure no violation of restrict behavior + C4_SUPPRESS_WARNING_GCC("-Wrestrict") // there's an assert to ensure no violation of restrict behavior #endif if(s.len) memcpy(cp.str, s.str, s.len); @@ -1362,8 +1323,214 @@ class RYML_EXPORT Tree }; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** @defgroup doc_serialization_helpers Serialization helpers + * + * @{ + */ + + +// NON-ARITHMETIC ------------------------------------------------------------- + +/** convert the val of a scalar node to a particular non-arithmetic + * non-float type, by forwarding its val to @ref from_chars(). The + * full string is used. + * @return false if the conversion failed, or if the key was empty and unquoted */ +template +inline auto read(Tree const* C4_RESTRICT tree, id_type id, T *v) + -> typename std::enable_if::value, bool>::type +{ + return C4_LIKELY(!(tree->type(id) & VALNIL)) ? from_chars(tree->val(id), v) : false; +} + +/** convert the key of a node to a particular non-arithmetic + * non-float type, by forwarding its key to @ref from_chars(). The + * full string is used. + * @return false if the conversion failed, or if the key was empty and unquoted */ +template +inline auto readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) + -> typename std::enable_if::value, bool>::type +{ + return C4_LIKELY(!(tree->type(id) & KEYNIL)) ? from_chars(tree->key(id), v) : false; +} + + +// INTEGRAL, NOT FLOATING ------------------------------------------------------------- + +/** convert the val of a scalar node to a particular arithmetic + * integral non-float type, by forwarding its val to @ref + * from_chars(). The full string is used. + * + * @return false if the conversion failed */ +template +inline auto read(Tree const* C4_RESTRICT tree, id_type id, T *v) + -> typename std::enable_if::value && !std::is_floating_point::value, bool>::type +{ + using U = typename std::remove_cv::type; + enum { ischar = std::is_same::value || std::is_same::value || std::is_same::value }; + csubstr val = tree->val(id); + NodeType ty = tree->type(id); + if(C4_UNLIKELY((ty & VALNIL) || val.empty())) + return false; + // quote integral numbers if they have a leading 0 + // https://github.com/biojppm/rapidyaml/issues/291 + char first = val[0]; + if(ty.is_val_quoted() && (first != '0' && !ischar)) + return false; + else if(first == '+') + val = val.sub(1); + return from_chars(val, v); +} + +/** convert the key of a node to a particular arithmetic + * integral non-float type, by forwarding its val to @ref + * from_chars(). The full string is used. + * + * @return false if the conversion failed */ +template +inline auto readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) + -> typename std::enable_if::value && !std::is_floating_point::value, bool>::type +{ + using U = typename std::remove_cv::type; + enum { ischar = std::is_same::value || std::is_same::value || std::is_same::value }; + csubstr key = tree->key(id); + NodeType ty = tree->type(id); + if((ty & KEYNIL) || key.empty()) + return false; + // quote integral numbers if they have a leading 0 + // https://github.com/biojppm/rapidyaml/issues/291 + char first = key[0]; + if(ty.is_key_quoted() && (first != '0' && !ischar)) + return false; + else if(first == '+') + key = key.sub(1); + return from_chars(key, v); +} + + +// FLOATING ------------------------------------------------------------- + +/** encode a floating point value to a string. */ +template +size_t to_chars_float(substr buf, T val) +{ + static_assert(std::is_floating_point::value, "must be floating point"); + C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wfloat-equal"); + if(C4_UNLIKELY(std::isnan(val))) + return to_chars(buf, csubstr(".nan")); + else if(C4_UNLIKELY(val == std::numeric_limits::infinity())) + return to_chars(buf, csubstr(".inf")); + else if(C4_UNLIKELY(val == -std::numeric_limits::infinity())) + return to_chars(buf, csubstr("-.inf")); + return to_chars(buf, val); + C4_SUPPRESS_WARNING_GCC_CLANG_POP +} + + +/** decode a floating point from string. Accepts special values: .nan, + * .inf, -.inf */ +template +bool from_chars_float(csubstr buf, T *C4_RESTRICT val) +{ + static_assert(std::is_floating_point::value, "must be floating point"); + if(C4_LIKELY(from_chars(buf, val))) + { + return true; + } + else if(C4_UNLIKELY(buf.begins_with('+'))) + { + return from_chars(buf.sub(1), val); + } + else if(C4_UNLIKELY(buf == ".nan" || buf == ".NaN" || buf == ".NAN")) + { + *val = std::numeric_limits::quiet_NaN(); + return true; + } + else if(C4_UNLIKELY(buf == ".inf" || buf == ".Inf" || buf == ".INF")) + { + *val = std::numeric_limits::infinity(); + return true; + } + else if(C4_UNLIKELY(buf == "-.inf" || buf == "-.Inf" || buf == "-.INF")) + { + *val = -std::numeric_limits::infinity(); + return true; + } + else + { + return false; + } +} + +/** convert the val of a scalar node to a floating point type, by + * forwarding its val to @ref from_chars_float(). + * + * @return false if the conversion failed + * + * @warning Unlike non-floating types, only the leading part of the + * string that may constitute a number is processed. This happens + * because the float parsing is delegated to fast_float, which is + * implemented that way. Consequently, for example, all of `"34"`, + * `"34 "` `"34hg"` `"34 gh"` will be read as 34. If you are not sure + * about the contents of the data, you can use + * csubstr::first_real_span() to check before calling `>>`, for + * example like this: + * + * ```cpp + * csubstr val = node.val(); + * if(val.first_real_span() == val) + * node >> v; + * else + * ERROR("not a real") + * ``` + */ +template +typename std::enable_if::value, bool>::type +inline read(Tree const* C4_RESTRICT tree, id_type id, T *v) +{ + csubstr val = tree->val(id); + return C4_LIKELY(!val.empty()) ? from_chars_float(val, v) : false; +} + +/** convert the key of a scalar node to a floating point type, by + * forwarding its key to @ref from_chars_float(). + * + * @return false if the conversion failed + * + * @warning Unlike non-floating types, only the leading part of the + * string that may constitute a number is processed. This happens + * because the float parsing is delegated to fast_float, which is + * implemented that way. Consequently, for example, all of `"34"`, + * `"34 "` `"34hg"` `"34 gh"` will be read as 34. If you are not sure + * about the contents of the data, you can use + * csubstr::first_real_span() to check before calling `>>`, for + * example like this: + * + * ```cpp + * csubstr key = node.key(); + * if(key.first_real_span() == key) + * node >> v; + * else + * ERROR("not a real") + * ``` + */ +template +typename std::enable_if::value, bool>::type +inline readkey(Tree const* C4_RESTRICT tree, id_type id, T *v) +{ + csubstr key = tree->key(id); + return C4_LIKELY(!key.empty()) ? from_chars_float(key, v) : false; +} + +/** @} */ + /** @} */ + } // namespace yml } // namespace c4 diff --git a/test/test_anchor.cpp b/test/test_anchor.cpp index d23cad82..8dbe0dd5 100644 --- a/test/test_anchor.cpp +++ b/test/test_anchor.cpp @@ -1324,7 +1324,7 @@ a: 1 &anchor c: 3 )", N(MB, L{ - N(KP|VP, "a", "1"), N(KP|VP, "b", {}), N(KP|VP, "c", AR(KEYANCH, "anchor"), "3") + N(KP|VP, "a", "1"), N(KP|VN, "b", {}), N(KP|VP, "c", AR(KEYANCH, "anchor"), "3") }) ); diff --git a/test/test_doc.cpp b/test/test_doc.cpp index c4e78913..8de933a8 100644 --- a/test/test_doc.cpp +++ b/test/test_doc.cpp @@ -57,7 +57,7 @@ CASE_GROUP(SIMPLE_DOC) ADD_CASE_TO_GROUP("one empty doc", R"(--- )", - N(STREAM, L{DOC|VP}) + N(STREAM, L{DOC|VN}) ); ADD_CASE_TO_GROUP("one empty doc, indented", @@ -106,7 +106,7 @@ R"(... ... --- )", - N(STREAM, L{DOC|VP}) + N(STREAM, L{DOC|VN}) ); ADD_CASE_TO_GROUP("three terminations and one explicit, v1", @@ -115,7 +115,7 @@ R"(... --- ... )", - N(STREAM, L{DOC|VP}) + N(STREAM, L{DOC|VN}) ); ADD_CASE_TO_GROUP("three terminations and one explicit, v2", @@ -124,7 +124,7 @@ R"(... ... ... )", - N(STREAM, L{DOC|VP}) + N(STREAM, L{DOC|VN}) ); ADD_CASE_TO_GROUP("three terminations and one explicit, v3", @@ -133,14 +133,14 @@ R"(--- ... ... )", - N(STREAM, L{DOC|VP}) + N(STREAM, L{DOC|VN}) ); ADD_CASE_TO_GROUP("one empty doc, explicit termination", R"(--- ... )", - N(STREAM, L{DOC|VP}) + N(STREAM, L{DOC|VN}) ); ADD_CASE_TO_GROUP("one empty doc, explicit termination, first indented", @@ -154,7 +154,7 @@ ADD_CASE_TO_GROUP("two empty docs", R"(--- --- )", - N(STREAM, L{DOC|VP, DOC|VP}) + N(STREAM, L{DOC|VN, DOC|VN}) ); ADD_CASE_TO_GROUP("two empty docs, with termination", @@ -162,7 +162,7 @@ R"(--- ... --- )", - N(STREAM, L{DOC|VP, DOC|VP}) + N(STREAM, L{DOC|VN, DOC|VN}) ); ADD_CASE_TO_GROUP("doc with single scalar", @@ -183,7 +183,7 @@ R"(--- --- --- )", - N(STREAM, L{DOC|VP, DOC|VP, DOC|VP, DOC|VP}) + N(STREAM, L{DOC|VN, DOC|VN, DOC|VN, DOC|VN}) ); ADD_CASE_TO_GROUP("simple doc, empty docs, indented", @@ -207,7 +207,7 @@ R"(--- --- ... )", - N(STREAM, L{DOC|VP, DOC|VP, DOC|VP, DOC|VP}) + N(STREAM, L{DOC|VN, DOC|VN, DOC|VN, DOC|VN}) ); ADD_CASE_TO_GROUP("simple doc, empty docs, term, indented", @@ -336,9 +336,9 @@ R"( )", N(STREAM, L{ N(DOC|MB, TL("!!set", L{ - N(KP|VP, "Mark McGwire", {}), - N(KP|VP, "Sammy Sosa", {}), - N(KP|VP, "Ken Griff", {}), + N(KP|VN, "Mark McGwire", {}), + N(KP|VN, "Sammy Sosa", {}), + N(KP|VN, "Ken Griff", {}), })) }) ); diff --git a/test/test_explicit_key.cpp b/test/test_explicit_key.cpp index cb73d2a9..3d1e06dd 100644 --- a/test/test_explicit_key.cpp +++ b/test/test_explicit_key.cpp @@ -134,18 +134,18 @@ R"( )", N(STREAM, L{ N(DOC|MB, L{ - N(KP|VP, "a", {}), - N(KP|VP, "b", {}), - N(KP|VP, "", {}) + N(KP|VN, "a", {}), + N(KP|VN, "b", {}), + N(KN|VN, "", {}) }), N(DOC|MB, TL("!!set", L{ - N(KP|VP, "a", {}), - N(KP|VP, "b", {}), + N(KP|VN, "a", {}), + N(KP|VN, "b", {}), })), N(DOC|MB, TL("!!set", L{ - N(KP|VP, "a", {}), - N(KP|VP, "b", {}), - N(KP|VP, "", {}) + N(KP|VN, "a", {}), + N(KP|VN, "b", {}), + N(KN|VN, "", {}) })), }) ); @@ -157,9 +157,9 @@ R"( ? )", N(MB, L{ - N(KP|VP, "", {}), - N(KP|VP, "", {}), - N(KP|VP, "", {}), + N(KN|VN, "", {}), + N(KN|VN, "", {}), + N(KN|VN, "", {}), }) ); @@ -181,18 +181,18 @@ R"( )", N(STREAM, L{ N(DOC|MB, L{ - N(KP|VP, "a", {}), - N(KP|VP, "b", {}), - N(KP|VP, "", {}) + N(KP|VN, "a", {}), + N(KP|VN, "b", {}), + N(KN|VN, "", {}) }), N(DOC|MB, TL("!!set", L{ - N(KP|VP, "a", {}), - N(KP|VP, "b", {}), + N(KP|VN, "a", {}), + N(KP|VN, "b", {}), })), N(DOC|MB, TL("!!set", L{ - N(KP|VP, "a", {}), - N(KP|VP, "b", {}), - N(KP|VP, "", {}) + N(KP|VN, "a", {}), + N(KP|VN, "b", {}), + N(KN|VN, "", {}) })), }) ); @@ -390,11 +390,11 @@ R"( e: )", N(MB, L{ - N(KP|VP, "a", {}), - N(KP|VP, "b", {}), - N(KP|VP, "c", {}), - N(KP|VP, "d", {}), - N(KP|VP, "e", {}), + N(KP|VN, "a", {}), + N(KP|VN, "b", {}), + N(KP|VN, "c", {}), + N(KP|VN, "d", {}), + N(KP|VN, "e", {}), }) ); @@ -409,11 +409,11 @@ a: 1 )", N(MB, L{ N(KP|VP, "a", "1"), - N(KP|VP, "b", {}), + N(KP|VN, "b", {}), N(KP|VP, "c", AR(KEYANCH, "anchor"), "3"), - N(KP|VP, "d", {}), + N(KP|VN, "d", {}), N(KP|VP, TS("!!str", "e"), "4"), - N(KP|VP, "f", {}), + N(KP|VN, "f", {}), }) ); diff --git a/test/test_github_issues.cpp b/test/test_github_issues.cpp index bad0eee4..decd67cd 100644 --- a/test/test_github_issues.cpp +++ b/test/test_github_issues.cpp @@ -301,7 +301,7 @@ ADD_CASE_TO_GROUP("github3-problem2-ex1", R"( audio resource: )", -N(MB, L{N(KP|VP, "audio resource", /*"~"*/{})}) +N(MB, L{N(KP|VN, "audio resource", /*"~"*/{})}) ); ADD_CASE_TO_GROUP("github3-problem2-ex2", @@ -310,7 +310,7 @@ audio resource: more: example: y )", -N(MB, L{N(KP|VP, "audio resource", /*"~"*/{}), N(KP|MB, "more", L{N(KP|VP, "example", "y")})}) +N(MB, L{N(KP|VN, "audio resource", /*"~"*/{}), N(KP|MB, "more", L{N(KP|VP, "example", "y")})}) ); ADD_CASE_TO_GROUP("github3-problem3", diff --git a/test/test_lib/test_case.cpp b/test/test_lib/test_case.cpp index a7857739..6e972990 100644 --- a/test/test_lib/test_case.cpp +++ b/test/test_lib/test_case.cpp @@ -538,6 +538,28 @@ void test_invariants(ConstNodeRef const& n) EXPECT_FALSE(n.val_ref().empty()); EXPECT_FALSE(n.has_val_anchor()); } + if(n.has_key()) + { + if(n.is_key_quoted()) + { + EXPECT_FALSE(n.key_is_null()); + } + if(n.key_is_null()) + { + EXPECT_FALSE(n.is_key_quoted()); + } + } + if(n.has_val() && n.is_val_quoted()) + { + if(n.is_val_quoted()) + { + EXPECT_FALSE(n.val_is_null()); + } + if(n.val_is_null()) + { + EXPECT_FALSE(n.is_val_quoted()); + } + } // ... add more tests here // now recurse into the children diff --git a/test/test_lib/test_case_node.cpp b/test/test_lib/test_case_node.cpp index 86b0aaa4..1cc08fad 100644 --- a/test/test_lib/test_case_node.cpp +++ b/test/test_lib/test_case_node.cpp @@ -113,7 +113,7 @@ void TestCaseNode::compare_child(yml::ConstNodeRef const& n, id_type pos) const } else { - printf("error: node should have child %.*s: ", (int)expectedch.key.len, expectedch.key.str); + printf("error: node should have child with key [%zu]~~~%.*s~~~: ", expectedch.key.len, (int)expectedch.key.len, expectedch.key.str); fflush(stdout); print_path(n); fflush(stdout); diff --git a/test/test_lib/test_group.hpp b/test/test_lib/test_group.hpp index fe97d935..9f564e0e 100644 --- a/test/test_lib/test_group.hpp +++ b/test/test_lib/test_group.hpp @@ -150,12 +150,14 @@ C4_SUPPRESS_WARNING_GCC("-Wunused-const-variable") #endif constexpr const NodeType_e KP = (KEY|KEY_PLAIN); ///< key, plain scalar +constexpr const NodeType_e KN = (KEY|KEY_PLAIN|KEYNIL); ///< key, plain scalar, nil constexpr const NodeType_e KS = (KEY|KEY_SQUO); ///< key, single-quoted scalar constexpr const NodeType_e KD = (KEY|KEY_DQUO); ///< key, double-quoted scalar constexpr const NodeType_e KL = (KEY|KEY_LITERAL); ///< key, block-literal scalar constexpr const NodeType_e KF = (KEY|KEY_FOLDED); ///< key, block-folded scalar constexpr const NodeType_e VP = (VAL|VAL_PLAIN); ///< val, plain scalar +constexpr const NodeType_e VN = (VAL|VAL_PLAIN|VALNIL); ///< val, plain scalar, nil constexpr const NodeType_e VS = (VAL|VAL_SQUO); ///< val, single-quoted scalar constexpr const NodeType_e VD = (VAL|VAL_DQUO); ///< val, double-quoted scalar constexpr const NodeType_e VL = (VAL|VAL_LITERAL); ///< val, block-literal scalar diff --git a/test/test_map.cpp b/test/test_map.cpp index 86fc386e..08f56e4d 100644 --- a/test/test_map.cpp +++ b/test/test_map.cpp @@ -839,12 +839,12 @@ key: val g: foo: bar )", -N(MB, L{N(KP|VP, "key", "val"), N(KP|VP, "a", {}), N(KP|VP, "b", {}), N(KP|VP, "c", {}), N(KP|VP, "d", {}), N(KP|VP, "e", {}), N(KP|VP, "f", {}), N(KP|VP, "g", {}), N(KP|VP, "foo", "bar"),}) +N(MB, L{N(KP|VP, "key", "val"), N(KP|VN, "a", {}), N(KP|VN, "b", {}), N(KP|VN, "c", {}), N(KP|VN, "d", {}), N(KP|VN, "e", {}), N(KP|VN, "f", {}), N(KP|VN, "g", {}), N(KP|VP, "foo", "bar"),}) ); ADD_CASE_TO_GROUP("simple map expl, null values 1", R"({key: val, a, b, c, d, e: , f: , g: , foo: bar})", -N(MFS, L{N(KP|VP, "key", "val"), N(KP|VP, "a", {}), N(KP|VP, "b", {}), N(KP|VP, "c", {}), N(KP|VP, "d", {}), N(KP|VP, "e", {}), N(KP|VP, "f", {}), N(KP|VP, "g", {}), N(KP|VP, "foo", "bar"),}) +N(MFS, L{N(KP|VP, "key", "val"), N(KP|VN, "a", {}), N(KP|VN, "b", {}), N(KP|VN, "c", {}), N(KP|VN, "d", {}), N(KP|VN, "e", {}), N(KP|VN, "f", {}), N(KP|VN, "g", {}), N(KP|VP, "foo", "bar"),}) ); ADD_CASE_TO_GROUP("simple map expl, null values 2", @@ -856,11 +856,11 @@ R"( - {a, b: 1, c: 2} )", N(SB, L{ - N(MFS, L{N(KP|VP, "a", {})}), - N(MFS, L{N(KP|VP, "a", {}), N(KP|VP, "b", {}), N(KP|VP, "c", {})}), - N(MFS, L{N(KP|VP, "a", "1"), N(KP|VP, "b", "2"), N(KP|VP, "c", {})}), - N(MFS, L{N(KP|VP, "a", "1"), N(KP|VP, "b", {}), N(KP|VP, "c", "2")}), - N(MFS, L{N(KP|VP, "a", {}), N(KP|VP, "b", "1"), N(KP|VP, "c", "2")}), + N(MFS, L{N(KP|VN, "a", {})}), + N(MFS, L{N(KP|VN, "a", {}), N(KP|VN, "b", {}), N(KP|VN, "c", {})}), + N(MFS, L{N(KP|VP, "a", "1"), N(KP|VP, "b", "2"), N(KP|VN, "c", {})}), + N(MFS, L{N(KP|VP, "a", "1"), N(KP|VN, "b", {}), N(KP|VP, "c", "2")}), + N(MFS, L{N(KP|VN, "a", {}), N(KP|VP, "b", "1"), N(KP|VP, "c", "2")}), }) ); @@ -874,12 +874,12 @@ R"( - {:foo:foo:, :bar:bar:, :baz:baz:} )", N(SB, L{ - N(MFS, L{N(KP|VP, "foo", {}), N(KP|VP, "bar", {}), N(KP|VP, "baz", {})}), - N(MFS, L{N(KP|VP, "foo", {}), N(KP|VP, "bar", {}), N(KP|VP, "baz", {})}), - N(MFS, L{N(KP|VP, "foo:foo", {}), N(KP|VP, "bar:bar", {}), N(KP|VP, "baz:baz", {})}), - N(MFS, L{N(KP|VP, "foo:foo", {}), N(KP|VP, "bar:bar", {}), N(KP|VP, "baz:baz", {})}), - N(MFS, L{N(KP|VP, ":foo:foo", {}), N(KP|VP, ":bar:bar", {}), N(KP|VP, ":baz:baz", {})}), - N(MFS, L{N(KP|VP, ":foo:foo", {}), N(KP|VP, ":bar:bar", {}), N(KP|VP, ":baz:baz", {})}), + N(MFS, L{N(KP|VN, "foo", {}), N(KP|VN, "bar", {}), N(KP|VN, "baz", {})}), + N(MFS, L{N(KP|VN, "foo", {}), N(KP|VN, "bar", {}), N(KP|VN, "baz", {})}), + N(MFS, L{N(KP|VN, "foo:foo", {}), N(KP|VN, "bar:bar", {}), N(KP|VN, "baz:baz", {})}), + N(MFS, L{N(KP|VN, "foo:foo", {}), N(KP|VN, "bar:bar", {}), N(KP|VN, "baz:baz", {})}), + N(MFS, L{N(KP|VN, ":foo:foo", {}), N(KP|VN, ":bar:bar", {}), N(KP|VN, ":baz:baz", {})}), + N(MFS, L{N(KP|VN, ":foo:foo", {}), N(KP|VN, ":bar:bar", {}), N(KP|VN, ":baz:baz", {})}), }) ); @@ -1122,23 +1122,23 @@ e3 ,f3: val3 , 0003 h3 ,i3: val3 ,0003 })", N(MFS, L{ // this is crazy... - N(KP|VP, "a0", {}), - N(KP|VP, "b0", "val0"), N(KP|VP, "0000 c0", {}), - N(KP|VP, "d0", "val0"), N(KP|VP, "0000 e0", {}), - N(KP|VP, "f0", "val0"), N(KP|VP, "0000 h0", {}), - N(KP|VP, "i0", "val0"), N(KP|VP, "0000 a1", {}), - N(KP|VP, "b1", "val1"), N(KP|VP, "0001 c1", {}), - N(KP|VP, "d1", "val1"), N(KP|VP, "0001 e1", {}), - N(KP|VP, "f1", "val1"), N(KP|VP, "0001 h1", {}), - N(KP|VP, "i1", "val1"), N(KP|VP, "0001 a2", {}), - N(KP|VP, "b2", "val2"), N(KP|VP, "0002 c2", {}), - N(KP|VP, "d2", "val2"), N(KP|VP, "0002 e2", {}), - N(KP|VP, "f2", "val2"), N(KP|VP, "0002 h2", {}), - N(KP|VP, "i2", "val2"), N(KP|VP, "0002 a3", {}), - N(KP|VP, "b3", "val3"), N(KP|VP, "0003 c3", {}), - N(KP|VP, "d3", "val3"), N(KP|VP, "0003 e3", {}), - N(KP|VP, "f3", "val3"), N(KP|VP, "0003 h3", {}), - N(KP|VP, "i3", "val3"), N(KP|VP, "0003", {}), + N(KP|VN, "a0", {}), + N(KP|VP, "b0", "val0"), N(KP|VN, "0000 c0", {}), + N(KP|VP, "d0", "val0"), N(KP|VN, "0000 e0", {}), + N(KP|VP, "f0", "val0"), N(KP|VN, "0000 h0", {}), + N(KP|VP, "i0", "val0"), N(KP|VN, "0000 a1", {}), + N(KP|VP, "b1", "val1"), N(KP|VN, "0001 c1", {}), + N(KP|VP, "d1", "val1"), N(KP|VN, "0001 e1", {}), + N(KP|VP, "f1", "val1"), N(KP|VN, "0001 h1", {}), + N(KP|VP, "i1", "val1"), N(KP|VN, "0001 a2", {}), + N(KP|VP, "b2", "val2"), N(KP|VN, "0002 c2", {}), + N(KP|VP, "d2", "val2"), N(KP|VN, "0002 e2", {}), + N(KP|VP, "f2", "val2"), N(KP|VN, "0002 h2", {}), + N(KP|VP, "i2", "val2"), N(KP|VN, "0002 a3", {}), + N(KP|VP, "b3", "val3"), N(KP|VN, "0003 c3", {}), + N(KP|VP, "d3", "val3"), N(KP|VN, "0003 e3", {}), + N(KP|VP, "f3", "val3"), N(KP|VN, "0003 h3", {}), + N(KP|VP, "i3", "val3"), N(KP|VN, "0003", {}), }) ); @@ -1236,8 +1236,8 @@ R"( : b )", N(MB, L{ - N(KP|VP, "", "a"), - N(KP|VP, "", "b"), + N(KN|VP, "", "a"), + N(KN|VP, "", "b"), })); ADD_CASE_TO_GROUP("simple map, empty keys 2JQS, v2", @@ -1248,8 +1248,8 @@ R"( b )", N(MB, L{ - N(KP|VP, "", "a"), - N(KP|VP, "", "b"), + N(KN|VP, "", "a"), + N(KN|VP, "", "b"), })); ADD_CASE_TO_GROUP("simple map, empty keys 4ABK, v1", @@ -1258,8 +1258,8 @@ R"({ : b, })", N(MFS, L{ - N(KP|VP, "", "a"), - N(KP|VP, "", "b"), + N(KN|VP, "", "a"), + N(KN|VP, "", "b"), })); ADD_CASE_TO_GROUP("simple map, empty keys 4ABK, v2", @@ -1270,8 +1270,8 @@ R"({ b, })", N(MFS, L{ - N(KP|VP, "", "a"), - N(KP|VP, "", "b"), + N(KN|VP, "", "a"), + N(KN|VP, "", "b"), })); ADD_CASE_TO_GROUP("simple map, empty keys 4ABK, v3", diff --git a/test/test_map_set.cpp b/test/test_map_set.cpp index d49c79a1..b9212f64 100644 --- a/test/test_map_set.cpp +++ b/test/test_map_set.cpp @@ -54,7 +54,7 @@ R"(!!set ? b ? )", -N(MB, TL("!!set", L{N(KP|VP, "a", {}), N(KP|VP, "b", {}), N(KP|VP, {}, "")})) +N(MB, TL("!!set", L{N(KP|VN, "a", {}), N(KP|VN, "b", {}), N(KN|VN, {}, "")})) ); ADD_CASE_TO_GROUP("doc as set, implicit", @@ -62,7 +62,7 @@ R"(!!set ? a ? b )", -N(MB, TL("!!set", L{N(KP|VP, "a", {}), N(KP|VP, "b", {})})) +N(MB, TL("!!set", L{N(KP|VN, "a", {}), N(KP|VN, "b", {})})) ); ADD_CASE_TO_GROUP("doc as set", @@ -73,9 +73,9 @@ R"(--- !!set )", N(STREAM, L{ N(DOC|MB, TL("!!set", L{ - N(KP|VP, "aa", /*"~"*/{}), - N(KP|VP, "bb", /*"~"*/{}), - N(KP|VP, "cc", /*"~"*/{}) + N(KP|VN, "aa", /*"~"*/{}), + N(KP|VN, "bb", /*"~"*/{}), + N(KP|VN, "cc", /*"~"*/{}) })) }) ); @@ -89,9 +89,9 @@ R"( )", N(STREAM, L{ N(DOC|MB, TL("!!set", L{ - N(KP|VP, "Mark McGwire", /*"~"*/{}), - N(KP|VP, "Sammy Sosa", /*"~"*/{}), - N(KP|VP, "Ken Griff", /*"~"*/{}), + N(KP|VN, "Mark McGwire", /*"~"*/{}), + N(KP|VN, "Sammy Sosa", /*"~"*/{}), + N(KP|VN, "Ken Griff", /*"~"*/{}), })) }) ); @@ -105,9 +105,9 @@ R"( )", N(STREAM, L{ N(DOC|MB, L{ - N(KP|VP, "Mark McGwire", /*"~"*/{}), - N(KP|VP, "Sammy Sosa", /*"~"*/{}), - N(KP|VP, "Ken Griff", /*"~"*/{}), + N(KP|VN, "Mark McGwire", /*"~"*/{}), + N(KP|VN, "Sammy Sosa", /*"~"*/{}), + N(KP|VN, "Ken Griff", /*"~"*/{}), }) }) ); @@ -119,9 +119,9 @@ R"(!!set ? Ken Griff )", N(MB, TL("!!set", L{ - N(KP|VP, "Mark McGwire", /*"~"*/{}), - N(KP|VP, "Sammy Sosa", /*"~"*/{}), - N(KP|VP, "Ken Griff", /*"~"*/{}), + N(KP|VN, "Mark McGwire", /*"~"*/{}), + N(KP|VN, "Sammy Sosa", /*"~"*/{}), + N(KP|VN, "Ken Griff", /*"~"*/{}), })) ); @@ -132,9 +132,9 @@ R"( ? Ken Griff )", N(MB, L{ - N(KP|VP, "Mark McGwire", /*"~"*/{}), - N(KP|VP, "Sammy Sosa", /*"~"*/{}), - N(KP|VP, "Ken Griff", /*"~"*/{}), + N(KP|VN, "Mark McGwire", /*"~"*/{}), + N(KP|VN, "Sammy Sosa", /*"~"*/{}), + N(KP|VN, "Ken Griff", /*"~"*/{}), }) ); diff --git a/test/test_node_type.cpp b/test/test_node_type.cpp index 47b55b36..e4dd66a3 100644 --- a/test/test_node_type.cpp +++ b/test/test_node_type.cpp @@ -30,6 +30,8 @@ TEST(NodeType, type_str_preset) EXPECT_EQ(to_csubstr(NodeType(DOC).type_str()), "DOC"); EXPECT_EQ(to_csubstr(NodeType(STREAM).type_str()), "STREAM"); EXPECT_EQ(to_csubstr(NodeType(NOTYPE).type_str()), "NOTYPE"); + EXPECT_EQ(to_csubstr(NodeType(KEYVAL|KEYNIL).type_str()), "KEYVAL***"); + EXPECT_EQ(to_csubstr(NodeType(KEYVAL|VALNIL).type_str()), "KEYVAL***"); EXPECT_EQ(to_csubstr(NodeType(KEYVAL|KEYREF).type_str()), "KEYVAL***"); EXPECT_EQ(to_csubstr(NodeType(KEYVAL|VALREF).type_str()), "KEYVAL***"); EXPECT_EQ(to_csubstr(NodeType(KEYVAL|KEYANCH).type_str()), "KEYVAL***"); @@ -99,6 +101,7 @@ TEST(NodeType, type_str) teststr(STREAM, "STREAM") teststr(DOC, "DOC") teststr(KEY, "KEY") + teststr(KEYNIL, "KNIL") teststr(KEYTAG, "KTAG") teststr(KEYANCH, "KANCH") teststr(KEYREF, "KREF") @@ -109,6 +112,7 @@ TEST(NodeType, type_str) teststr(KEY_PLAIN, "KPLAIN") teststr(KEY_UNFILT, "KUNFILT") teststr(VAL, "VAL") + teststr(VALNIL, "VNIL") teststr(VALTAG, "VTAG") teststr(VALANCH, "VANCH") teststr(VALREF, "VREF") @@ -328,6 +332,20 @@ TEST(NodeType, is_val) EXPECT_FALSE(NodeType(KEYSEQ).is_val()); } +TEST(NodeType, has_null_val) +{ + EXPECT_FALSE(NodeType(NOTYPE).val_is_null()); + EXPECT_FALSE(NodeType(VAL).val_is_null()); + EXPECT_FALSE(NodeType(KEY|KEYNIL).val_is_null()); + EXPECT_TRUE(NodeType(VAL|VALNIL).val_is_null()); + EXPECT_FALSE(NodeType(KEYVAL).val_is_null()); + EXPECT_FALSE(NodeType(KEYMAP).val_is_null()); + EXPECT_FALSE(NodeType(KEYSEQ).val_is_null()); + EXPECT_TRUE(NodeType(KEYVAL|VALNIL).val_is_null()); + EXPECT_TRUE(NodeType(KEYMAP|VALNIL).val_is_null()); + EXPECT_TRUE(NodeType(KEYSEQ|VALNIL).val_is_null()); +} + TEST(NodeType, has_key) { EXPECT_FALSE(NodeType(NOTYPE).has_key()); @@ -338,6 +356,20 @@ TEST(NodeType, has_key) EXPECT_TRUE(NodeType(KEYSEQ).has_key()); } +TEST(NodeType, key_is_null) +{ + EXPECT_FALSE(NodeType(NOTYPE).key_is_null()); + EXPECT_FALSE(NodeType(KEY).key_is_null()); + EXPECT_TRUE(NodeType(KEY|KEYNIL).key_is_null()); + EXPECT_FALSE(NodeType(VAL|VALNIL).key_is_null()); + EXPECT_FALSE(NodeType(KEYVAL).key_is_null()); + EXPECT_FALSE(NodeType(KEYMAP).key_is_null()); + EXPECT_FALSE(NodeType(KEYSEQ).key_is_null()); + EXPECT_TRUE(NodeType(KEYVAL|KEYNIL).key_is_null()); + EXPECT_TRUE(NodeType(KEYMAP|KEYNIL).key_is_null()); + EXPECT_TRUE(NodeType(KEYSEQ|KEYNIL).key_is_null()); +} + TEST(NodeType, is_keyval) { EXPECT_FALSE(NodeType(NOTYPE).is_keyval()); diff --git a/test/test_scalar_empty.cpp b/test/test_scalar_empty.cpp index 85bc514b..99e4dfa3 100644 --- a/test/test_scalar_empty.cpp +++ b/test/test_scalar_empty.cpp @@ -343,8 +343,8 @@ TEST(empty_scalar, build_zero_length_string) std::string yaml = emitrs_yaml(tr); #ifdef RYML_DBG - print_tree(tr); printf("~~~~~\n%.*s~~~~\n", (int)yaml.size(), yaml.c_str()); + print_tree(tr); #endif { diff --git a/test/test_scalar_null.cpp b/test/test_scalar_null.cpp index 4fc20cb3..29d37e7e 100644 --- a/test/test_scalar_null.cpp +++ b/test/test_scalar_null.cpp @@ -278,6 +278,320 @@ TEST(null_val, readme_example) } +TEST(issue480, deserialize_empty_val) +{ + csubstr yaml = R"( +flow: { + dquoted: "", + squoted: '', + plain: , + enull: null, + tilde: ~, +} +block: + dquoted: "" + squoted: '' + literal: | + folded: > + plain: + enull: null + tilde: ~ +)"; + ParserOptions opts = ParserOptions().locations(true); + Parser::handler_type evt_handler = {}; + Parser parser(&evt_handler, opts); + Tree t = parse_in_arena(&parser, yaml); + ConstNodeRef fdquoted = t["flow" ][0]; + ConstNodeRef bdquoted = t["block"][0]; + ConstNodeRef fsquoted = t["flow" ][1]; + ConstNodeRef bsquoted = t["block"][1]; + ConstNodeRef bliteral = t["block"][2]; + ConstNodeRef bfolded = t["block"][3]; + ConstNodeRef fplain = t["flow" ][2]; + ConstNodeRef bplain = t["block"][4]; + ConstNodeRef fenull = t["flow" ][3]; + ConstNodeRef benull = t["block"][5]; + ConstNodeRef ftilde = t["flow" ][4]; + ConstNodeRef btilde = t["block"][6]; + // + // check also locations because nullity may influence the search + // for location + EXPECT_EQ(parser.location(fdquoted).line, 2u); + EXPECT_EQ(parser.location(bdquoted).line, 9u); + EXPECT_EQ(parser.location(fsquoted).line, 3u); + EXPECT_EQ(parser.location(bsquoted).line, 10u); + EXPECT_EQ(parser.location(bliteral).line, 11u); + EXPECT_EQ(parser.location(bfolded ).line, 12u); + EXPECT_EQ(parser.location(fplain ).line, 4u); + EXPECT_EQ(parser.location(bplain ).line, 13u); + EXPECT_EQ(parser.location(fenull ).line, 5u); + EXPECT_EQ(parser.location(benull ).line, 14u); + EXPECT_EQ(parser.location(ftilde ).line, 6u); + EXPECT_EQ(parser.location(btilde ).line, 15u); + // + EXPECT_TRUE(fdquoted.has_val()); + EXPECT_TRUE(bdquoted.has_val()); + EXPECT_TRUE(fsquoted.has_val()); + EXPECT_TRUE(bsquoted.has_val()); + EXPECT_TRUE(bliteral.has_val()); + EXPECT_TRUE(bfolded .has_val()); + EXPECT_TRUE(fplain .has_val()); + EXPECT_TRUE(bplain .has_val()); + EXPECT_TRUE(fenull .has_val()); + EXPECT_TRUE(benull .has_val()); + EXPECT_TRUE(ftilde .has_val()); + EXPECT_TRUE(btilde .has_val()); + // + EXPECT_FALSE(fdquoted.val_is_null()); + EXPECT_FALSE(bdquoted.val_is_null()); + EXPECT_FALSE(fsquoted.val_is_null()); + EXPECT_FALSE(bsquoted.val_is_null()); + EXPECT_FALSE(bliteral.val_is_null()); + EXPECT_FALSE(bfolded .val_is_null()); + EXPECT_TRUE (fplain .val_is_null()); + EXPECT_TRUE (bplain .val_is_null()); + EXPECT_TRUE (fenull .val_is_null()); + EXPECT_TRUE (benull .val_is_null()); + EXPECT_TRUE (ftilde .val_is_null()); + EXPECT_TRUE (btilde .val_is_null()); + // + EXPECT_EQ(fdquoted, ""); + EXPECT_EQ(bdquoted, ""); + EXPECT_EQ(fsquoted, ""); + EXPECT_EQ(bsquoted, ""); + EXPECT_EQ(bliteral, ""); + EXPECT_EQ(bfolded , ""); + EXPECT_EQ(fplain , ""); + EXPECT_EQ(bplain , ""); + EXPECT_EQ(fenull , "null"); + EXPECT_EQ(benull , "null"); + EXPECT_EQ(ftilde , "~"); + EXPECT_EQ(btilde , "~"); + // + EXPECT_NE(fdquoted.val().str, nullptr); + EXPECT_NE(bdquoted.val().str, nullptr); + EXPECT_NE(fsquoted.val().str, nullptr); + EXPECT_NE(bsquoted.val().str, nullptr); + EXPECT_NE(bliteral.val().str, nullptr); + EXPECT_NE(bfolded .val().str, nullptr); + EXPECT_EQ(fplain .val().str, nullptr); + EXPECT_EQ(bplain .val().str, nullptr); + EXPECT_NE(fenull .val().str, nullptr); + EXPECT_NE(benull .val().str, nullptr); + EXPECT_NE(ftilde .val().str, nullptr); + EXPECT_NE(btilde .val().str, nullptr); + // + EXPECT_EQ(fdquoted.val().len, 0); + EXPECT_EQ(bdquoted.val().len, 0); + EXPECT_EQ(fsquoted.val().len, 0); + EXPECT_EQ(bsquoted.val().len, 0); + EXPECT_EQ(bliteral.val().len, 0); + EXPECT_EQ(bfolded .val().len, 0); + EXPECT_EQ(fplain .val().len, 0); + EXPECT_EQ(bplain .val().len, 0); + EXPECT_EQ(fenull .val().len, 4); + EXPECT_EQ(benull .val().len, 4); + EXPECT_EQ(ftilde .val().len, 1); + EXPECT_EQ(btilde .val().len, 1); + // + EXPECT_EQ(fdquoted, csubstr{}); + EXPECT_EQ(bdquoted, csubstr{}); + EXPECT_EQ(fsquoted, csubstr{}); + EXPECT_EQ(bsquoted, csubstr{}); + EXPECT_EQ(bliteral, csubstr{}); + EXPECT_EQ(bfolded , csubstr{}); + EXPECT_EQ(fplain , csubstr{}); + EXPECT_EQ(bplain , csubstr{}); + EXPECT_NE(fenull , csubstr{}); + EXPECT_NE(benull , csubstr{}); + EXPECT_NE(ftilde , csubstr{}); + EXPECT_NE(btilde , csubstr{}); + // + EXPECT_NE(fdquoted, nullptr); + EXPECT_NE(bdquoted, nullptr); + EXPECT_NE(fsquoted, nullptr); + EXPECT_NE(bsquoted, nullptr); + EXPECT_NE(bliteral, nullptr); + EXPECT_NE(bfolded , nullptr); + EXPECT_NE(fplain , nullptr); + EXPECT_NE(bplain , nullptr); + EXPECT_NE(fenull , nullptr); + EXPECT_NE(benull , nullptr); + EXPECT_NE(ftilde , nullptr); + EXPECT_NE(btilde , nullptr); + // + std::string s; + s = "asd"; fdquoted >> s; EXPECT_EQ(s, ""); + s = "asd"; bdquoted >> s; EXPECT_EQ(s, ""); + s = "asd"; fsquoted >> s; EXPECT_EQ(s, ""); + s = "asd"; bsquoted >> s; EXPECT_EQ(s, ""); + s = "asd"; bliteral >> s; EXPECT_EQ(s, ""); + s = "asd"; bfolded >> s; EXPECT_EQ(s, ""); + s = "asd"; ExpectError::check_error(&t, [&]{ fplain >> s; }); + s = "asd"; ExpectError::check_error(&t, [&]{ bplain >> s; }); + s = "asd"; fenull >> s; EXPECT_EQ(s, "null"); + s = "asd"; benull >> s; EXPECT_EQ(s, "null"); + s = "asd"; ftilde >> s; EXPECT_EQ(s, "~"); + s = "asd"; btilde >> s; EXPECT_EQ(s, "~"); + // check error also for integral and float types + ExpectError::check_error(&t, [&]{ int val = 0; fplain >> val; }); + ExpectError::check_error(&t, [&]{ int val = 0; bplain >> val; }); + ExpectError::check_error(&t, [&]{ float val = 0; fplain >> val; }); + ExpectError::check_error(&t, [&]{ float val = 0; bplain >> val; }); +} + +TEST(issue480, deserialize_empty_key) +{ + csubstr yaml = R"( +flow: { + "": dquoted, + '': squoted, + : plain, + null: enull, + ~: tilde, +} +block: + "": dquoted + '': squoted + ? | + : literal + ? > + : folded + : plain + null: enull + ~: tilde +)"; + ParserOptions opts = ParserOptions().locations(true); + Parser::handler_type evt_handler = {}; + Parser parser(&evt_handler, opts); + Tree t = parse_in_arena(&parser, yaml); + ConstNodeRef fdquoted = t["flow" ][0]; + ConstNodeRef bdquoted = t["block"][0]; + ConstNodeRef fsquoted = t["flow" ][1]; + ConstNodeRef bsquoted = t["block"][1]; + ConstNodeRef bliteral = t["block"][2]; + ConstNodeRef bfolded = t["block"][3]; + ConstNodeRef fplain = t["flow" ][2]; + ConstNodeRef bplain = t["block"][4]; + ConstNodeRef fenull = t["flow" ][3]; + ConstNodeRef benull = t["block"][5]; + ConstNodeRef ftilde = t["flow" ][4]; + ConstNodeRef btilde = t["block"][6]; + // + // check also locations because nullity may influence the search + // for location + EXPECT_EQ(parser.location(fdquoted).line, 2u); + EXPECT_EQ(parser.location(bdquoted).line, 9u); + EXPECT_EQ(parser.location(fsquoted).line, 3u); + EXPECT_EQ(parser.location(bsquoted).line, 10u); + EXPECT_EQ(parser.location(bliteral).line, 12u); + EXPECT_EQ(parser.location(bfolded ).line, 14u); + EXPECT_EQ(parser.location(fplain ).line, 4u); + EXPECT_EQ(parser.location(bplain ).line, 15u); + EXPECT_EQ(parser.location(fenull ).line, 5u); + EXPECT_EQ(parser.location(benull ).line, 16u); + EXPECT_EQ(parser.location(ftilde ).line, 6u); + EXPECT_EQ(parser.location(btilde ).line, 17u); + // + EXPECT_TRUE(fdquoted.has_key()); + EXPECT_TRUE(bdquoted.has_key()); + EXPECT_TRUE(fsquoted.has_key()); + EXPECT_TRUE(bsquoted.has_key()); + EXPECT_TRUE(bliteral.has_key()); + EXPECT_TRUE(bfolded .has_key()); + EXPECT_TRUE(fplain .has_key()); + EXPECT_TRUE(bplain .has_key()); + EXPECT_TRUE(fenull .has_key()); + EXPECT_TRUE(benull .has_key()); + EXPECT_TRUE(ftilde .has_key()); + EXPECT_TRUE(btilde .has_key()); + // + EXPECT_FALSE(fdquoted.key_is_null()); + EXPECT_FALSE(bdquoted.key_is_null()); + EXPECT_FALSE(fsquoted.key_is_null()); + EXPECT_FALSE(bsquoted.key_is_null()); + EXPECT_FALSE(bliteral.key_is_null()); + EXPECT_FALSE(bfolded .key_is_null()); + EXPECT_TRUE (fplain .key_is_null()); + EXPECT_TRUE (bplain .key_is_null()); + EXPECT_TRUE (fenull .key_is_null()); + EXPECT_TRUE (benull .key_is_null()); + EXPECT_TRUE (ftilde .key_is_null()); + EXPECT_TRUE (btilde .key_is_null()); + // + EXPECT_EQ(fdquoted.key(), ""); + EXPECT_EQ(bdquoted.key(), ""); + EXPECT_EQ(fsquoted.key(), ""); + EXPECT_EQ(bsquoted.key(), ""); + EXPECT_EQ(bliteral.key(), ""); + EXPECT_EQ(bfolded.key() , ""); + EXPECT_EQ(fplain.key() , ""); + EXPECT_EQ(bplain.key() , ""); + EXPECT_EQ(fenull.key() , "null"); + EXPECT_EQ(benull.key() , "null"); + EXPECT_EQ(ftilde.key() , "~"); + EXPECT_EQ(btilde.key() , "~"); + // + EXPECT_NE(fdquoted.key().str, nullptr); + EXPECT_NE(bdquoted.key().str, nullptr); + EXPECT_NE(fsquoted.key().str, nullptr); + EXPECT_NE(bsquoted.key().str, nullptr); + EXPECT_NE(bliteral.key().str, nullptr); + EXPECT_NE(bfolded .key().str, nullptr); + EXPECT_EQ(fplain .key().str, nullptr); + EXPECT_EQ(bplain .key().str, nullptr); + EXPECT_NE(fenull .key().str, nullptr); + EXPECT_NE(benull .key().str, nullptr); + EXPECT_NE(ftilde .key().str, nullptr); + EXPECT_NE(btilde .key().str, nullptr); + // + EXPECT_EQ(fdquoted.key().len, 0); + EXPECT_EQ(bdquoted.key().len, 0); + EXPECT_EQ(fsquoted.key().len, 0); + EXPECT_EQ(bsquoted.key().len, 0); + EXPECT_EQ(bliteral.key().len, 0); + EXPECT_EQ(bfolded .key().len, 0); + EXPECT_EQ(fplain .key().len, 0); + EXPECT_EQ(bplain .key().len, 0); + EXPECT_EQ(fenull .key().len, 4); + EXPECT_EQ(benull .key().len, 4); + EXPECT_EQ(ftilde .key().len, 1); + EXPECT_EQ(btilde .key().len, 1); + // + EXPECT_EQ(fdquoted.key(), csubstr{}); + EXPECT_EQ(bdquoted.key(), csubstr{}); + EXPECT_EQ(fsquoted.key(), csubstr{}); + EXPECT_EQ(bsquoted.key(), csubstr{}); + EXPECT_EQ(bliteral.key(), csubstr{}); + EXPECT_EQ(bfolded.key() , csubstr{}); + EXPECT_EQ(fplain.key() , csubstr{}); + EXPECT_EQ(bplain.key() , csubstr{}); + EXPECT_NE(fenull.key() , csubstr{}); + EXPECT_NE(benull.key() , csubstr{}); + EXPECT_NE(ftilde.key() , csubstr{}); + EXPECT_NE(btilde.key() , csubstr{}); + // + std::string s; + s = "asd"; fdquoted >> key(s); EXPECT_EQ(s, ""); + s = "asd"; bdquoted >> key(s); EXPECT_EQ(s, ""); + s = "asd"; fsquoted >> key(s); EXPECT_EQ(s, ""); + s = "asd"; bsquoted >> key(s); EXPECT_EQ(s, ""); + s = "asd"; bliteral >> key(s); EXPECT_EQ(s, ""); + s = "asd"; bfolded >> key(s); EXPECT_EQ(s, ""); + s = "asd"; ExpectError::check_error(&t, [&]{ fplain >> key(s); }); + s = "asd"; ExpectError::check_error(&t, [&]{ bplain >> key(s); }); + s = "asd"; fenull >> key(s); EXPECT_EQ(s, "null"); + s = "asd"; benull >> key(s); EXPECT_EQ(s, "null"); + s = "asd"; ftilde >> key(s); EXPECT_EQ(s, "~"); + s = "asd"; btilde >> key(s); EXPECT_EQ(s, "~"); + // check error also for integral and float types + ExpectError::check_error(&t, [&]{ int k = 0; fplain >> key(k); }); + ExpectError::check_error(&t, [&]{ int k = 0; bplain >> key(k); }); + ExpectError::check_error(&t, [&]{ float k = 0; fplain >> key(k); }); + ExpectError::check_error(&t, [&]{ float k = 0; bplain >> key(k); }); +} + + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- @@ -301,8 +615,8 @@ R"( - null: ~ )", N(SB, L{ -N(VP, nullptr), -N(VP, nullptr), +N(VN, nullptr), +N(VN, nullptr), N(VP, "null"), N(VP, "Null"), N(VP, "NULL"), @@ -319,7 +633,7 @@ N(MB, L{N(KP|VP, "null", "~")}), ADD_CASE_TO_GROUP("null map vals, expl", R"({foo: , bar: , baz: } )", -N(MFS, L{N(KP|VP, "foo", nullptr), N(KP|VP, "bar", nullptr), N(KP|VP, "baz", nullptr)}) +N(MFS, L{N(KP|VN, "foo", nullptr), N(KP|VN, "bar", nullptr), N(KP|VN, "baz", nullptr)}) ); ADD_CASE_TO_GROUP("null map vals, impl", @@ -328,7 +642,7 @@ R"( bar: baz: )", -N(MB, L{N(KP|VP, "foo", nullptr), N(KP|VP, "bar", nullptr), N(KP|VP, "baz", nullptr)}) +N(MB, L{N(KP|VN, "foo", nullptr), N(KP|VN, "bar", nullptr), N(KP|VN, "baz", nullptr)}) ); ADD_CASE_TO_GROUP("null seq vals, impl", @@ -336,7 +650,7 @@ R"(- - - )", -N(SB, L{N(VP, nullptr), N(VP, nullptr), N(VP, nullptr)}) +N(SB, L{N(VN, nullptr), N(VN, nullptr), N(VN, nullptr)}) ); ADD_CASE_TO_GROUP("null seq vals in map, impl, mixed 1", @@ -348,7 +662,7 @@ R"( bar: baz: )", -N(MB, L{N(KP|SB, "foo", L{N(VP, nullptr), N(VP, nullptr), N(VP, nullptr)}), N(KP|VP, "bar", nullptr), N(KP|VP, "baz", nullptr)}) +N(MB, L{N(KP|SB, "foo", L{VN, VN, VN}), N(KP|VN, "bar", nullptr), N(KP|VN, "baz", nullptr)}) ); ADD_CASE_TO_GROUP("null seq vals in map, impl, mixed 2", @@ -360,7 +674,7 @@ R"( - baz: )", -N(MB, L{N(KP|VP, "foo", nullptr), N(KP|SB, "bar", L{N(VP, nullptr), N(VP, nullptr), N(VP, nullptr)}), N(KP|VP, "baz", nullptr)}) +N(MB, L{N(KP|VN, "foo", nullptr), N(KP|SB, "bar", L{VN, VN, VN}), N(KP|VN, "baz", nullptr)}) ); ADD_CASE_TO_GROUP("null seq vals in map, impl, mixed 3", @@ -372,7 +686,7 @@ R"( - - )", -N(MB, L{N(KP|VP, "foo", nullptr), N(KP|VP, "bar", nullptr), N(KP|SB, "baz", L{N(VP, nullptr), N(VP, nullptr), N(VP, nullptr)})}) +N(MB, L{N(KP|VN, "foo", nullptr), N(KP|VN, "bar", nullptr), N(KP|SB, "baz", L{VN, VN, VN})}) ); ADD_CASE_TO_GROUP("null map vals in seq, impl, mixed 1", @@ -385,12 +699,12 @@ R"( )", N(SB, L{ N(MB, L{ - N(KP|VP, "foo", nullptr), - N(KP|VP, "bar", nullptr), - N(KP|VP, "baz", nullptr) + N(KP|VN, "foo", nullptr), + N(KP|VN, "bar", nullptr), + N(KP|VN, "baz", nullptr) }), - N(VP, nullptr), - N(VP, nullptr) + VN, + VN }) ); @@ -403,13 +717,13 @@ R"( - )", N(SB, L{ - N(VP, nullptr), + VN, N(MB, L{ - N(KP|VP, "foo", nullptr), - N(KP|VP, "bar", nullptr), - N(KP|VP, "baz", nullptr) + N(KP|VN, "foo", nullptr), + N(KP|VN, "bar", nullptr), + N(KP|VN, "baz", nullptr) }), - N(VP, nullptr) + VN }) ); @@ -422,12 +736,12 @@ R"( baz: )", N(SB, L{ - N(VP, nullptr), - N(VP, nullptr), + VN, + VN, N(MB, L{ - N(KP|VP, "foo", nullptr), - N(KP|VP, "bar", nullptr), - N(KP|VP, "baz", nullptr) + N(KP|VN, "foo", nullptr), + N(KP|VN, "bar", nullptr), + N(KP|VN, "baz", nullptr) }), }) ); @@ -443,7 +757,7 @@ your case: whatever: baz )", N(MB, L{ - N(KP|MB, "fixed case", L{N(KP|VP, "foo", "a"), N(KP|VP, "bar", nullptr)}), + N(KP|MB, "fixed case", L{N(KP|VP, "foo", "a"), N(KP|VN, "bar", nullptr)}), N(KP|MB, "your case", L{N(KP|VP, "foo", "a"), N(KP|VS, "bar", "")}), N(KP|VP, "whatever", "baz"), }) @@ -481,7 +795,7 @@ N(MB, L{ N(KP|VP, "IsLifeInfinite", "false"), N(KP|VP, "ElectricalDischarge", "1.0"), N(KP|VP, "IsBurnOutBorn", "false"), - N(KP|VP, "BurnOutBornName", nullptr), + N(KP|VN, "BurnOutBornName", nullptr), N(KP|VP, "IsBurnOutBornIdent", "false"), N(KP|VS, "ChangeDropTableName", ""), }), @@ -509,7 +823,7 @@ N(MB, L{ N(KP|MB, "objects", L{ N(KP|MB, "TestContent", L{ N(KP|VS, "Str64_empty", ""), - N(KP|VP, "Str64_empty2", nullptr), + N(KP|VN, "Str64_empty2", nullptr), N(KP|VS, "Str64_empty3", ""), }), }), diff --git a/test/test_scalar_plain.cpp b/test/test_scalar_plain.cpp index 5f78b70c..2880652a 100644 --- a/test/test_scalar_plain.cpp +++ b/test/test_scalar_plain.cpp @@ -881,11 +881,11 @@ k:#foo N(STREAM, L{ N(DOC|MB, L{ N(KP|VP, "a", "1"), - N(KP|VP, "b", {}), + N(KP|VN, "b", {}), N(KP|VP, "c", AR(KEYANCH, "anchor"), "3"), - N(KP|VP, "d", {}), + N(KP|VN, "d", {}), N(KP|VP, TS("!!str", "e"), "4"), - N(KP|VP, "f", {}), + N(KP|VN, "f", {}), }), N(DOC|VP, "k:#foo &a !t s"), diff --git a/test/test_seq.cpp b/test/test_seq.cpp index aa35b5fc..3f1e6441 100644 --- a/test/test_seq.cpp +++ b/test/test_seq.cpp @@ -338,7 +338,7 @@ R"(- - # with comments - # more comments )", -N(SB, L{N{VP, ""}, N{VP, ""}, N{VP, ""}, N{VP, ""}, N{VP, ""}, N{VP, ""}}) +N(SB, L{VN, VN, VN, VN, VN, VN}) ); ADD_CASE_TO_GROUP("simple seq, empty elements with non-empty first", @@ -350,7 +350,7 @@ R"( - # with comments - # more comments )", -N(SB, L{N{VP, "non-empty"}, N{VP, ""}, N{VP, ""}, N{VP, ""}, N{VP, ""}, N{VP, ""}}) +N(SB, L{N{VP, "non-empty"}, VN, VN, VN, VN, VN}) ); ADD_CASE_TO_GROUP("simple seq, scalars with special chars, comma", @@ -602,9 +602,8 @@ N(SFS, L{ N(VP, ":^"), N(VP, ":$"), N(VP, ":`"), - N(MFS, L{N(KP|VP, "", "")}), - _RYML_WITH_OR_WITHOUT_TAB_TOKENS(N(MFS, L{N(KP|VP, "", "")}), - N(VP, ":\t")), + N(MFS, L{KN|VN}), + _RYML_WITH_OR_WITHOUT_TAB_TOKENS(N(MFS, L{KN|VN}), N(VP, ":\t")), N(VP, "x:a"), N(VP, "x:0"), N(VP, "x::"), @@ -615,9 +614,8 @@ N(SFS, L{ N(VP, "x:^"), N(VP, "x:$"), N(VP, "x:`"), - N(MFS, L{N(KP|VP, "x", "")}), - _RYML_WITH_OR_WITHOUT_TAB_TOKENS(N(MFS, L{N(KP|VP, "x", "")}), - N(VP, "x:\t")), + N(MFS, L{N(KP|VN, "x", "")}), + _RYML_WITH_OR_WITHOUT_TAB_TOKENS(N(MFS, L{N(KP|VN, "x", "")}), N(VP, "x:\t")), N(VP, ":z:a"), N(VP, ":z:0"), N(VP, ":z::"), @@ -628,9 +626,8 @@ N(SFS, L{ N(VP, ":z:^"), N(VP, ":z:$"), N(VP, ":z:`"), - N(MFS, L{N(KP|VP, ":z", "")}), - _RYML_WITH_OR_WITHOUT_TAB_TOKENS(N(MFS, L{N(KP|VP, ":z", "")}), - N(VP, ":z:\t")), + N(MFS, L{N(KP|VN, ":z", "")}), + _RYML_WITH_OR_WITHOUT_TAB_TOKENS(N(MFS, L{N(KP|VN, ":z", "")}), N(VP, ":z:\t")), }) ); @@ -675,7 +672,7 @@ R"(- :a N(SB, L{ N(VP, ":a"), N(VP, ":0"), - N(MB, L{N(KP|VP, ":", {})}), + N(MB, L{N(KP|VN, ":", {})}), N(VP, ":-"), N(VP, ":*"), N(VP, ":@"), @@ -683,12 +680,12 @@ N(SB, L{ N(VP, ":^"), N(VP, ":$"), N(VP, ":`"), - N(MB, L{N(KP|VP, "", "")}), -// _RYML_WITH_OR_WITHOUT_TAB_TOKENS(N(MB, L{N(KP|VP, "", "")}), + N(MB, L{KN|VN}), +// _RYML_WITH_OR_WITHOUT_TAB_TOKENS(N(MB, L{KN|VN}), // N(VP, ":")), N(VP, "x:a"), N(VP, "x:0"), - N(MB, L{N(KP|VP, "x:", {})}), + N(MB, L{N(KP|VN, "x:", {})}), N(VP, "x:-"), N(VP, "x:*"), N(VP, "x:@"), @@ -696,12 +693,12 @@ N(SB, L{ N(VP, "x:^"), N(VP, "x:$"), N(VP, "x:`"), - N(MB, L{N(KP|VP, "x", "")}), -// _RYML_WITH_OR_WITHOUT_TAB_TOKENS(N(MB, L{N(KP|VP, "x", "")}), + N(MB, L{N(KP|VN, "x", {})}), +// _RYML_WITH_OR_WITHOUT_TAB_TOKENS(N(MB, L{N(KP|VN, "x", "")}), // N(VP, "x:")), N(VP, ":z:a"), N(VP, ":z:0"), - N(MB, L{N(KP|VP, ":z:", {})}), + N(MB, L{N(KP|VN, ":z:", {})}), N(VP, ":z:-"), N(VP, ":z:*"), N(VP, ":z:@"), @@ -709,8 +706,8 @@ N(SB, L{ N(VP, ":z:^"), N(VP, ":z:$"), N(VP, ":z:`"), - N(MB, L{N(KP|VP, ":z", "")}), -// _RYML_WITH_OR_WITHOUT_TAB_TOKENS(N(MB, L{N(KP|VP, ":z", "")}), + N(MB, L{N(KP|VN, ":z", "")}), +// _RYML_WITH_OR_WITHOUT_TAB_TOKENS(N(MB, L{N(KP|VN, ":z", "")}), // N(VP, ":z:")), }) ); diff --git a/test/test_seq_of_map.cpp b/test/test_seq_of_map.cpp index b0dcb87d..adcc84ed 100644 --- a/test/test_seq_of_map.cpp +++ b/test/test_seq_of_map.cpp @@ -301,9 +301,9 @@ c : [ : ]})", N(MFS, L{ - N(KP|SFS, "a", L{N(MFS, L{N(KP|VP, "", "foo")}),}), - N(KP|SFS, "b", L{N(MFS, L{N(KP|VP, "", "foo")}),}), - N(KP|SFS, "c", L{N(MFS, L{N(KP|VP, "", {})}), N(MFS, L{N(KP|VP, "", {})}),}), + N(KP|SFS, "a", L{N(MFS, L{N(KN|VP, "", "foo")}),}), + N(KP|SFS, "b", L{N(MFS, L{N(KN|VP, "", "foo")}),}), + N(KP|SFS, "c", L{N(MFS, L{N(KN|VN, "", {})}), N(MFS, L{N(KN|VN, "", {})}),}), }) ); diff --git a/test/test_suite/test_suite_event_handler.hpp b/test/test_suite/test_suite_event_handler.hpp index ad5f7e9e..97836e0e 100644 --- a/test/test_suite/test_suite_event_handler.hpp +++ b/test/test_suite/test_suite_event_handler.hpp @@ -393,6 +393,19 @@ struct EventHandlerYamlStd : public EventHandlerStacknode_id); + _send_key_scalar_({}, ':'); + _enable_(KEY|KEY_PLAIN|KEYNIL); + } + C4_ALWAYS_INLINE void set_val_scalar_plain_empty() noexcept + { + _c4dbgpf("node[{}]: set val scalar plain as empty", m_curr->node_id); + _send_val_scalar_({}, ':'); + _enable_(VAL|VAL_PLAIN|VALNIL); + } + C4_ALWAYS_INLINE void set_key_scalar_plain(csubstr scalar) { _c4dbgpf("node[{}]: set key scalar plain: [{}]~~~{}~~~ ({})", m_curr->node_id, scalar.len, scalar, reinterpret_cast(scalar.str)); diff --git a/test/test_suite/test_suite_events.cpp b/test/test_suite/test_suite_events.cpp index 881fab34..7fc2f88b 100644 --- a/test/test_suite/test_suite_events.cpp +++ b/test/test_suite/test_suite_events.cpp @@ -127,10 +127,9 @@ struct Scalar #ifdef RYML_DBG char buf1[128]; char buf2[128]; - char buf3[128]; #endif - _nfo_logf("node[{}]: set key flags: {}: {}->{}", node, flags.type_str(buf1), flags.type_str(buf2), flags.type_str(buf3)); - tree->_add_flags(node, flags & KEY_STYLE); + _nfo_logf("node[{}]: set key flags: {}<-{}", node, NodeType(flags & (KEY_STYLE|KEYNIL)).type_str(buf1), flags.type_str(buf2)); + tree->_add_flags(node, flags & (KEY_STYLE|KEYNIL)); } } void add_val_props(Tree *tree, id_type node) const @@ -156,10 +155,9 @@ struct Scalar #ifdef RYML_DBG char buf1[128]; char buf2[128]; - char buf3[128]; #endif - _nfo_logf("node[{}]: set val flags: {}: {}->{}", node, flags.type_str(buf1), flags.type_str(buf2), flags.type_str(buf3)); - tree->_add_flags(node, flags & VAL_STYLE); + _nfo_logf("node[{}]: set val flags: {}<-{}", node, NodeType(flags & (VAL_STYLE|VALNIL)).type_str(buf1), flags.type_str(buf2)); + tree->_add_flags(node, flags & (VAL_STYLE|VALNIL)); } } csubstr filtered_scalar(Tree *tree) const @@ -353,6 +351,8 @@ void parse_events_to_tree(csubstr src, Tree *C4_RESTRICT tree_) ASSERT_TRUE(line.begins_with(':')); curr.scalar = line.sub(1); curr.flags = SCALAR_PLAIN; + if(!line.sub(1).len) + curr.flags |= KEYNIL|VALNIL; } _nfo_logf("parsed scalar: '{}'", curr.scalar.maybe_get()); if(m_stack.empty()) diff --git a/test/test_tag_property.cpp b/test/test_tag_property.cpp index 27800d47..ca1a030c 100644 --- a/test/test_tag_property.cpp +++ b/test/test_tag_property.cpp @@ -1300,7 +1300,7 @@ R"( ? b )", N(STREAM, L{ - N(DOC|MB, TL("!!set", L{N(KP|VP, "a", /*"~"*/{}), N(KP|VP, "b", /*"~"*/{})})), + N(DOC|MB, TL("!!set", L{N(KP|VN, "a", /*"~"*/{}), N(KP|VN, "b", /*"~"*/{})})), }) ); @@ -1311,7 +1311,7 @@ R"( ? b )", N(STREAM, L{ - N(DOC|MB, TL("!!set", L{N(KP|VP, "a", /*"~"*/{}), N(KP|VP, "b", /*"~"*/{})})), + N(DOC|MB, TL("!!set", L{N(KP|VN, "a", /*"~"*/{}), N(KP|VN, "b", /*"~"*/{})})), }) ); diff --git a/test/test_tree.cpp b/test/test_tree.cpp index 1077f934..8e08cca9 100644 --- a/test/test_tree.cpp +++ b/test/test_tree.cpp @@ -1922,6 +1922,131 @@ TEST(NodeType, has_key) EXPECT_TRUE(NodeType(KEYSEQ).has_key()); } +TEST(Tree, key_val_is_null) +{ + Tree t = parse_in_arena(R"( +map: {foo: bar, : knil, vnil: } +seq: [foo, bar] +: knil +vnil: +squoted: '' +dquoted: "" +literal: | +folded: > +)"); + const NodeRef mroot = t.rootref(); + const NodeRef mmap = t["map"]; + const NodeRef mfoo = t["map"]["foo"]; + const NodeRef mfooknil = t["map"][1]; + const NodeRef mfoovnil = t["map"]["vnil"]; + const NodeRef mseq = t["seq"]; + const NodeRef mknil = t[2]; + const NodeRef mvnil = t["vnil"]; + const NodeRef msquo = t["squoted"]; + const NodeRef mdquo = t["dquoted"]; + const NodeRef mliteral = t["literal"]; + const NodeRef mfolded = t["folded"]; + const ConstNodeRef root = mroot; + const ConstNodeRef map = mmap; + const ConstNodeRef foo = mfoo; + const ConstNodeRef fooknil = mfooknil; + const ConstNodeRef foovnil = mfoovnil; + const ConstNodeRef seq = mseq; + const ConstNodeRef knil = mknil; + const ConstNodeRef vnil = mvnil; + const ConstNodeRef squo = msquo; + const ConstNodeRef dquo = mdquo; + const ConstNodeRef literal = mliteral; + const ConstNodeRef folded = mfolded; + const size_t root_id = root.id(); + const size_t map_id = map.id(); + const size_t foo_id = foo.id(); + const size_t fooknil_id = fooknil.id(); + const size_t foovnil_id = foovnil.id(); + const size_t seq_id = seq.id(); + const size_t knil_id = knil.id(); + const size_t vnil_id = vnil.id(); + const size_t squo_id = squo.id(); + const size_t dquo_id = dquo.id(); + const size_t literal_id = literal.id(); + const size_t folded_id = folded.id(); + // + verify_assertion(t, [&](Tree const&){ return t.key_is_null(root_id); }); + verify_assertion(t, [&](Tree const&){ return t.val_is_null(root_id); }); + EXPECT_FALSE(t.key_is_null(map_id)); + verify_assertion(t, [&](Tree const&){ return t.val_is_null(map_id); }); + EXPECT_FALSE(t.key_is_null(foo_id)); + EXPECT_FALSE(t.val_is_null(foo_id)); + EXPECT_TRUE (t.key_is_null(fooknil_id)); + EXPECT_FALSE(t.val_is_null(fooknil_id)); + EXPECT_FALSE(t.key_is_null(foovnil_id)); + EXPECT_TRUE (t.val_is_null(foovnil_id)); + EXPECT_FALSE(t.key_is_null(seq_id)); + verify_assertion(t, [&](Tree const&){ return t.val_is_null(seq_id); }); + EXPECT_TRUE (t.key_is_null(knil_id)); + EXPECT_FALSE(t.val_is_null(knil_id)); + EXPECT_FALSE(t.key_is_null(vnil_id)); + EXPECT_TRUE (t.val_is_null(vnil_id)); + EXPECT_FALSE(t.key_is_null(squo_id)); + EXPECT_FALSE(t.val_is_null(squo_id)); + EXPECT_FALSE(t.key_is_null(dquo_id)); + EXPECT_FALSE(t.val_is_null(dquo_id)); + EXPECT_FALSE(t.key_is_null(literal_id)); + EXPECT_FALSE(t.val_is_null(literal_id)); + EXPECT_FALSE(t.key_is_null(folded_id)); + EXPECT_FALSE(t.val_is_null(folded_id)); + // + verify_assertion(t, [&](Tree const&){ return root.key_is_null(); }); + verify_assertion(t, [&](Tree const&){ return root.val_is_null(); }); + EXPECT_FALSE(map.key_is_null()); + verify_assertion(t, [&](Tree const&){ return map.val_is_null(); }); + EXPECT_FALSE(foo.key_is_null()); + EXPECT_FALSE(foo.val_is_null()); + EXPECT_TRUE (fooknil.key_is_null()); + EXPECT_FALSE(fooknil.val_is_null()); + EXPECT_FALSE(foovnil.key_is_null()); + EXPECT_TRUE (foovnil.val_is_null()); + EXPECT_FALSE(seq.key_is_null()); + verify_assertion(t, [&](Tree const&){ seq.val_is_null(); }); + EXPECT_TRUE (knil.key_is_null()); + EXPECT_FALSE(knil.val_is_null()); + EXPECT_FALSE(vnil.key_is_null()); + EXPECT_TRUE (vnil.val_is_null()); + EXPECT_FALSE(squo.key_is_null()); + EXPECT_FALSE(squo.val_is_null()); + EXPECT_FALSE(dquo.key_is_null()); + EXPECT_FALSE(dquo.val_is_null()); + EXPECT_FALSE(literal.key_is_null()); + EXPECT_FALSE(literal.val_is_null()); + EXPECT_FALSE(folded.key_is_null()); + EXPECT_FALSE(folded.val_is_null()); + // + verify_assertion(t, [&](Tree const&){ return mroot.key_is_null(); }); + verify_assertion(t, [&](Tree const&){ return mroot.val_is_null(); }); + EXPECT_FALSE(mmap.key_is_null()); + verify_assertion(t, [&](Tree const&){ return mmap.val_is_null(); }); + EXPECT_FALSE(mfoo.key_is_null()); + EXPECT_FALSE(mfoo.val_is_null()); + EXPECT_TRUE (mfooknil.key_is_null()); + EXPECT_FALSE(mfooknil.val_is_null()); + EXPECT_FALSE(mfoovnil.key_is_null()); + EXPECT_TRUE (mfoovnil.val_is_null()); + EXPECT_FALSE(mseq.key_is_null()); + verify_assertion(t, [&](Tree const&){ mseq.val_is_null(); }); + EXPECT_TRUE (mknil.key_is_null()); + EXPECT_FALSE(mknil.val_is_null()); + EXPECT_FALSE(mvnil.key_is_null()); + EXPECT_TRUE (mvnil.val_is_null()); + EXPECT_FALSE(msquo.key_is_null()); + EXPECT_FALSE(msquo.val_is_null()); + EXPECT_FALSE(mdquo.key_is_null()); + EXPECT_FALSE(mdquo.val_is_null()); + EXPECT_FALSE(mliteral.key_is_null()); + EXPECT_FALSE(mliteral.val_is_null()); + EXPECT_FALSE(mfolded.key_is_null()); + EXPECT_FALSE(mfolded.val_is_null()); +} + TEST(Tree, has_key) { Tree t = parse_in_arena(R"(---