diff --git a/changelog/current.md b/changelog/current.md index 84aabb08..632052ee 100644 --- a/changelog/current.md +++ b/changelog/current.md @@ -1,4 +1,4 @@ -Most of the changes are from the giant Parser refactor described below. Before getting to that, a couple of other points first. +Most of the changes are from the giant Parser refactor described below. Before getting to that, a couple of other minor changes first. ### Fixes @@ -9,7 +9,16 @@ Most of the changes are from the giant Parser refactor described below. Before g ### New features -- [#PR431](https://github.com/biojppm/rapidyaml/pull/431) - append-emitting to the `emitrs_` functions, suggested in [#345](https://github.com/biojppm/rapidyaml/issues/345). This was achieved by adding a `bool append=false` as the last parameter of these functions. +- [#PR431](https://github.com/biojppm/rapidyaml/pull/431) - append-emitting to existing containers in the `emitrs_` functions, suggested in [#345](https://github.com/biojppm/rapidyaml/issues/345). This was achieved by adding a `bool append=false` as the last parameter of these functions. +- [#PR431](https://github.com/biojppm/rapidyaml/pull/431) - add depth query methods: + ```cpp + Tree::depth_asc(id_type) const; // O(log(num_tree_nodes)) get the depth of a node ascending (ie, from root to node) + Tree::depth_desc(id_type) const; // O(num_tree_nodes) get the depth of a node descending (ie, from node to deep-most leaf node) + ConstNodeRef::depth_asc() const; // likewise + ConstNodeRef::depth_desc() const; + NodeRef::depth_asc() const; + NodeRef::depth_desc() const; + ``` ------ @@ -21,7 +30,7 @@ The parser was completely refactored ([#PR414](https://github.com/biojppm/rapidy - The new parser is an event-based parser, based on an event dispatcher engine. This engine is templated on event handler, where each event is a function call, which spares branches on the event handler. The parsing code was fully rewritten, and is now much more simple (albeit longer), and much easier to work with and fix. - YAML standard-conformance was improved significantly. Along with many smaller fixes and additions, (too many to list here), the main changes are the following: - - The parser engine can now successfully parse container keys, emitting all the events in the correct , **but** as before, the ryml tree cannot accomodate these (and this constraint is no longer enforced by the parser, but instead by `EventHandlerTree`). For an example of a handler which can accomodate key containers, see the one which is used for the test suite at `test/test_suite/test_suite_event_handler.hpp` + - The parser engine can now successfully parse container keys, emitting all the events in correctly, **but** as before, the ryml tree cannot accomodate these (and this constraint is no longer enforced by the parser, but instead by `EventHandlerTree`). For an example of a handler which can accomodate key containers, see the one which is used for the test suite at `test/test_suite/test_suite_event_handler.hpp` - Anchor keys can now be terminated with colon (eg, `&anchor: key: val`), as dictated by the standard. - The parser engine can now be used to create native trees in other programming languages, or in cases where the user *must* have container keys. - Performance of both parsing and emitting improved significantly; see some figures below. diff --git a/src/c4/yml/emit.def.hpp b/src/c4/yml/emit.def.hpp index 13a46805..efbaa351 100644 --- a/src/c4/yml/emit.def.hpp +++ b/src/c4/yml/emit.def.hpp @@ -29,7 +29,7 @@ substr Emitter::emit_as(EmitType_e type, Tree const& t, id_type id, bool if(type == EMIT_YAML) _emit_yaml(id); else if(type == EMIT_JSON) - _do_visit_json(id); + _do_visit_json(id, 0); else _RYML_CB_ERR(m_tree->callbacks(), "unknown emit type"); m_tree = nullptr; @@ -67,7 +67,7 @@ void Emitter::_emit_yaml(id_type id) this->Writer::_do_write(":\n"); ++ilevel; } - _do_visit_block_container(id, ilevel, ilevel); + _do_visit_block_container(id, 0, ilevel, ilevel); return; } } @@ -241,14 +241,14 @@ void Emitter::_write_doc(id_type id) } template -void Emitter::_do_visit_flow_sl(id_type node, id_type ilevel) +void Emitter::_do_visit_flow_sl(id_type node, id_type depth, id_type ilevel) { const bool prev_flow = m_flow; m_flow = true; RYML_ASSERT(!m_tree->is_stream(node)); RYML_ASSERT(m_tree->is_container(node) || m_tree->is_doc(node)); RYML_ASSERT(m_tree->is_root(node) || (m_tree->parent_is_map(node) || m_tree->parent_is_seq(node))); - if(C4_UNLIKELY(ilevel > m_opts.max_depth())) + if(C4_UNLIKELY(depth > m_opts.max_depth())) _RYML_CB_ERR(m_tree->callbacks(), "max depth exceeded"); if(m_tree->is_doc(node)) @@ -332,7 +332,7 @@ void Emitter::_do_visit_flow_sl(id_type node, id_type ilevel) else { // with single-line flow, we can never go back to block - _do_visit_flow_sl(child, ilevel + 1); + _do_visit_flow_sl(child, depth + 1, ilevel + 1); } } @@ -350,21 +350,25 @@ void Emitter::_do_visit_flow_sl(id_type node, id_type ilevel) C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4702) // unreachable error, triggered by flow_ml not implemented template -void Emitter::_do_visit_flow_ml(id_type id, id_type ilevel, id_type do_indent) +void Emitter::_do_visit_flow_ml(id_type id, id_type depth, id_type ilevel, id_type do_indent) { C4_UNUSED(id); + C4_UNUSED(depth); C4_UNUSED(ilevel); C4_UNUSED(do_indent); - if(C4_UNLIKELY(ilevel > m_opts.max_depth())) + c4::yml::error("not implemented"); + #ifdef THIS_IS_A_WORK_IN_PROGRESS + if(C4_UNLIKELY(depth > m_opts.max_depth())) _RYML_CB_ERR(m_tree->callbacks(), "max depth exceeded"); const bool prev_flow = m_flow; m_flow = true; - c4::yml::error("not implemented"); + // do it... m_flow = prev_flow; + #endif } template -void Emitter::_do_visit_block_container(id_type node, id_type level, bool do_indent) +void Emitter::_do_visit_block_container(id_type node, id_type depth, id_type level, bool do_indent) { if(m_tree->is_seq(node)) { @@ -386,19 +390,19 @@ void Emitter::_do_visit_block_container(id_type node, id_type level, boo { _indent(level, do_indent); this->Writer::_do_write("- "); - _do_visit_flow_sl(child, 0u); + _do_visit_flow_sl(child, depth+1, 0u); this->Writer::_do_write('\n'); } else if(ty.is_flow_ml()) { _indent(level, do_indent); this->Writer::_do_write("- "); - _do_visit_flow_ml(child, 0u, do_indent); + _do_visit_flow_ml(child, depth+1, 0u, do_indent); this->Writer::_do_write('\n'); } else { - _do_visit_block(child, level, do_indent); // same level + _do_visit_block(child, depth+1, level, do_indent); // same indentation level } } do_indent = true; @@ -425,18 +429,18 @@ void Emitter::_do_visit_block_container(id_type node, id_type level, boo if(ty.is_flow_sl()) { _indent(level, do_indent); - _do_visit_flow_sl(ich, 0u); + _do_visit_flow_sl(ich, depth+1, 0u); this->Writer::_do_write('\n'); } else if(ty.is_flow_ml()) { _indent(level, do_indent); - _do_visit_flow_ml(ich, 0u); + _do_visit_flow_ml(ich, depth+1, 0u); this->Writer::_do_write('\n'); } else { - _do_visit_block(ich, level, do_indent); // same level! + _do_visit_block(ich, depth+1, level, do_indent); // same level! } } // keyval vs container do_indent = true; @@ -445,12 +449,12 @@ void Emitter::_do_visit_block_container(id_type node, id_type level, boo } template -void Emitter::_do_visit_block(id_type node, id_type ilevel, id_type do_indent) +void Emitter::_do_visit_block(id_type node, id_type depth, id_type ilevel, id_type do_indent) { RYML_ASSERT(!m_tree->is_stream(node)); RYML_ASSERT(m_tree->is_container(node) || m_tree->is_doc(node)); RYML_ASSERT(m_tree->is_root(node) || (m_tree->parent_is_map(node) || m_tree->parent_is_seq(node))); - if(C4_UNLIKELY(ilevel > m_opts.max_depth())) + if(C4_UNLIKELY(depth > m_opts.max_depth())) _RYML_CB_ERR(m_tree->callbacks(), "max depth exceeded"); if(m_tree->is_doc(node)) { @@ -528,16 +532,18 @@ void Emitter::_do_visit_block(id_type node, id_type ilevel, id_type do_i if(m_tree->is_root(node) || m_tree->is_doc(node)) next_level = ilevel; // do not indent at top level - _do_visit_block_container(node, next_level, do_indent); + _do_visit_block_container(node, depth, next_level, do_indent); } C4_SUPPRESS_WARNING_MSVC_POP template -void Emitter::_do_visit_json(id_type id) +void Emitter::_do_visit_json(id_type id, id_type depth) { _RYML_CB_CHECK(m_tree->callbacks(), !m_tree->is_stream(id)); // JSON does not have streams + if(C4_UNLIKELY(depth > m_opts.max_depth())) + _RYML_CB_ERR(m_tree->callbacks(), "max depth exceeded"); if(m_tree->is_keyval(id)) { _writek_json(id); @@ -565,7 +571,7 @@ void Emitter::_do_visit_json(id_type id) { if(ich != m_tree->first_child(id)) this->Writer::_do_write(','); - _do_visit_json(ich); + _do_visit_json(ich, depth+1); } if(m_tree->is_seq(id)) diff --git a/src/c4/yml/emit.hpp b/src/c4/yml/emit.hpp index df271f1e..8c788b61 100644 --- a/src/c4/yml/emit.hpp +++ b/src/c4/yml/emit.hpp @@ -51,6 +51,15 @@ using EmitterOStream = Emitter>; using EmitterFile = Emitter; using EmitterBuf = Emitter; +namespace detail { +inline bool is_set_(ConstNodeRef n) { return n.tree() && (n.id() != NONE); } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + /** Specifies the type of content to emit */ typedef enum { EMIT_YAML = 0, ///< emit YAML @@ -134,6 +143,8 @@ class Emitter : public Writer /** emit starting at the given node */ substr emit_as(EmitType_e type, ConstNodeRef const& n, bool error_on_excess=true) { + if(!detail::is_set_(n)) + return {}; _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); return this->emit_as(type, *n.tree(), n.id(), error_on_excess); } @@ -157,11 +168,11 @@ class Emitter : public Writer private: void _emit_yaml(id_type id); - void _do_visit_flow_sl(id_type id, id_type ilevel=0); - void _do_visit_flow_ml(id_type id, id_type ilevel=0, id_type do_indent=1); - void _do_visit_block(id_type id, id_type ilevel=0, id_type do_indent=1); - void _do_visit_block_container(id_type id, id_type next_level, bool do_indent); - void _do_visit_json(id_type id); + void _do_visit_flow_sl(id_type id, id_type depth, id_type ilevel=0); + void _do_visit_flow_ml(id_type id, id_type depth, id_type ilevel=0, id_type do_indent=1); + void _do_visit_block(id_type id, id_type depth, id_type ilevel=0, id_type do_indent=1); + void _do_visit_block_container(id_type id, id_type depth, id_type next_level, bool do_indent); + void _do_visit_json(id_type id, id_type depth); private: @@ -288,12 +299,16 @@ inline size_t emit_json(Tree const& t, FILE *f=nullptr) * Return the number of bytes written. */ inline size_t emit_yaml(ConstNodeRef const& r, EmitOptions const& opts, FILE *f=nullptr) { + if(!detail::is_set_(r)) + return {}; EmitterFile em(opts, f); return em.emit_as(EMIT_YAML, r, /*error_on_excess*/true).len; } /** (2) like (1), but use default emit options */ inline size_t emit_yaml(ConstNodeRef const& r, FILE *f=nullptr) { + if(!detail::is_set_(r)) + return {}; EmitterFile em(f); return em.emit_as(EMIT_YAML, r, /*error_on_excess*/true).len; } @@ -301,12 +316,16 @@ inline size_t emit_yaml(ConstNodeRef const& r, FILE *f=nullptr) * Return the number of bytes written. */ inline size_t emit_json(ConstNodeRef const& r, EmitOptions const& opts, FILE *f=nullptr) { + if(!detail::is_set_(r)) + return {}; EmitterFile em(opts, f); return em.emit_as(EMIT_JSON, r, /*error_on_excess*/true).len; } /** (2) like (1), but use default emit options */ inline size_t emit_json(ConstNodeRef const& r, FILE *f=nullptr) { + if(!detail::is_set_(r)) + return {}; EmitterFile em(f); return em.emit_as(EMIT_JSON, r, /*error_on_excess*/true).len; } @@ -335,6 +354,8 @@ inline OStream& operator<< (OStream& s, Tree const& t) template inline OStream& operator<< (OStream& s, ConstNodeRef const& n) { + if(!detail::is_set_(n)) + return s; EmitterOStream em(s); em.emit_as(EMIT_YAML, n); return s; @@ -361,7 +382,8 @@ struct as_json as_json(ConstNodeRef const& n, EmitOptions const& opts={}) : tree(n.tree()), node(n.id()), options(opts) {} }; -/** mark a tree or node to be emitted as yaml when using @ref operator<< . For example: +/** mark a tree or node to be emitted as yaml when using @ref + * operator<< . For example: * * ```cpp * Tree t = parse_in_arena("{foo: bar}"); @@ -385,6 +407,8 @@ struct as_yaml template inline OStream& operator<< (OStream& s, as_json const& j) { + if(!j.tree || j.node == NONE) + return s; EmitterOStream em(j.options, s); em.emit_as(EMIT_JSON, *j.tree, j.node, true); return s; @@ -394,6 +418,8 @@ inline OStream& operator<< (OStream& s, as_json const& j) template inline OStream& operator<< (OStream& s, as_yaml const& y) { + if(!y.tree || y.node == NONE) + return s; EmitterOStream em(y.options, s); em.emit_as(EMIT_YAML, *y.tree, y.node, true); return s; @@ -414,13 +440,13 @@ inline OStream& operator<< (OStream& s, as_yaml const& y) /** (1) emit YAML to the given buffer. Return a substr trimmed to the emitted YAML. * @param t the tree to emit. * @param id the node where to start emitting. - * @param buf the output buffer. * @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 * insufficient (when error_on_excess is false), the string pointer of the * result will be set to null, and the length will report the required buffer size. */ -inline substr emit_yaml(Tree const& t, id_type id, substr buf, EmitOptions const& opts, bool error_on_excess=true) +inline substr emit_yaml(Tree const& t, id_type id, EmitOptions const& opts, substr buf, bool error_on_excess=true) { EmitterBuf em(opts, buf); return em.emit_as(EMIT_YAML, t, id, error_on_excess); @@ -434,13 +460,13 @@ inline substr emit_yaml(Tree const& t, id_type id, substr buf, bool error_on_exc /** (1) emit JSON to the given buffer. Return a substr trimmed to the emitted JSON. * @param t the tree to emit. * @param id the node where to start emitting. - * @param buf the output buffer. * @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 * insufficient (when error_on_excess is false), the string pointer of the * result will be set to null, and the length will report the required buffer size. */ -inline substr emit_json(Tree const& t, id_type id, substr buf, EmitOptions const& opts, bool error_on_excess=true) +inline substr emit_json(Tree const& t, id_type id, EmitOptions const& opts, substr buf, bool error_on_excess=true) { EmitterBuf em(opts, buf); return em.emit_as(EMIT_JSON, t, id, error_on_excess); @@ -462,7 +488,7 @@ inline substr emit_json(Tree const& t, id_type id, substr buf, bool error_on_exc * @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 * result will be set to null, and the length will report the required buffer size. */ -inline substr emit_yaml(Tree const& t, substr buf, EmitOptions const& opts, bool error_on_excess=true) +inline substr emit_yaml(Tree const& t, EmitOptions const& opts, substr buf, bool error_on_excess=true) { EmitterBuf em(opts, buf); return em.emit_as(EMIT_YAML, t, error_on_excess); @@ -480,7 +506,7 @@ inline substr emit_yaml(Tree const& t, substr buf, bool error_on_excess=true) * @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 * result will be set to null, and the length will report the required buffer size. */ -inline substr emit_json(Tree const& t, substr buf, EmitOptions const& opts, bool error_on_excess=true) +inline substr emit_json(Tree const& t, EmitOptions const& opts, substr buf, bool error_on_excess=true) { EmitterBuf em(opts, buf); return em.emit_as(EMIT_JSON, t, error_on_excess); @@ -503,14 +529,18 @@ inline substr emit_json(Tree const& t, substr buf, bool error_on_excess=true) * @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 * result will be set to null, and the length will report the required buffer size. */ -inline substr emit_yaml(ConstNodeRef const& r, substr buf, EmitOptions const& opts, bool error_on_excess=true) +inline substr emit_yaml(ConstNodeRef const& r, EmitOptions const& opts, substr buf, bool error_on_excess=true) { + if(!detail::is_set_(r)) + return {}; EmitterBuf em(opts, buf); return em.emit_as(EMIT_YAML, r, error_on_excess); } /** (2) like (1), but use default emit options */ inline substr emit_yaml(ConstNodeRef const& r, substr buf, bool error_on_excess=true) { + if(!detail::is_set_(r)) + return {}; EmitterBuf em(buf); return em.emit_as(EMIT_YAML, r, error_on_excess); } @@ -522,14 +552,18 @@ inline substr emit_yaml(ConstNodeRef const& r, substr buf, bool error_on_excess= * @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 * result will be set to null, and the length will report the required buffer size. */ -inline substr emit_json(ConstNodeRef const& r, substr buf, EmitOptions const& opts, bool error_on_excess=true) +inline substr emit_json(ConstNodeRef const& r, EmitOptions const& opts, substr buf, bool error_on_excess=true) { + if(!detail::is_set_(r)) + return {}; EmitterBuf em(opts, buf); return em.emit_as(EMIT_JSON, r, error_on_excess); } /** (2) like (1), but use default emit options */ inline substr emit_json(ConstNodeRef const& r, substr buf, bool error_on_excess=true) { + if(!detail::is_set_(r)) + return {}; EmitterBuf em(buf); return em.emit_as(EMIT_JSON, r, error_on_excess); } @@ -552,15 +586,15 @@ inline substr emit_json(ConstNodeRef const& r, substr buf, bool error_on_excess= template substr emitrs_yaml(Tree const& t, id_type id, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) { - const size_t startpos = append ? cont->size() : 0u; + size_t startpos = append ? cont->size() : 0u; cont->resize(cont->capacity()); // otherwise the first emit would be certain to fail substr buf = to_substr(*cont).sub(startpos); - substr ret = emit_yaml(t, id, buf, opts, /*error_on_excess*/false); + substr ret = emit_yaml(t, id, opts, buf, /*error_on_excess*/false); if(ret.str == nullptr && ret.len > 0) { cont->resize(startpos + ret.len); buf = to_substr(*cont).sub(startpos); - ret = emit_yaml(t, id, buf, opts, /*error_on_excess*/true); + ret = emit_yaml(t, id, opts, buf, /*error_on_excess*/true); } else { @@ -585,12 +619,13 @@ substr emitrs_json(Tree const& t, id_type id, EmitOptions const& opts, CharOwnin const size_t startpos = append ? cont->size() : 0u; cont->resize(cont->capacity()); // otherwise the first emit would be certain to fail substr buf = to_substr(*cont).sub(startpos); - substr ret = emit_json(t, id, buf, opts, /*error_on_excess*/false); + EmitterBuf em(opts, buf); + substr ret = emit_json(t, id, opts, buf, /*error_on_excess*/false); if(ret.str == nullptr && ret.len > 0) { cont->resize(startpos + ret.len); buf = to_substr(*cont).sub(startpos); - ret = emit_json(t, id, buf, opts, /*error_on_excess*/true); + ret = emit_json(t, id, opts, buf, /*error_on_excess*/true); } else { @@ -608,18 +643,18 @@ substr emitrs_json(Tree const& t, id_type id, CharOwningContainer * cont, bool a /** (3) emit+resize: YAML to a newly-created `std::string`/`std::vector`-like container. */ template -CharOwningContainer emitrs_yaml(Tree const& t, id_type id, EmitOptions const& opts={}, bool append=false) +CharOwningContainer emitrs_yaml(Tree const& t, id_type id, EmitOptions const& opts={}) { CharOwningContainer c; - emitrs_yaml(t, id, opts, &c, append); + emitrs_yaml(t, id, opts, &c); return c; } /** (3) emit+resize: JSON to a newly-created `std::string`/`std::vector`-like container. */ template -CharOwningContainer emitrs_json(Tree const& t, id_type id, EmitOptions const& opts={}, bool append=false) +CharOwningContainer emitrs_json(Tree const& t, id_type id, EmitOptions const& opts={}) { CharOwningContainer c; - emitrs_json(t, id, opts, &c, append); + emitrs_json(t, id, opts, &c); return c; } @@ -666,22 +701,22 @@ substr emitrs_json(Tree const& t, CharOwningContainer * cont, bool append=false) /** (3) emit+resize: YAML to a newly-created `std::string`/`std::vector`-like container. */ template -CharOwningContainer emitrs_yaml(Tree const& t, EmitOptions const& opts={}, bool append=false) +CharOwningContainer emitrs_yaml(Tree const& t, EmitOptions const& opts={}) { CharOwningContainer c; if(t.empty()) return c; - emitrs_yaml(t, t.root_id(), opts, &c, append); + emitrs_yaml(t, t.root_id(), opts, &c); return c; } /** (3) emit+resize: JSON to a newly-created `std::string`/`std::vector`-like container. */ template -CharOwningContainer emitrs_json(Tree const& t, EmitOptions const& opts={}, bool append=false) +CharOwningContainer emitrs_json(Tree const& t, EmitOptions const& opts={}) { CharOwningContainer c; if(t.empty()) return c; - emitrs_json(t, t.root_id(), opts, &c, append); + emitrs_json(t, t.root_id(), opts, &c); return c; } @@ -695,6 +730,8 @@ CharOwningContainer emitrs_json(Tree const& t, EmitOptions const& opts={}, bool template substr emitrs_yaml(ConstNodeRef const& n, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) { + if(!detail::is_set_(n)) + return {}; _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); return emitrs_yaml(*n.tree(), n.id(), opts, cont, append); } @@ -702,6 +739,8 @@ substr emitrs_yaml(ConstNodeRef const& n, EmitOptions const& opts, CharOwningCon template substr emitrs_yaml(ConstNodeRef const& n, CharOwningContainer * cont, bool append=false) { + if(!detail::is_set_(n)) + return {}; _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); return emitrs_yaml(*n.tree(), n.id(), EmitOptions{}, cont, append); } @@ -711,6 +750,8 @@ substr emitrs_yaml(ConstNodeRef const& n, CharOwningContainer * cont, bool appen template substr emitrs_json(ConstNodeRef const& n, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) { + if(!detail::is_set_(n)) + return {}; _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); return emitrs_json(*n.tree(), n.id(), opts, cont, append); } @@ -718,6 +759,8 @@ substr emitrs_json(ConstNodeRef const& n, EmitOptions const& opts, CharOwningCon template substr emitrs_json(ConstNodeRef const& n, CharOwningContainer * cont, bool append=false) { + if(!detail::is_set_(n)) + return {}; _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); return emitrs_json(*n.tree(), n.id(), EmitOptions{}, cont, append); } @@ -725,20 +768,24 @@ substr emitrs_json(ConstNodeRef const& n, CharOwningContainer * cont, bool appen /** (3) emit+resize: YAML to a newly-created `std::string`/`std::vector`-like container. */ template -CharOwningContainer emitrs_yaml(ConstNodeRef const& n, EmitOptions const& opts={}, bool append=false) +CharOwningContainer emitrs_yaml(ConstNodeRef const& n, EmitOptions const& opts={}) { + if(!detail::is_set_(n)) + return {}; _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); CharOwningContainer c; - emitrs_yaml(*n.tree(), n.id(), opts, &c, append); + emitrs_yaml(*n.tree(), n.id(), opts, &c); return c; } /** (3) emit+resize: JSON to a newly-created `std::string`/`std::vector`-like container. */ template -CharOwningContainer emitrs_json(ConstNodeRef const& n, EmitOptions const& opts={}, bool append=false) +CharOwningContainer emitrs_json(ConstNodeRef const& n, EmitOptions const& opts={}) { + if(!detail::is_set_(n)) + return {}; _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); CharOwningContainer c; - emitrs_json(*n.tree(), n.id(), opts, &c, append); + emitrs_json(*n.tree(), n.id(), opts, &c); return c; } diff --git a/src/c4/yml/node.hpp b/src/c4/yml/node.hpp index 288521dd..510e0bfc 100644 --- a/src/c4/yml/node.hpp +++ b/src/c4/yml/node.hpp @@ -383,6 +383,9 @@ struct RoNodeMethods /** O(num_siblings). Forward to Tree::sibling_pos(). */ C4_ALWAYS_INLINE id_type sibling_pos(ConstImpl const& n) const RYML_NOEXCEPT { _C4RR(); _RYML_CB_ASSERT(tree_->callbacks(), n.readable()); return tree_->child_pos(tree_->parent(id_), n.m_id); } + C4_ALWAYS_INLINE id_type depth_asc() const RYML_NOEXCEPT { _C4RR(); return tree_->depth_asc(id_); } /** O(log(num_nodes)). Forward to Tree::depth_asc(). Node must be readable. */ + C4_ALWAYS_INLINE id_type depth_desc() const RYML_NOEXCEPT { _C4RR(); return tree_->depth_desc(id_); } /** O(num_nodes). Forward to Tree::depth_desc(). Node must be readable. */ + /** @} */ public: diff --git a/src/c4/yml/tree.cpp b/src/c4/yml/tree.cpp index da78c8b0..ce4a8162 100644 --- a/src/c4/yml/tree.cpp +++ b/src/c4/yml/tree.cpp @@ -1192,6 +1192,37 @@ id_type Tree::find_child(id_type node, csubstr const& name) const # pragma GCC diagnostic pop #endif +namespace { +id_type depth_desc_(Tree const& C4_RESTRICT t, id_type id, id_type currdepth=0, id_type maxdepth=0) +{ + maxdepth = currdepth > maxdepth ? currdepth : maxdepth; + for(id_type child = t.first_child(id); child != NONE; child = t.next_sibling(child)) + { + const id_type d = depth_desc_(t, child, currdepth+1, maxdepth); + maxdepth = d > maxdepth ? d : maxdepth; + } + return maxdepth; +} +} + +id_type Tree::depth_desc(id_type node) const +{ + _RYML_CB_ASSERT(m_callbacks, node != NONE); + return depth_desc_(*this, node); +} + +id_type Tree::depth_asc(id_type node) const +{ + _RYML_CB_ASSERT(m_callbacks, node != NONE); + id_type depth = 0; + while(!is_root(node)) + { + ++depth; + node = parent(node); + } + return depth; +} + //----------------------------------------------------------------------------- diff --git a/src/c4/yml/tree.hpp b/src/c4/yml/tree.hpp index 60edc084..38e8e8fc 100644 --- a/src/c4/yml/tree.hpp +++ b/src/c4/yml/tree.hpp @@ -514,6 +514,9 @@ class RYML_EXPORT Tree id_type doc(id_type i) const { id_type rid = root_id(); _RYML_CB_ASSERT(m_callbacks, is_stream(rid)); return child(rid, i); } //!< gets the @p i document node index. requires that the root node is a stream. + id_type depth_asc(id_type node) const; /**< O(log(num_tree_nodes)) get the ascending depth of the node: number of levels between root and node */ + id_type depth_desc(id_type node) const; /**< O(num_tree_nodes) get the descending depth of the node: number of levels between node and deepest child */ + /** @} */ public: diff --git a/test/test_emit.cpp b/test/test_emit.cpp index 09fd967f..055afc96 100644 --- a/test/test_emit.cpp +++ b/test/test_emit.cpp @@ -27,6 +27,7 @@ std::string emit2file(Emit &&fn) fclose(f); std::string result = fs::file_get_contents(filename.c_str()); fs::rmfile(filename.c_str()); + _c4dbgpf("emit result: [{}]~~~{}~~~", result.size(), to_csubstr(result)); return result; C4_SUPPRESS_WARNING_MSVC_POP } @@ -36,7 +37,9 @@ std::string emit2stream(Emit &&fn) { std::ostringstream ss; fn(ss); - return ss.str(); + std::string result = ss.str(); + _c4dbgpf("emit result: [{}]~~~{}~~~", result.size(), to_csubstr(result)); + return result; } template @@ -51,6 +54,16 @@ std::string emit2buf(Emit &&fn) out = fn(to_substr(buf)); } buf.resize(out.len); + _c4dbgpf("emit result: [{}]~~~{}~~~", buf.size(), to_csubstr(buf)); + return buf; +} + +template +std::string emitrs_append(csubstr first_part, Emit &&fn) +{ + std::string buf(first_part.begin(), first_part.end()); + fn(&buf); + _c4dbgpf("emit result: [{}]~~~{}~~~", buf.size(), to_csubstr(buf)); return buf; } @@ -59,6 +72,60 @@ std::string emit2buf(Emit &&fn) //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- +bool operator== (const EmitOptions lhs, const EmitOptions rhs) +{ + return memcmp(&lhs, &rhs, sizeof(lhs)) == 0; +} + +TEST(as_yaml, basic) +{ + Tree et; + { + as_yaml j(et); + EXPECT_EQ(j.tree, &et); + } + Tree t = parse_in_arena("[foo, bar]"); + { + as_yaml j(t); + EXPECT_EQ(j.tree, &t); + EXPECT_EQ(j.node, t.root_id()); + EXPECT_EQ(j.options, EmitOptions{}); + } + { + EmitOptions opts = EmitOptions{}.max_depth(10); + as_yaml j(t, opts); + EXPECT_EQ(j.tree, &t); + EXPECT_EQ(j.node, t.root_id()); + EXPECT_EQ(j.options, opts); + } + { + as_yaml j(t, 2u); + EXPECT_EQ(j.tree, &t); + EXPECT_EQ(j.node, 2u); + EXPECT_EQ(j.options, EmitOptions{}); + } + { + EmitOptions opts = EmitOptions{}.max_depth(10); + as_yaml j(t, 2u, opts); + EXPECT_EQ(j.tree, &t); + EXPECT_EQ(j.node, 2u); + EXPECT_EQ(j.options, opts); + } + { + as_yaml j(t[0]); + EXPECT_EQ(j.tree, &t); + EXPECT_EQ(j.node, 1u); + EXPECT_EQ(j.options, EmitOptions{}); + } + { + EmitOptions opts = EmitOptions{}.max_depth(10); + as_yaml j(t[0], opts); + EXPECT_EQ(j.tree, &t); + EXPECT_EQ(j.node, 1u); + EXPECT_EQ(j.options, opts); + } +} + TEST(as_json, basic) { Tree et; @@ -71,16 +138,40 @@ TEST(as_json, basic) as_json j(t); EXPECT_EQ(j.tree, &t); EXPECT_EQ(j.node, t.root_id()); + EXPECT_EQ(j.options, EmitOptions{}); + } + { + EmitOptions opts = EmitOptions{}.max_depth(10); + as_json j(t, opts); + EXPECT_EQ(j.tree, &t); + EXPECT_EQ(j.node, t.root_id()); + EXPECT_EQ(j.options, opts); } { as_json j(t, 2u); EXPECT_EQ(j.tree, &t); EXPECT_EQ(j.node, 2u); + EXPECT_EQ(j.options, EmitOptions{}); + } + { + EmitOptions opts = EmitOptions{}.max_depth(10); + as_json j(t, 2u, opts); + EXPECT_EQ(j.tree, &t); + EXPECT_EQ(j.node, 2u); + EXPECT_EQ(j.options, opts); } { as_json j(t[0]); EXPECT_EQ(j.tree, &t); EXPECT_EQ(j.node, 1u); + EXPECT_EQ(j.options, EmitOptions{}); + } + { + EmitOptions opts = EmitOptions{}.max_depth(10); + as_json j(t[0], opts); + EXPECT_EQ(j.tree, &t); + EXPECT_EQ(j.node, 1u); + EXPECT_EQ(j.options, opts); } } @@ -89,41 +180,157 @@ TEST(as_json, basic) //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -template -void test_emits(Tree const& t, size_t id, std::string const& expected, std::string const& expected_json) + +void test_emits(Tree const& t, id_type id, std::string const& expected, std::string const& expected_json) { - EXPECT_EQ(emit2buf([&](substr buf){ return emit_yaml(t, id, buf); }), expected); - EXPECT_EQ(emit2buf([&](substr buf){ return emit_json(t, id, buf); }), expected_json); - EXPECT_EQ(emit2file([&](FILE *f){ return emit_yaml(t, id, f); }), expected); - EXPECT_EQ(emit2file([&](FILE *f){ return emit_json(t, id, f); }), expected_json); - EXPECT_EQ(emitrs_yaml(t, id), expected); - EXPECT_EQ(emitrs_json(t, id), expected_json); + EXPECT_EQ(emit2buf([&](substr buf){ return emit_yaml(t, id, buf); }), expected); + EXPECT_EQ(emit2buf([&](substr buf){ return emit_yaml(t, id, EmitOptions{}, buf); }), expected); + EXPECT_EQ(emit2buf([&](substr buf){ return emit_yaml(t, id, EmitOptions{}, buf); }), expected); + EXPECT_EQ(emit2buf([&](substr buf){ return emit_json(t, id, buf); }), expected_json); + EXPECT_EQ(emit2buf([&](substr buf){ return emit_json(t, id, EmitOptions{}, buf); }), expected_json); + EXPECT_EQ(emit2buf([&](substr buf){ EmitterBuf em(buf); return em.emit_as(EMIT_YAML, t, id, /*error_on_excess*/true); }), expected); + EXPECT_EQ(emit2buf([&](substr buf){ EmitterBuf em(buf); return em.emit_as(EMIT_JSON, t, id, /*error_on_excess*/true); }), expected_json); + EXPECT_EQ(emit2file([&](FILE *f){ return emit_yaml(t, id, f); }), expected); + EXPECT_EQ(emit2file([&](FILE *f){ return emit_yaml(t, id, EmitOptions{}, f); }), expected); + EXPECT_EQ(emit2file([&](FILE *f){ return emit_json(t, id, f); }), expected_json); + EXPECT_EQ(emit2file([&](FILE *f){ return emit_json(t, id, EmitOptions{}, f); }), expected_json); + EXPECT_EQ(emit2file([&](FILE *f){ EmitterFile em(f); return em.emit_as(EMIT_YAML, t, id, /*error_on_excess*/true).len; }), expected); + EXPECT_EQ(emit2file([&](FILE *f){ EmitterFile em(f); return em.emit_as(EMIT_JSON, t, id, /*error_on_excess*/true).len; }), expected_json); + if(id != NONE) + { + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ oss << as_yaml(t, id); }), expected); + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ oss << as_yaml(t, id, EmitOptions{}); }), expected); + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ oss << as_json(t, id); }), expected_json); + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ oss << as_json(t, id, EmitOptions{}); }), expected_json); + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ EmitterOStream em({}, oss); em.emit_as(EMIT_YAML, t, id, /*error_on_excess*/true); }), expected); + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ EmitterOStream em({}, oss); em.emit_as(EMIT_JSON, t, id, /*error_on_excess*/true); }), expected_json); + } + EXPECT_EQ(emitrs_yaml(t, id), expected); + EXPECT_EQ(emitrs_yaml(t, id, EmitOptions{}), expected); + EXPECT_EQ(emitrs_json(t, id), expected_json); + EXPECT_EQ(emitrs_json(t, id, EmitOptions{}), expected_json); + std::string append_prefix = "#before\n"; + EXPECT_EQ(emitrs_append(to_csubstr(append_prefix), [&](std::string *s) { emitrs_yaml(t, id, s, /*append*/true); } ), append_prefix + expected); + EXPECT_EQ(emitrs_append(to_csubstr(append_prefix), [&](std::string *s) { emitrs_yaml(t, id, EmitOptions{}, s, /*append*/true); } ), append_prefix + expected); + EXPECT_EQ(emitrs_append(to_csubstr(append_prefix), [&](std::string *s) { emitrs_json(t, id, s, /*append*/true); } ), append_prefix + expected_json); + EXPECT_EQ(emitrs_append(to_csubstr(append_prefix), [&](std::string *s) { emitrs_json(t, id, EmitOptions{}, s, /*append*/true); } ), append_prefix + expected_json); + // error on max depth + if(id == NONE) + return; + id_type max_depth = t.depth_desc(id); + if(max_depth > 1) + { + EmitOptions opts = EmitOptions{}.max_depth(0); + ExpectError::do_check(&t, [&]{ return emit2buf([&](substr buf){ return emit_yaml(t, id, opts, buf); }); }); + ExpectError::do_check(&t, [&]{ return emit2buf([&](substr buf){ return emit_json(t, id, opts, buf); }); }); + ExpectError::do_check(&t, [&]{ return emit2file([&](FILE *f){ return emit_yaml(t, id, opts, f); }); }); + ExpectError::do_check(&t, [&]{ return emit2file([&](FILE *f){ return emit_json(t, id, opts, f); }); }); + ExpectError::do_check(&t, [&]{ return emit2stream([&](std::ostringstream &oss){ oss << as_yaml(t, id, opts); }); }); + ExpectError::do_check(&t, [&]{ return emit2stream([&](std::ostringstream &oss){ oss << as_json(t, id, opts); }); }); + ExpectError::do_check(&t, [&]{ return emitrs_yaml(t, id, opts); }); + ExpectError::do_check(&t, [&]{ return emitrs_json(t, id, opts); }); + } } -template void test_emits(Tree const& t, std::string const& expected, std::string const& expected_json) { - EXPECT_EQ(emit2buf([&](substr buf){ return emit_yaml(t, buf); }), expected); - EXPECT_EQ(emit2buf([&](substr buf){ return emit_json(t, buf); }), expected_json); - EXPECT_EQ(emit2file([&](FILE *f){ return emit_yaml(t, f); }), expected); - EXPECT_EQ(emit2file([&](FILE *f){ return emit_json(t, f); }), expected_json); - EXPECT_EQ(emit2stream([&](std::ostream& s){ s << t; }), expected); - EXPECT_EQ(emit2stream([&](std::ostream& s){ s << as_json(t); }), expected_json); - EXPECT_EQ(emitrs_yaml(t), expected); - EXPECT_EQ(emitrs_json(t), expected_json); + EXPECT_EQ(emit2buf([&](substr buf){ return emit_yaml(t, buf); }), expected); + EXPECT_EQ(emit2buf([&](substr buf){ return emit_yaml(t, EmitOptions{}, buf); }), expected); + EXPECT_EQ(emit2buf([&](substr buf){ return emit_json(t, buf); }), expected_json); + EXPECT_EQ(emit2buf([&](substr buf){ return emit_json(t, EmitOptions{}, buf); }), expected_json); + EXPECT_EQ(emit2buf([&](substr buf){ EmitterBuf em(buf); return em.emit_as(EMIT_YAML, t, /*error_on_excess*/true); }), expected); + EXPECT_EQ(emit2buf([&](substr buf){ EmitterBuf em(buf); return em.emit_as(EMIT_JSON, t, /*error_on_excess*/true); }), expected_json); + EXPECT_EQ(emit2file([&](FILE *f){ return emit_yaml(t, f); }), expected); + EXPECT_EQ(emit2file([&](FILE *f){ return emit_yaml(t, EmitOptions{}, f); }), expected); + EXPECT_EQ(emit2file([&](FILE *f){ return emit_json(t, f); }), expected_json); + EXPECT_EQ(emit2file([&](FILE *f){ return emit_json(t, EmitOptions{}, f); }), expected_json); + EXPECT_EQ(emit2file([&](FILE *f){ EmitterFile em(f); return em.emit_as(EMIT_YAML, t, /*error_on_excess*/true).len; }), expected); + EXPECT_EQ(emit2file([&](FILE *f){ EmitterFile em(f); return em.emit_as(EMIT_JSON, t, /*error_on_excess*/true).len; }), expected_json); + if(!t.empty()) + { + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ oss << as_yaml(t); }), expected); + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ oss << as_yaml(t, EmitOptions{}); }), expected); + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ oss << as_json(t); }), expected_json); + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ oss << as_json(t, EmitOptions{}); }), expected_json); + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ EmitterOStream em({}, oss); em.emit_as(EMIT_YAML, t, /*error_on_excess*/true); }), expected); + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ EmitterOStream em({}, oss); em.emit_as(EMIT_JSON, t, /*error_on_excess*/true); }), expected_json); + } + EXPECT_EQ(emitrs_yaml(t), expected); + EXPECT_EQ(emitrs_yaml(t, EmitOptions{}), expected); + EXPECT_EQ(emitrs_json(t), expected_json); + EXPECT_EQ(emitrs_json(t, EmitOptions{}), expected_json); + std::string append_prefix = "#before\n"; + EXPECT_EQ(emitrs_append(to_csubstr(append_prefix), [&](std::string *s) { emitrs_yaml(t, s, /*append*/true); } ), append_prefix + expected); + EXPECT_EQ(emitrs_append(to_csubstr(append_prefix), [&](std::string *s) { emitrs_yaml(t, EmitOptions{}, s, /*append*/true); } ), append_prefix + expected); + EXPECT_EQ(emitrs_append(to_csubstr(append_prefix), [&](std::string *s) { emitrs_json(t, s, /*append*/true); } ), append_prefix + expected_json); + EXPECT_EQ(emitrs_append(to_csubstr(append_prefix), [&](std::string *s) { emitrs_json(t, EmitOptions{}, s, /*append*/true); } ), append_prefix + expected_json); + // error on max depth + id_type max_depth = t.empty() ? 0 : t.depth_desc(t.root_id()); + if(max_depth > 1) + { + EmitOptions opts = EmitOptions{}.max_depth(0); + ExpectError::do_check(&t, [&]{ return emit2buf([&](substr buf){ return emit_yaml(t, opts, buf); }); }); + ExpectError::do_check(&t, [&]{ return emit2buf([&](substr buf){ return emit_json(t, opts, buf); }); }); + ExpectError::do_check(&t, [&]{ return emit2file([&](FILE *f){ return emit_yaml(t, opts, f); }); }); + ExpectError::do_check(&t, [&]{ return emit2file([&](FILE *f){ return emit_json(t, opts, f); }); }); + ExpectError::do_check(&t, [&]{ return emit2stream([&](std::ostringstream &oss){ oss << as_yaml(ConstNodeRef(&t), opts); }); }); + ExpectError::do_check(&t, [&]{ return emit2stream([&](std::ostringstream &oss){ oss << as_json(ConstNodeRef(&t), opts); }); }); + ExpectError::do_check(&t, [&]{ return emitrs_yaml(t, opts); }); + ExpectError::do_check(&t, [&]{ return emitrs_json(t, opts); }); + } } -template -void test_emits(ConstNodeRef t, std::string const& expected, std::string const& expected_json) +void test_emits(ConstNodeRef n, std::string const& expected, std::string const& expected_json) { - EXPECT_EQ(emit2buf([&](substr buf){ return emit_yaml(t, buf); }), expected); - EXPECT_EQ(emit2buf([&](substr buf){ return emit_json(t, buf); }), expected_json); - EXPECT_EQ(emit2file([&](FILE *f){ return emit_yaml(t, f); }), expected); - EXPECT_EQ(emit2file([&](FILE *f){ return emit_json(t, f); }), expected_json); - EXPECT_EQ(emit2stream([&](std::ostream& s){ s << t; }), expected); - EXPECT_EQ(emit2stream([&](std::ostream& s){ s << as_json(t); }), expected_json); - EXPECT_EQ(emitrs_yaml(t), expected); - EXPECT_EQ(emitrs_json(t), expected_json); + EXPECT_EQ(emit2buf([&](substr buf){ return emit_yaml(n, buf); }), expected); + EXPECT_EQ(emit2buf([&](substr buf){ return emit_yaml(n, EmitOptions{}, buf); }), expected); + EXPECT_EQ(emit2buf([&](substr buf){ return emit_json(n, buf); }), expected_json); + EXPECT_EQ(emit2buf([&](substr buf){ return emit_json(n, EmitOptions{}, buf); }), expected_json); + EXPECT_EQ(emit2buf([&](substr buf){ EmitterBuf em(buf); return em.emit_as(EMIT_YAML, n, /*error_on_excess*/true); }), expected); + EXPECT_EQ(emit2buf([&](substr buf){ EmitterBuf em(buf); return em.emit_as(EMIT_JSON, n, /*error_on_excess*/true); }), expected_json); + EXPECT_EQ(emit2file([&](FILE *f){ return emit_yaml(n, f); }), expected); + EXPECT_EQ(emit2file([&](FILE *f){ return emit_yaml(n, EmitOptions{}, f); }), expected); + EXPECT_EQ(emit2file([&](FILE *f){ return emit_json(n, f); }), expected_json); + EXPECT_EQ(emit2file([&](FILE *f){ return emit_json(n, EmitOptions{}, f); }), expected_json); + EXPECT_EQ(emit2file([&](FILE *f){ EmitterFile em(f); return em.emit_as(EMIT_YAML, n, /*error_on_excess*/true).len; }), expected); + EXPECT_EQ(emit2file([&](FILE *f){ EmitterFile em(f); return em.emit_as(EMIT_JSON, n, /*error_on_excess*/true).len; }), expected_json); + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ oss << n; }), expected); + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ oss << as_yaml(n); }), expected); + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ oss << as_yaml(n, EmitOptions{}); }), expected); + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ oss << as_json(n); }), expected_json); + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ oss << as_json(n, EmitOptions{}); }), expected_json); + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ EmitterOStream em({}, oss); em.emit_as(EMIT_YAML, n, /*error_on_excess*/true); }), expected); + EXPECT_EQ(emit2stream([&](std::ostringstream &oss){ EmitterOStream em({}, oss); em.emit_as(EMIT_JSON, n, /*error_on_excess*/true); }), expected_json); + EXPECT_EQ(emitrs_yaml(n), expected); + EXPECT_EQ(emitrs_yaml(n, EmitOptions{}), expected); + EXPECT_EQ(emitrs_json(n), expected_json); + EXPECT_EQ(emitrs_json(n, EmitOptions{}), expected_json); + std::string append_prefix = "#before\n"; + EXPECT_EQ(emitrs_append(to_csubstr(append_prefix), [&](std::string *s) { emitrs_yaml(n, s, /*append*/true); } ), append_prefix + expected); + EXPECT_EQ(emitrs_append(to_csubstr(append_prefix), [&](std::string *s) { emitrs_yaml(n, EmitOptions{}, s, /*append*/true); } ), append_prefix + expected); + EXPECT_EQ(emitrs_append(to_csubstr(append_prefix), [&](std::string *s) { emitrs_json(n, s, /*append*/true); } ), append_prefix + expected_json); + EXPECT_EQ(emitrs_append(to_csubstr(append_prefix), [&](std::string *s) { emitrs_json(n, EmitOptions{}, s, /*append*/true); } ), append_prefix + expected_json); + // error on max depth + if(n.tree() && n.id() != NONE) + { + id_type max_depth = n.depth_desc(); + if(max_depth > 1) + { + EmitOptions opts = EmitOptions{}.max_depth(0); + ExpectError::do_check(n.tree(), [&]{ + return emit2buf([&](substr buf){ + return emit_yaml(n, opts, buf); + }); + }); + ExpectError::do_check(n.tree(), [&]{ return emit2buf([&](substr buf){ return emit_json(n, opts, buf); }); }); + ExpectError::do_check(n.tree(), [&]{ return emit2file([&](FILE *f){ return emit_yaml(n, opts, f); }); }); + ExpectError::do_check(n.tree(), [&]{ return emit2file([&](FILE *f){ return emit_json(n, opts, f); }); }); + ExpectError::do_check(n.tree(), [&]{ return emit2stream([&](std::ostringstream &oss){ oss << as_yaml(n, opts); }); }); + ExpectError::do_check(n.tree(), [&]{ return emit2stream([&](std::ostringstream &oss){ oss << as_json(n, opts); }); }); + ExpectError::do_check(n.tree(), [&]{ return emitrs_yaml(n, opts); }); + ExpectError::do_check(n.tree(), [&]{ return emitrs_json(n, opts); }); + } + } } @@ -134,6 +341,13 @@ TEST(emit, empty_tree) test_emits(t, expected, expected); } +TEST(emit, empty_node) +{ + std::string expected = R"()"; + test_emits(NodeRef{}, expected, expected); + test_emits(ConstNodeRef{}, expected, expected); +} + TEST(emit, existing_tree) { const Tree t = parse_in_arena("[foo, bar]"); @@ -150,7 +364,7 @@ TEST(emit, no_node) test_emits(t, NONE, expected, expected_json); } -TEST(emit, existing_seq_node) +TEST(emit, existing_seq_node_flow) { Tree nct = parse_in_arena("[foo, bar, [nested, seq], {nested: map}]"); Tree const& t = nct; @@ -187,7 +401,7 @@ TEST(emit, existing_seq_node) nct._add_flags(n.id(), FLOW_SL); expected = "foo"; { - SCOPED_TRACE("t, id"); + SCOPED_TRACE("noderef"); test_emits(n, expected, expected_json); } { @@ -211,14 +425,13 @@ TEST(emit, existing_seq_node) nct._add_flags(n.id(), FLOW_SL); expected = "bar"; { - SCOPED_TRACE("t, id"); + SCOPED_TRACE("noderef"); test_emits(n, expected, expected_json); } { SCOPED_TRACE("t, id"); test_emits(t, n.id(), expected, expected_json); } - } { SCOPED_TRACE("t[2]"); @@ -236,7 +449,7 @@ TEST(emit, existing_seq_node) expected = "[nested,seq]"; nct._add_flags(n.id(), FLOW_SL); { - SCOPED_TRACE("t, id"); + SCOPED_TRACE("noderef"); test_emits(n, expected, expected_json); } { @@ -259,8 +472,127 @@ TEST(emit, existing_seq_node) } expected = "{nested: map}"; nct._add_flags(n.id(), FLOW_SL); + { + SCOPED_TRACE("noderef"); + test_emits(n, expected, expected_json); + } { SCOPED_TRACE("t, id"); + test_emits(t, n.id(), expected, expected_json); + } + } +} + +TEST(emit, existing_seq_node_block) +{ + Tree nct = parse_in_arena("- foo\n- bar\n- - nested\n - seq\n- nested: map\n"); + Tree const& t = nct; + { + SCOPED_TRACE("full"); + std::string expected = "- foo\n- bar\n- - nested\n - seq\n- nested: map\n"; + std::string expected_json = R"(["foo","bar",["nested","seq"],{"nested": "map"}])"; + { + SCOPED_TRACE("rootref"); + test_emits(t.crootref(), expected, expected_json); + } + { + SCOPED_TRACE("t"); + test_emits(t, expected, expected_json); + } + { + SCOPED_TRACE("t, id"); + test_emits(t, t.root_id(), expected, expected_json); + } + } + { + SCOPED_TRACE("t[0]"); + ConstNodeRef n = t[0]; + std::string expected = "foo\n"; + std::string expected_json = "\"foo\""; + { + SCOPED_TRACE("noderef"); + test_emits(n, expected, expected_json); + } + { + SCOPED_TRACE("t, id"); + test_emits(t, n.id(), expected, expected_json); + } + nct._add_flags(n.id(), FLOW_SL); + expected = "foo"; + { + SCOPED_TRACE("noderef"); + test_emits(n, expected, expected_json); + } + { + SCOPED_TRACE("t, id"); + test_emits(t, n.id(), expected, expected_json); + } + } + { + SCOPED_TRACE("t[1]"); + ConstNodeRef n = t[1]; + std::string expected = "bar\n"; + std::string expected_json = "\"bar\""; + { + SCOPED_TRACE("noderef"); + test_emits(n, expected, expected_json); + } + { + SCOPED_TRACE("t, id"); + test_emits(t, n.id(), expected, expected_json); + } + nct._add_flags(n.id(), FLOW_SL); + expected = "bar"; + { + SCOPED_TRACE("noderef"); + test_emits(n, expected, expected_json); + } + { + SCOPED_TRACE("t, id"); + test_emits(t, n.id(), expected, expected_json); + } + } + { + SCOPED_TRACE("t[2]"); + ConstNodeRef n = t[2]; + std::string expected = "- nested\n- seq\n"; + std::string expected_json = "[\"nested\",\"seq\"]"; + { + SCOPED_TRACE("noderef"); + test_emits(n, expected, expected_json); + } + { + SCOPED_TRACE("t, id"); + test_emits(t, n.id(), expected, expected_json); + } + expected = "[nested,seq]"; + nct._add_flags(n.id(), FLOW_SL); + { + SCOPED_TRACE("noderef"); + test_emits(n, expected, expected_json); + } + { + SCOPED_TRACE("t, id"); + test_emits(t, n.id(), expected, expected_json); + } + } + { + SCOPED_TRACE("t[3]"); + ConstNodeRef n = t[3]; + std::string expected = "nested: map\n"; + std::string expected_json = "{\"nested\": \"map\"}"; + { + SCOPED_TRACE("noderef"); + test_emits(n, expected, expected_json); + } + { + SCOPED_TRACE("t, id"); + test_emits(t, n.id(), expected, expected_json); + } + expected = "{nested: map}"; + nct._add_flags(n.id(), FLOW_SL); + { + SCOPED_TRACE("noderef"); test_emits(n, expected, expected_json); } { @@ -270,7 +602,7 @@ TEST(emit, existing_seq_node) } } -TEST(emit, existing_map_node) +TEST(emit, existing_map_node_flow) { Tree nct = parse_in_arena("{0: foo, 1: bar, 2: [nested, seq], 3: {nested: map}}"); Tree const& t = nct; @@ -391,6 +723,127 @@ TEST(emit, existing_map_node) } } +TEST(emit, existing_map_node_block) +{ + Tree nct = parse_in_arena("0: foo\n1: bar\n2:\n - nested\n - seq\n3:\n nested: map\n"); + Tree const& t = nct; + { + SCOPED_TRACE("root"); + std::string expected = "0: foo\n1: bar\n2:\n - nested\n - seq\n3:\n nested: map\n"; + std::string expected_json = R"({"0": "foo","1": "bar","2": ["nested","seq"],"3": {"nested": "map"}})"; + { + SCOPED_TRACE("rootref"); + test_emits(t.rootref(), expected, expected_json); + } + { + SCOPED_TRACE("t"); + test_emits(t, expected, expected_json); + } + { + SCOPED_TRACE("t, id"); + test_emits(t, t.root_id(), expected, expected_json); + } + } + { + SCOPED_TRACE("t[0]"); + ConstNodeRef n = t[0]; + std::string expected = "0: foo\n"; + std::string expected_json = "\"0\": \"foo\""; + { + SCOPED_TRACE("noderef"); + test_emits(n, expected, expected_json); + } + { + SCOPED_TRACE("t, id"); + test_emits(t, n.id(), expected, expected_json); + } + expected = "0: foo"; + nct._add_flags(n.id(), FLOW_SL); + { + SCOPED_TRACE("t, id"); + test_emits(n, expected, expected_json); + } + { + SCOPED_TRACE("t, id"); + test_emits(t, n.id(), expected, expected_json); + } + } + { + SCOPED_TRACE("t[1]"); + ConstNodeRef n = t[1]; + std::string expected = "1: bar\n"; + std::string expected_json = "\"1\": \"bar\""; + { + SCOPED_TRACE("noderef"); + test_emits(n, expected, expected_json); + } + { + SCOPED_TRACE("t, id"); + test_emits(t, n.id(), expected, expected_json); + } + expected = "1: bar"; + nct._add_flags(n.id(), FLOW_SL); + { + SCOPED_TRACE("t, id"); + test_emits(n, expected, expected_json); + } + { + SCOPED_TRACE("t, id"); + test_emits(t, n.id(), expected, expected_json); + } + } + { + SCOPED_TRACE("t[2]"); + ConstNodeRef n = t[2]; + std::string expected = "2:\n - nested\n - seq\n"; + std::string expected_json = "\"2\": [\"nested\",\"seq\"]"; + { + SCOPED_TRACE("noderef"); + test_emits(n, expected, expected_json); + } + { + SCOPED_TRACE("t, id"); + test_emits(t, n.id(), expected, expected_json); + } + expected = "2:\n - nested\n - seq\n"; + nct._rem_flags(n.id(), CONTAINER_STYLE); + nct._add_flags(n.id(), BLOCK); + { + SCOPED_TRACE("t, id"); + test_emits(n, expected, expected_json); + } + { + SCOPED_TRACE("t, id"); + test_emits(t, n.id(), expected, expected_json); + } + } + { + SCOPED_TRACE("t[3]"); + ConstNodeRef n = t[3]; + std::string expected = "3:\n nested: map\n"; + std::string expected_json = "\"3\": {\"nested\": \"map\"}"; + { + SCOPED_TRACE("noderef"); + test_emits(n, expected, expected_json); + } + { + SCOPED_TRACE("t, id"); + test_emits(t, n.id(), expected, expected_json); + } + expected = "3:\n nested: map\n"; + nct._rem_flags(n.id(), CONTAINER_STYLE); + nct._add_flags(n.id(), BLOCK); + { + SCOPED_TRACE("t, id"); + test_emits(n, expected, expected_json); + } + { + SCOPED_TRACE("t, id"); + test_emits(t, n.id(), expected, expected_json); + } + } +} + TEST(emit, percent_is_quoted) { Tree ti = parse_in_arena("{}"); diff --git a/test/test_lib/test_case.cpp b/test/test_lib/test_case.cpp index d27e6d91..eda27f70 100644 --- a/test/test_lib/test_case.cpp +++ b/test/test_lib/test_case.cpp @@ -219,6 +219,12 @@ ExpectError::ExpectError(Tree *tree, Location loc) auto err = [](const char* msg, size_t len, Location errloc, void *this_) { _c4dbgpf("called error callback! (withlocation={})", bool(errloc)); ((ExpectError*)this_)->m_got_an_error = true; // assign in here to ensure the exception was thrown here + _c4dbgpf("msglen={}", len); + _c4dbgpf("msg={}", csubstr{msg, len}); + _c4dbgpf("loc.offset={}", errloc.offset); + _c4dbgpf("loc.line={}", errloc.line); + _c4dbgpf("loc.col={}", errloc.col); + _c4dbgpf("loc.file={}", errloc.name); C4_IF_EXCEPTIONS( throw ExpectedError(msg, len, errloc); , @@ -262,6 +268,11 @@ void ExpectError::check_success(Tree *tree, std::function fn) EXPECT_FALSE(context.m_got_an_error); } +void ExpectError::do_check(Tree const* tree, std::function fn, Location expected_location) +{ + do_check(const_cast(tree), fn, expected_location); +} + void ExpectError::do_check(Tree *tree, std::function fn, Location expected_location) { auto context = ExpectError(tree, expected_location); diff --git a/test/test_lib/test_case.hpp b/test/test_lib/test_case.hpp index bc2a9939..e85e964b 100644 --- a/test/test_lib/test_case.hpp +++ b/test/test_lib/test_case.hpp @@ -203,8 +203,9 @@ struct ExpectError ExpectError(Tree *tree, Location loc={}); ~ExpectError(); - static void do_check( std::function fn, Location expected={}) { do_check(nullptr, fn, expected); } + static void do_check( std::function fn, Location expected={}) { do_check((const Tree*)nullptr, fn, expected); } static void do_check(Tree *tree, std::function fn, Location expected={}); + static void do_check(Tree const *tree, std::function fn, Location expected={}); static void check_assertion( std::function fn, Location expected={}) { check_assertion(nullptr, fn, expected); } static void check_assertion(Tree *tree, std::function fn, Location expected={}); static void check_success( std::function fn) { check_success(nullptr, fn); }; diff --git a/test/test_parser.cpp b/test/test_parser.cpp index 49b85bfb..1baba8f0 100644 --- a/test/test_parser.cpp +++ b/test/test_parser.cpp @@ -400,6 +400,23 @@ TEST(Parser, estimate_tree_capacity) )")); } +TEST(Parser, alloc_arena) +{ + Tree tree; + Parser::handler_type evt_handler = {}; + evt_handler.reset(&tree, tree.root_id()); + substr bufa = evt_handler.alloc_arena(64); + bufa.fill('a'); + csubstr prev = bufa; + csubstr prev_arena = tree.arena(); + substr bufb = evt_handler.alloc_arena(64, &bufa); + csubstr curr_arena = tree.arena(); + EXPECT_NE(prev_arena.str, curr_arena.str); + EXPECT_NE(prev.str, bufa.str); + EXPECT_EQ(bufa.first_not_of('a'), npos); + (void)bufb; +} + TEST(parse_in_place, overloads) { char src1_[] = "{a: b}"; diff --git a/test/test_scalar_squoted.cpp b/test/test_scalar_squoted.cpp index 6a329be5..3cd5b4bf 100644 --- a/test/test_scalar_squoted.cpp +++ b/test/test_scalar_squoted.cpp @@ -283,6 +283,39 @@ void verify_error_is_reported(csubstr case_name, csubstr yaml, Location loc={}) }, loc); } +void verify_filter_error_is_reported(csubstr case_name, csubstr scalar_, Location loc={}) +{ + SCOPED_TRACE(case_name); + SCOPED_TRACE(scalar_); + { + Tree t; + ExpectError::do_check(&t, [&](){ + Parser::handler_type evt_handler = {}; + Parser parser(&evt_handler); + evt_handler.reset(&t, t.root_id()); + std::string buf(scalar_.begin(), scalar_.end()); + buf.resize(scalar_.len * 2); + substr scalar = to_substr(buf).first(scalar_.len); + to_substr(buf).sub(scalar_.len).fill('_'); + FilterResult result = parser.filter_scalar_squoted_in_place(scalar, buf.size()); + return result; + }, loc); + } + { + Tree t; + ExpectError::do_check(&t, [&](){ + Parser::handler_type evt_handler = {}; + Parser parser(&evt_handler); + evt_handler.reset(&t, t.root_id()); + std::string buf; + buf.resize(scalar_.len * 2); + to_substr(buf).fill('_'); + FilterResult result = parser.filter_scalar_squoted(scalar_, to_substr(buf)); + return result; + }, loc); + } +} + TEST(single_quoted, error_on_unmatched_quotes) { verify_error_is_reported("map block", R"(foo: '" @@ -363,6 +396,11 @@ bar: ''')"); verify_error_is_reported("seq flow", R"([what, '''])"); } +TEST(single_quoted, error_on_isolated_quotes) +{ + verify_filter_error_is_reported("isolated quotes", "a'a'a'a"); +} + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- diff --git a/test/test_seq.cpp b/test/test_seq.cpp index e092901b..3436c6bd 100644 --- a/test/test_seq.cpp +++ b/test/test_seq.cpp @@ -205,6 +205,25 @@ TEST(simple_seq, flow_tab_tokens__3_flow_tabs_everywhere) #endif // RYML_WITH_TAB_TOKENS +TEST(simple_seq, unterminated_seqimap) +{ + { + SCOPED_TRACE("space after"); + Tree t; + ExpectError::do_check(&t, [&]{ + parse_in_arena("[a: "); + }); + } + { + SCOPED_TRACE("no space after"); + Tree t; + ExpectError::do_check(&t, [&]{ + parse_in_arena("[a:"); + }); + } +} + + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- @@ -1081,6 +1100,14 @@ R"([ , , ] LineCol(1, 3) ); +ADD_CASE_TO_GROUP("simple seq flow, seqimap, at line end", +R"([a: + +b] +)", +N(SFS, L{N(MFS, L{N(KP|VP, "a", "b")})}) +); + } } // namespace yml diff --git a/test/test_tree.cpp b/test/test_tree.cpp index 42acd698..a6e9cfad 100644 --- a/test/test_tree.cpp +++ b/test/test_tree.cpp @@ -3395,6 +3395,84 @@ seq: &seq [*valref, bar] verify_assertion(t, [&](Tree const&){ return t.find_sibling(NONE, "foo"); }); } +TEST(Tree, depth_asc_desc) +{ + Tree t = parse_in_arena(R"(--- +map: {foo: *keyvalref, notag: none} +seq: &seq [*valref, bar] +...)"); + const size_t stream_id = t.root_id(); + const size_t doc_id = t.first_child(stream_id); + const size_t map_id = t.first_child(doc_id); + const size_t seq_id = t.last_child(doc_id); + const size_t map_child_id = t.first_child(map_id); + const size_t seq_child_id = t.first_child(seq_id); + ConstNodeRef stream = t.cref(stream_id); + ConstNodeRef doc = t.cref(doc_id); + ConstNodeRef map = t.cref(map_id); + ConstNodeRef seq = t.cref(seq_id); + ConstNodeRef map_child = t.cref(map_child_id); + ConstNodeRef seq_child = t.cref(seq_child_id); + NodeRef mstream = t.ref(stream_id); + NodeRef mdoc = t.ref(doc_id); + NodeRef mmap = t.ref(map_id); + NodeRef mseq = t.ref(seq_id); + NodeRef mmap_child = t.ref(map_child_id); + NodeRef mseq_child = t.ref(seq_child_id); + // + EXPECT_EQ(t.depth_asc(stream_id), id_type(0)); + EXPECT_EQ(t.depth_asc(doc_id), id_type(1)); + EXPECT_EQ(t.depth_asc(map_id), id_type(2)); + EXPECT_EQ(t.depth_asc(seq_id), id_type(2)); + EXPECT_EQ(t.depth_asc(map_child_id), id_type(3)); + EXPECT_EQ(t.depth_asc(seq_child_id), id_type(3)); + // + EXPECT_EQ(stream.depth_asc(), id_type(0)); + EXPECT_EQ(doc.depth_asc(), id_type(1)); + EXPECT_EQ(map.depth_asc(), id_type(2)); + EXPECT_EQ(seq.depth_asc(), id_type(2)); + EXPECT_EQ(map_child.depth_asc(), id_type(3)); + EXPECT_EQ(seq_child.depth_asc(), id_type(3)); + // + EXPECT_EQ(mstream.depth_asc(), id_type(0)); + EXPECT_EQ(mdoc.depth_asc(), id_type(1)); + EXPECT_EQ(mmap.depth_asc(), id_type(2)); + EXPECT_EQ(mseq.depth_asc(), id_type(2)); + EXPECT_EQ(mmap_child.depth_asc(), id_type(3)); + EXPECT_EQ(mseq_child.depth_asc(), id_type(3)); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].depth_asc(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).depth_asc(); }); + verify_assertion(t, [&](Tree const&){ return t.depth_asc(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.depth_asc(NONE); }); + // + EXPECT_EQ(t.depth_desc(stream_id), id_type(3)); + EXPECT_EQ(t.depth_desc(doc_id), id_type(2)); + EXPECT_EQ(t.depth_desc(map_id), id_type(1)); + EXPECT_EQ(t.depth_desc(seq_id), id_type(1)); + EXPECT_EQ(t.depth_desc(map_child_id), id_type(0)); + EXPECT_EQ(t.depth_desc(seq_child_id), id_type(0)); + // + EXPECT_EQ(stream.depth_desc(), id_type(3)); + EXPECT_EQ(doc.depth_desc(), id_type(2)); + EXPECT_EQ(map.depth_desc(), id_type(1)); + EXPECT_EQ(seq.depth_desc(), id_type(1)); + EXPECT_EQ(map_child.depth_desc(), id_type(0)); + EXPECT_EQ(seq_child.depth_desc(), id_type(0)); + // + EXPECT_EQ(mstream.depth_desc(), id_type(3)); + EXPECT_EQ(mdoc.depth_desc(), id_type(2)); + EXPECT_EQ(mmap.depth_desc(), id_type(1)); + EXPECT_EQ(mseq.depth_desc(), id_type(1)); + EXPECT_EQ(mmap_child.depth_desc(), id_type(0)); + EXPECT_EQ(mseq_child.depth_desc(), id_type(0)); + // + verify_assertion(t, [&](Tree const&){ return t.docref(0)["none"].depth_desc(); }); + verify_assertion(t, [&](Tree const&){ return t.docref(2).depth_desc(); }); + verify_assertion(t, [&](Tree const&){ return t.depth_desc(t.capacity()); }); + verify_assertion(t, [&](Tree const&){ return t.depth_desc(NONE); }); +} + //----------------------------------------------------------------------------- //-----------------------------------------------------------------------------