aboutsummaryrefslogtreecommitdiff
path: root/thirdparty/ryml/src/c4/yml/emit.def.hpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2025-11-07 14:49:13 +0100
committerGitHub Enterprise <[email protected]>2025-11-07 14:49:13 +0100
commit24e43a913f29ac3b314354e8ce5175f135bcc64f (patch)
treeca442937ceeb63461012b33a4576e9835099f106 /thirdparty/ryml/src/c4/yml/emit.def.hpp
parentget oplog attachments (#622) (diff)
downloadzen-24e43a913f29ac3b314354e8ce5175f135bcc64f.tar.xz
zen-24e43a913f29ac3b314354e8ce5175f135bcc64f.zip
switch to xmake for package management (#611)
This change removes our dependency on vcpkg for package management, in favour of bringing some code in-tree in the `thirdparty` folder as well as using the xmake build-in package management feature. For the latter, all the package definitions are maintained in the zen repo itself, in the `repo` folder. It should now also be easier to build the project as it will no longer depend on having the right version of vcpkg installed, which has been a common problem for new people coming in to the codebase. Now you should only need xmake to build. * Bumps xmake requirement on github runners to 2.9.9 to resolve an issue where xmake on Windows invokes cmake with `v144` toolchain which does not exist * BLAKE3 is now in-tree at `thirdparty/blake3` * cpr is now in-tree at `thirdparty/cpr` * cxxopts is now in-tree at `thirdparty/cxxopts` * fmt is now in-tree at `thirdparty/fmt` * robin-map is now in-tree at `thirdparty/robin-map` * ryml is now in-tree at `thirdparty/ryml` * sol2 is now in-tree at `thirdparty/sol2` * spdlog is now in-tree at `thirdparty/spdlog` * utfcpp is now in-tree at `thirdparty/utfcpp` * xmake package repo definitions is in `repo` * implemented support for sanitizers. ASAN is supported on windows, TSAN, UBSAN, MSAN etc are supported on Linux/MacOS though I have not yet tested it extensively on MacOS * the zencore encryption implementation also now supports using mbedTLS which is used on MacOS, though for now we still use openssl on Linux * crashpad * bumps libcurl to 8.11.0 (from 8.8.0) which should address a rare build upload bug
Diffstat (limited to 'thirdparty/ryml/src/c4/yml/emit.def.hpp')
-rw-r--r--thirdparty/ryml/src/c4/yml/emit.def.hpp960
1 files changed, 960 insertions, 0 deletions
diff --git a/thirdparty/ryml/src/c4/yml/emit.def.hpp b/thirdparty/ryml/src/c4/yml/emit.def.hpp
new file mode 100644
index 000000000..d262a9e2a
--- /dev/null
+++ b/thirdparty/ryml/src/c4/yml/emit.def.hpp
@@ -0,0 +1,960 @@
+#ifndef _C4_YML_EMIT_DEF_HPP_
+#define _C4_YML_EMIT_DEF_HPP_
+
+#ifndef _C4_YML_EMIT_HPP_
+#include "c4/yml/emit.hpp"
+#endif
+
+namespace c4 {
+namespace yml {
+
+template<class Writer>
+substr Emitter<Writer>::emit_as(EmitType_e type, Tree const& t, size_t id, bool error_on_excess)
+{
+ if(t.empty())
+ {
+ _RYML_CB_ASSERT(t.callbacks(), id == NONE);
+ return {};
+ }
+ _RYML_CB_CHECK(t.callbacks(), id < t.size());
+ m_tree = &t;
+ if(type == EMIT_YAML)
+ _emit_yaml(id);
+ else if(type == EMIT_JSON)
+ _do_visit_json(id);
+ else
+ _RYML_CB_ERR(m_tree->callbacks(), "unknown emit type");
+ return this->Writer::_get(error_on_excess);
+}
+
+template<class Writer>
+substr Emitter<Writer>::emit_as(EmitType_e type, Tree const& t, bool error_on_excess)
+{
+ if(t.empty())
+ return {};
+ return this->emit_as(type, t, t.root_id(), error_on_excess);
+}
+
+template<class Writer>
+substr Emitter<Writer>::emit_as(EmitType_e type, ConstNodeRef const& n, bool error_on_excess)
+{
+ _RYML_CB_CHECK(n.tree()->callbacks(), n.valid());
+ return this->emit_as(type, *n.tree(), n.id(), error_on_excess);
+}
+
+
+//-----------------------------------------------------------------------------
+
+template<class Writer>
+void Emitter<Writer>::_emit_yaml(size_t id)
+{
+ // save branches in the visitor by doing the initial stream/doc
+ // logic here, sparing the need to check stream/val/keyval inside
+ // the visitor functions
+ auto dispatch = [this](size_t node){
+ NodeType ty = m_tree->type(node);
+ if(ty.marked_flow_sl())
+ _do_visit_flow_sl(node, 0);
+ else if(ty.marked_flow_ml())
+ _do_visit_flow_ml(node, 0);
+ else
+ {
+ _do_visit_block(node, 0);
+ }
+ };
+ if(!m_tree->is_root(id))
+ {
+ if(m_tree->is_container(id) && !m_tree->type(id).marked_flow())
+ {
+ size_t ilevel = 0;
+ if(m_tree->has_key(id))
+ {
+ this->Writer::_do_write(m_tree->key(id));
+ this->Writer::_do_write(":\n");
+ ++ilevel;
+ }
+ _do_visit_block_container(id, ilevel, ilevel);
+ return;
+ }
+ }
+
+ auto *btd = m_tree->tag_directives().b;
+ auto *etd = m_tree->tag_directives().e;
+ auto write_tag_directives = [&btd, etd, this](size_t next_node){
+ auto end = btd;
+ while(end < etd)
+ {
+ if(end->next_node_id > next_node)
+ break;
+ ++end;
+ }
+ for( ; btd != end; ++btd)
+ {
+ if(next_node != m_tree->first_child(m_tree->parent(next_node)))
+ this->Writer::_do_write("...\n");
+ this->Writer::_do_write("%TAG ");
+ this->Writer::_do_write(btd->handle);
+ this->Writer::_do_write(' ');
+ this->Writer::_do_write(btd->prefix);
+ this->Writer::_do_write('\n');
+ }
+ };
+ if(m_tree->is_stream(id))
+ {
+ if(m_tree->first_child(id) != NONE)
+ write_tag_directives(m_tree->first_child(id));
+ for(size_t child = m_tree->first_child(id); child != NONE; child = m_tree->next_sibling(child))
+ {
+ dispatch(child);
+ if(m_tree->next_sibling(child) != NONE)
+ write_tag_directives(m_tree->next_sibling(child));
+ }
+ }
+ else if(m_tree->is_container(id))
+ {
+ dispatch(id);
+ }
+ else if(m_tree->is_doc(id))
+ {
+ _RYML_CB_ASSERT(m_tree->callbacks(), !m_tree->is_container(id)); // checked above
+ _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_val(id)); // so it must be a val
+ _write_doc(id);
+ }
+ else if(m_tree->is_keyval(id))
+ {
+ _writek(id, 0);
+ this->Writer::_do_write(": ");
+ _writev(id, 0);
+ if(!m_tree->type(id).marked_flow())
+ this->Writer::_do_write('\n');
+ }
+ else if(m_tree->is_val(id))
+ {
+ //this->Writer::_do_write("- ");
+ _writev(id, 0);
+ if(!m_tree->type(id).marked_flow())
+ this->Writer::_do_write('\n');
+ }
+ else if(m_tree->type(id) == NOTYPE)
+ {
+ ;
+ }
+ else
+ {
+ _RYML_CB_ERR(m_tree->callbacks(), "unknown type");
+ }
+}
+
+template<class Writer>
+void Emitter<Writer>::_write_doc(size_t id)
+{
+ RYML_ASSERT(m_tree->is_doc(id));
+ 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(m_tree->has_val_tag(id))
+ {
+ if(!m_tree->is_root(id))
+ this->Writer::_do_write(' ');
+ _write_tag(m_tree->val_tag(id));
+ }
+ if(m_tree->has_val_anchor(id))
+ {
+ if(!m_tree->is_root(id))
+ this->Writer::_do_write(' ');
+ this->Writer::_do_write('&');
+ this->Writer::_do_write(m_tree->val_anchor(id));
+ }
+ }
+ else // docval
+ {
+ RYML_ASSERT(m_tree->has_val(id));
+ RYML_ASSERT(!m_tree->has_key(id));
+ if(!m_tree->is_root(id))
+ this->Writer::_do_write(' ');
+ _writev(id, 0);
+ }
+ this->Writer::_do_write('\n');
+}
+
+template<class Writer>
+void Emitter<Writer>::_do_visit_flow_sl(size_t node, size_t ilevel)
+{
+ 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(m_tree->is_doc(node))
+ {
+ _write_doc(node);
+ if(!m_tree->has_children(node))
+ return;
+ }
+ else if(m_tree->is_container(node))
+ {
+ RYML_ASSERT(m_tree->is_map(node) || m_tree->is_seq(node));
+
+ bool spc = false; // write a space
+
+ if(m_tree->has_key(node))
+ {
+ _writek(node, ilevel);
+ this->Writer::_do_write(':');
+ spc = true;
+ }
+
+ if(m_tree->has_val_tag(node))
+ {
+ if(spc)
+ this->Writer::_do_write(' ');
+ _write_tag(m_tree->val_tag(node));
+ spc = true;
+ }
+
+ if(m_tree->has_val_anchor(node))
+ {
+ if(spc)
+ this->Writer::_do_write(' ');
+ this->Writer::_do_write('&');
+ this->Writer::_do_write(m_tree->val_anchor(node));
+ spc = true;
+ }
+
+ if(spc)
+ this->Writer::_do_write(' ');
+
+ if(m_tree->is_map(node))
+ {
+ this->Writer::_do_write('{');
+ }
+ else
+ {
+ _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_seq(node));
+ this->Writer::_do_write('[');
+ }
+ } // container
+
+ for(size_t child = m_tree->first_child(node), count = 0; child != NONE; child = m_tree->next_sibling(child))
+ {
+ if(count++)
+ this->Writer::_do_write(',');
+ if(m_tree->is_keyval(child))
+ {
+ _writek(child, ilevel);
+ this->Writer::_do_write(": ");
+ _writev(child, ilevel);
+ }
+ else if(m_tree->is_val(child))
+ {
+ _writev(child, ilevel);
+ }
+ else
+ {
+ // with single-line flow, we can never go back to block
+ _do_visit_flow_sl(child, ilevel + 1);
+ }
+ }
+
+ if(m_tree->is_map(node))
+ {
+ this->Writer::_do_write('}');
+ }
+ else if(m_tree->is_seq(node))
+ {
+ this->Writer::_do_write(']');
+ }
+}
+
+template<class Writer>
+void Emitter<Writer>::_do_visit_flow_ml(size_t id, size_t ilevel, size_t do_indent)
+{
+ C4_UNUSED(id);
+ C4_UNUSED(ilevel);
+ C4_UNUSED(do_indent);
+ RYML_CHECK(false/*not implemented*/);
+}
+
+template<class Writer>
+void Emitter<Writer>::_do_visit_block_container(size_t node, size_t next_level, size_t do_indent)
+{
+ RepC ind = indent_to(do_indent * next_level);
+
+ if(m_tree->is_seq(node))
+ {
+ for(size_t child = m_tree->first_child(node); child != NONE; child = m_tree->next_sibling(child))
+ {
+ _RYML_CB_ASSERT(m_tree->callbacks(), !m_tree->has_key(child));
+ if(m_tree->is_val(child))
+ {
+ this->Writer::_do_write(ind);
+ this->Writer::_do_write("- ");
+ _writev(child, next_level);
+ this->Writer::_do_write('\n');
+ }
+ else
+ {
+ _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_container(child));
+ NodeType ty = m_tree->type(child);
+ if(ty.marked_flow_sl())
+ {
+ this->Writer::_do_write(ind);
+ this->Writer::_do_write("- ");
+ _do_visit_flow_sl(child, 0u);
+ this->Writer::_do_write('\n');
+ }
+ else if(ty.marked_flow_ml())
+ {
+ this->Writer::_do_write(ind);
+ this->Writer::_do_write("- ");
+ _do_visit_flow_ml(child, next_level, do_indent);
+ this->Writer::_do_write('\n');
+ }
+ else
+ {
+ _do_visit_block(child, next_level, do_indent);
+ }
+ }
+ do_indent = true;
+ ind = indent_to(do_indent * next_level);
+ }
+ }
+ else // map
+ {
+ _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_map(node));
+ for(size_t ich = m_tree->first_child(node); ich != NONE; ich = m_tree->next_sibling(ich))
+ {
+ _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->has_key(ich));
+ if(m_tree->is_keyval(ich))
+ {
+ this->Writer::_do_write(ind);
+ _writek(ich, next_level);
+ this->Writer::_do_write(": ");
+ _writev(ich, next_level);
+ this->Writer::_do_write('\n');
+ }
+ else
+ {
+ _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_container(ich));
+ NodeType ty = m_tree->type(ich);
+ if(ty.marked_flow_sl())
+ {
+ this->Writer::_do_write(ind);
+ _do_visit_flow_sl(ich, 0u);
+ this->Writer::_do_write('\n');
+ }
+ else if(ty.marked_flow_ml())
+ {
+ this->Writer::_do_write(ind);
+ _do_visit_flow_ml(ich, 0u);
+ this->Writer::_do_write('\n');
+ }
+ else
+ {
+ _do_visit_block(ich, next_level, do_indent);
+ }
+ }
+ do_indent = true;
+ ind = indent_to(do_indent * next_level);
+ }
+ }
+}
+
+template<class Writer>
+void Emitter<Writer>::_do_visit_block(size_t node, size_t ilevel, size_t 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)));
+ RepC ind = indent_to(do_indent * ilevel);
+
+ if(m_tree->is_doc(node))
+ {
+ _write_doc(node);
+ if(!m_tree->has_children(node))
+ return;
+ }
+ else if(m_tree->is_container(node))
+ {
+ RYML_ASSERT(m_tree->is_map(node) || m_tree->is_seq(node));
+
+ bool spc = false; // write a space
+ bool nl = false; // write a newline
+
+ if(m_tree->has_key(node))
+ {
+ this->Writer::_do_write(ind);
+ _writek(node, ilevel);
+ this->Writer::_do_write(':');
+ spc = true;
+ }
+ else if(!m_tree->is_root(node))
+ {
+ this->Writer::_do_write(ind);
+ this->Writer::_do_write('-');
+ spc = true;
+ }
+
+ if(m_tree->has_val_tag(node))
+ {
+ if(spc)
+ this->Writer::_do_write(' ');
+ _write_tag(m_tree->val_tag(node));
+ spc = true;
+ nl = true;
+ }
+
+ if(m_tree->has_val_anchor(node))
+ {
+ if(spc)
+ this->Writer::_do_write(' ');
+ this->Writer::_do_write('&');
+ this->Writer::_do_write(m_tree->val_anchor(node));
+ spc = true;
+ nl = true;
+ }
+
+ if(m_tree->has_children(node))
+ {
+ if(m_tree->has_key(node))
+ nl = true;
+ else
+ if(!m_tree->is_root(node) && !nl)
+ spc = true;
+ }
+ else
+ {
+ if(m_tree->is_seq(node))
+ this->Writer::_do_write(" []\n");
+ else if(m_tree->is_map(node))
+ this->Writer::_do_write(" {}\n");
+ return;
+ }
+
+ if(spc && !nl)
+ this->Writer::_do_write(' ');
+
+ do_indent = 0;
+ if(nl)
+ {
+ this->Writer::_do_write('\n');
+ do_indent = 1;
+ }
+ } // container
+
+ size_t next_level = ilevel + 1;
+ 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);
+}
+
+template<class Writer>
+void Emitter<Writer>::_do_visit_json(size_t id)
+{
+ _RYML_CB_CHECK(m_tree->callbacks(), !m_tree->is_stream(id)); // JSON does not have streams
+ if(m_tree->is_keyval(id))
+ {
+ _writek_json(id);
+ this->Writer::_do_write(": ");
+ _writev_json(id);
+ }
+ else if(m_tree->is_val(id))
+ {
+ _writev_json(id);
+ }
+ else if(m_tree->is_container(id))
+ {
+ if(m_tree->has_key(id))
+ {
+ _writek_json(id);
+ this->Writer::_do_write(": ");
+ }
+ if(m_tree->is_seq(id))
+ this->Writer::_do_write('[');
+ else if(m_tree->is_map(id))
+ this->Writer::_do_write('{');
+ } // container
+
+ for(size_t ich = m_tree->first_child(id); ich != NONE; ich = m_tree->next_sibling(ich))
+ {
+ if(ich != m_tree->first_child(id))
+ this->Writer::_do_write(',');
+ _do_visit_json(ich);
+ }
+
+ if(m_tree->is_seq(id))
+ this->Writer::_do_write(']');
+ else if(m_tree->is_map(id))
+ this->Writer::_do_write('}');
+}
+
+template<class Writer>
+void Emitter<Writer>::_write(NodeScalar const& C4_RESTRICT sc, NodeType flags, size_t ilevel)
+{
+ if( ! sc.tag.empty())
+ {
+ _write_tag(sc.tag);
+ this->Writer::_do_write(' ');
+ }
+ if(flags.has_anchor())
+ {
+ RYML_ASSERT(flags.is_ref() != flags.has_anchor());
+ RYML_ASSERT( ! sc.anchor.empty());
+ this->Writer::_do_write('&');
+ this->Writer::_do_write(sc.anchor);
+ this->Writer::_do_write(' ');
+ }
+ else if(flags.is_ref())
+ {
+ if(sc.anchor != "<<")
+ this->Writer::_do_write('*');
+ this->Writer::_do_write(sc.anchor);
+ return;
+ }
+
+ // ensure the style flags only have one of KEY or VAL
+ _RYML_CB_ASSERT(m_tree->callbacks(), ((flags & (_WIP_KEY_STYLE|_WIP_VAL_STYLE)) == 0) || (((flags&_WIP_KEY_STYLE) == 0) != ((flags&_WIP_VAL_STYLE) == 0)));
+
+ auto style_marks = flags & (_WIP_KEY_STYLE|_WIP_VAL_STYLE);
+ if(style_marks & (_WIP_KEY_LITERAL|_WIP_VAL_LITERAL))
+ {
+ _write_scalar_literal(sc.scalar, ilevel, flags.has_key());
+ }
+ else if(style_marks & (_WIP_KEY_FOLDED|_WIP_VAL_FOLDED))
+ {
+ _write_scalar_folded(sc.scalar, ilevel, flags.has_key());
+ }
+ else if(style_marks & (_WIP_KEY_SQUO|_WIP_VAL_SQUO))
+ {
+ _write_scalar_squo(sc.scalar, ilevel);
+ }
+ else if(style_marks & (_WIP_KEY_DQUO|_WIP_VAL_DQUO))
+ {
+ _write_scalar_dquo(sc.scalar, ilevel);
+ }
+ else if(style_marks & (_WIP_KEY_PLAIN|_WIP_VAL_PLAIN))
+ {
+ _write_scalar_plain(sc.scalar, ilevel);
+ }
+ else if(!style_marks)
+ {
+ size_t first_non_nl = sc.scalar.first_not_of('\n');
+ bool all_newlines = first_non_nl == npos;
+ bool has_leading_ws = (!all_newlines) && sc.scalar.sub(first_non_nl).begins_with_any(" \t");
+ bool do_literal = ((!sc.scalar.empty() && all_newlines) || (has_leading_ws && !sc.scalar.trim(' ').empty()));
+ if(do_literal)
+ {
+ _write_scalar_literal(sc.scalar, ilevel, flags.has_key(), /*explicit_indentation*/has_leading_ws);
+ }
+ else
+ {
+ for(size_t i = 0; i < sc.scalar.len; ++i)
+ {
+ if(sc.scalar.str[i] == '\n')
+ {
+ _write_scalar_literal(sc.scalar, ilevel, flags.has_key(), /*explicit_indentation*/has_leading_ws);
+ goto wrote_special;
+ }
+ // todo: check for escaped characters requiring double quotes
+ }
+ _write_scalar(sc.scalar, flags.is_quoted());
+ wrote_special:
+ ;
+ }
+ }
+ else
+ {
+ _RYML_CB_ERR(m_tree->callbacks(), "not implemented");
+ }
+}
+template<class Writer>
+void Emitter<Writer>::_write_json(NodeScalar const& C4_RESTRICT sc, NodeType flags)
+{
+ if(C4_UNLIKELY( ! sc.tag.empty()))
+ _RYML_CB_ERR(m_tree->callbacks(), "JSON does not have tags");
+ if(C4_UNLIKELY(flags.has_anchor()))
+ _RYML_CB_ERR(m_tree->callbacks(), "JSON does not have anchors");
+ _write_scalar_json(sc.scalar, flags.has_key(), flags.is_quoted());
+}
+
+#define _rymlindent_nextline() for(size_t lv = 0; lv < ilevel+1; ++lv) { this->Writer::_do_write(' '); this->Writer::_do_write(' '); }
+
+template<class Writer>
+void Emitter<Writer>::_write_scalar_literal(csubstr s, size_t ilevel, bool explicit_key, bool explicit_indentation)
+{
+ if(explicit_key)
+ this->Writer::_do_write("? ");
+ csubstr trimmed = s.trimr("\n\r");
+ size_t numnewlines_at_end = s.len - trimmed.len - s.sub(trimmed.len).count('\r');
+ //
+ if(!explicit_indentation)
+ this->Writer::_do_write('|');
+ else
+ this->Writer::_do_write("|2");
+ //
+ if(numnewlines_at_end > 1 || (trimmed.len == 0 && s.len > 0)/*only newlines*/)
+ this->Writer::_do_write("+\n");
+ else if(numnewlines_at_end == 1)
+ this->Writer::_do_write('\n');
+ else
+ this->Writer::_do_write("-\n");
+ //
+ if(trimmed.len)
+ {
+ size_t pos = 0; // tracks the last character that was already written
+ for(size_t i = 0; i < trimmed.len; ++i)
+ {
+ if(trimmed[i] != '\n')
+ continue;
+ // write everything up to this point
+ csubstr since_pos = trimmed.range(pos, i+1); // include the newline
+ _rymlindent_nextline()
+ this->Writer::_do_write(since_pos);
+ pos = i+1; // already written
+ }
+ if(pos < trimmed.len)
+ {
+ _rymlindent_nextline()
+ this->Writer::_do_write(trimmed.sub(pos));
+ }
+ if(numnewlines_at_end)
+ {
+ this->Writer::_do_write('\n');
+ --numnewlines_at_end;
+ }
+ }
+ for(size_t i = 0; i < numnewlines_at_end; ++i)
+ {
+ _rymlindent_nextline()
+ if(i+1 < numnewlines_at_end || explicit_key)
+ this->Writer::_do_write('\n');
+ }
+ if(explicit_key && !numnewlines_at_end)
+ this->Writer::_do_write('\n');
+}
+
+template<class Writer>
+void Emitter<Writer>::_write_scalar_folded(csubstr s, size_t ilevel, bool explicit_key)
+{
+ if(explicit_key)
+ {
+ this->Writer::_do_write("? ");
+ }
+ RYML_ASSERT(s.find("\r") == csubstr::npos);
+ csubstr trimmed = s.trimr('\n');
+ size_t numnewlines_at_end = s.len - trimmed.len;
+ if(numnewlines_at_end == 0)
+ {
+ this->Writer::_do_write(">-\n");
+ }
+ else if(numnewlines_at_end == 1)
+ {
+ this->Writer::_do_write(">\n");
+ }
+ else if(numnewlines_at_end > 1)
+ {
+ this->Writer::_do_write(">+\n");
+ }
+ if(trimmed.len)
+ {
+ size_t pos = 0; // tracks the last character that was already written
+ for(size_t i = 0; i < trimmed.len; ++i)
+ {
+ if(trimmed[i] != '\n')
+ continue;
+ // write everything up to this point
+ csubstr since_pos = trimmed.range(pos, i+1); // include the newline
+ pos = i+1; // because of the newline
+ _rymlindent_nextline()
+ this->Writer::_do_write(since_pos);
+ this->Writer::_do_write('\n'); // write the newline twice
+ }
+ if(pos < trimmed.len)
+ {
+ _rymlindent_nextline()
+ this->Writer::_do_write(trimmed.sub(pos));
+ }
+ if(numnewlines_at_end)
+ {
+ this->Writer::_do_write('\n');
+ --numnewlines_at_end;
+ }
+ }
+ for(size_t i = 0; i < numnewlines_at_end; ++i)
+ {
+ _rymlindent_nextline()
+ if(i+1 < numnewlines_at_end || explicit_key)
+ this->Writer::_do_write('\n');
+ }
+ if(explicit_key && !numnewlines_at_end)
+ this->Writer::_do_write('\n');
+}
+
+template<class Writer>
+void Emitter<Writer>::_write_scalar_squo(csubstr s, size_t ilevel)
+{
+ size_t pos = 0; // tracks the last character that was already written
+ this->Writer::_do_write('\'');
+ for(size_t i = 0; i < s.len; ++i)
+ {
+ if(s[i] == '\n')
+ {
+ csubstr sub = s.range(pos, i+1);
+ this->Writer::_do_write(sub); // write everything up to (including) this char
+ this->Writer::_do_write('\n'); // write the character again
+ if(i + 1 < s.len)
+ _rymlindent_nextline() // indent the next line
+ pos = i+1;
+ }
+ else if(s[i] == '\'')
+ {
+ csubstr sub = s.range(pos, i+1);
+ this->Writer::_do_write(sub); // write everything up to (including) this char
+ this->Writer::_do_write('\''); // write the character again
+ pos = i+1;
+ }
+ }
+ // write missing characters at the end of the string
+ if(pos < s.len)
+ this->Writer::_do_write(s.sub(pos));
+ this->Writer::_do_write('\'');
+}
+
+template<class Writer>
+void Emitter<Writer>::_write_scalar_dquo(csubstr s, size_t ilevel)
+{
+ size_t pos = 0; // tracks the last character that was already written
+ this->Writer::_do_write('"');
+ for(size_t i = 0; i < s.len; ++i)
+ {
+ const char curr = s.str[i];
+ if(curr == '"' || curr == '\\')
+ {
+ csubstr sub = s.range(pos, i);
+ this->Writer::_do_write(sub); // write everything up to (excluding) this char
+ this->Writer::_do_write('\\'); // write the escape
+ this->Writer::_do_write(curr); // write the char
+ pos = i+1;
+ }
+ else if(s[i] == '\n')
+ {
+ csubstr sub = s.range(pos, i+1);
+ this->Writer::_do_write(sub); // write everything up to (including) this newline
+ this->Writer::_do_write('\n'); // write the newline again
+ if(i + 1 < s.len)
+ _rymlindent_nextline() // indent the next line
+ pos = i+1;
+ if(i+1 < s.len) // escape leading whitespace after the newline
+ {
+ const char next = s.str[i+1];
+ if(next == ' ' || next == '\t')
+ this->Writer::_do_write('\\');
+ }
+ }
+ else if(curr == ' ' || curr == '\t')
+ {
+ // escape trailing whitespace before a newline
+ size_t next = s.first_not_of(" \t\r", i);
+ if(next != npos && s[next] == '\n')
+ {
+ csubstr sub = s.range(pos, i);
+ this->Writer::_do_write(sub); // write everything up to (excluding) this char
+ this->Writer::_do_write('\\'); // escape the whitespace
+ pos = i;
+ }
+ }
+ else if(C4_UNLIKELY(curr == '\r'))
+ {
+ csubstr sub = s.range(pos, i);
+ this->Writer::_do_write(sub); // write everything up to (excluding) this char
+ this->Writer::_do_write("\\r"); // write the escaped char
+ pos = i+1;
+ }
+ }
+ // write missing characters at the end of the string
+ if(pos < s.len)
+ {
+ csubstr sub = s.sub(pos);
+ this->Writer::_do_write(sub);
+ }
+ this->Writer::_do_write('"');
+}
+
+template<class Writer>
+void Emitter<Writer>::_write_scalar_plain(csubstr s, size_t ilevel)
+{
+ size_t pos = 0; // tracks the last character that was already written
+ for(size_t i = 0; i < s.len; ++i)
+ {
+ const char curr = s.str[i];
+ if(curr == '\n')
+ {
+ csubstr sub = s.range(pos, i+1);
+ this->Writer::_do_write(sub); // write everything up to (including) this newline
+ this->Writer::_do_write('\n'); // write the newline again
+ if(i + 1 < s.len)
+ _rymlindent_nextline() // indent the next line
+ pos = i+1;
+ }
+ }
+ // write missing characters at the end of the string
+ if(pos < s.len)
+ {
+ csubstr sub = s.sub(pos);
+ this->Writer::_do_write(sub);
+ }
+}
+
+#undef _rymlindent_nextline
+
+template<class Writer>
+void Emitter<Writer>::_write_scalar(csubstr s, bool was_quoted)
+{
+ // this block of code needed to be moved to before the needs_quotes
+ // assignment to work around a g++ optimizer bug where (s.str != nullptr)
+ // was evaluated as true even if s.str was actually a nullptr (!!!)
+ if(s.len == size_t(0))
+ {
+ if(was_quoted || s.str != nullptr)
+ this->Writer::_do_write("''");
+ return;
+ }
+
+ const bool needs_quotes = (
+ was_quoted
+ ||
+ (
+ ( ! s.is_number())
+ &&
+ (
+ // has leading whitespace
+ // looks like reference or anchor
+ // would be treated as a directive
+ // see https://www.yaml.info/learn/quote.html#noplain
+ s.begins_with_any(" \n\t\r*&%@`")
+ ||
+ s.begins_with("<<")
+ ||
+ // has trailing whitespace
+ s.ends_with_any(" \n\t\r")
+ ||
+ // has special chars
+ (s.first_of("#:-?,\n{}[]'\"") != npos)
+ )
+ )
+ );
+
+ if( ! needs_quotes)
+ {
+ this->Writer::_do_write(s);
+ }
+ else
+ {
+ const bool has_dquotes = s.first_of( '"') != npos;
+ const bool has_squotes = s.first_of('\'') != npos;
+ if(!has_squotes && has_dquotes)
+ {
+ this->Writer::_do_write('\'');
+ this->Writer::_do_write(s);
+ this->Writer::_do_write('\'');
+ }
+ else if(has_squotes && !has_dquotes)
+ {
+ RYML_ASSERT(s.count('\n') == 0);
+ this->Writer::_do_write('"');
+ this->Writer::_do_write(s);
+ this->Writer::_do_write('"');
+ }
+ else
+ {
+ _write_scalar_squo(s, /*FIXME FIXME FIXME*/0);
+ }
+ }
+}
+template<class Writer>
+void Emitter<Writer>::_write_scalar_json(csubstr s, bool as_key, bool use_quotes)
+{
+ if((!use_quotes)
+ // json keys require quotes
+ && (!as_key)
+ && (
+ // do not quote special cases
+ (s == "true" || s == "false" || s == "null")
+ || (
+ // do not quote numbers
+ (s.is_number()
+ && (
+ // quote integral numbers if they have a leading 0
+ // https://github.com/biojppm/rapidyaml/issues/291
+ (!(s.len > 1 && s.begins_with('0')))
+ // do not quote reals with leading 0
+ // https://github.com/biojppm/rapidyaml/issues/313
+ || (s.find('.') != csubstr::npos) ))
+ )
+ )
+ )
+ {
+ this->Writer::_do_write(s);
+ }
+ else
+ {
+ size_t pos = 0;
+ this->Writer::_do_write('"');
+ for(size_t i = 0; i < s.len; ++i)
+ {
+ switch(s.str[i])
+ {
+ case '"':
+ this->Writer ::_do_write(s.range(pos, i));
+ this->Writer ::_do_write("\\\"");
+ pos = i + 1;
+ break;
+ case '\n':
+ this->Writer ::_do_write(s.range(pos, i));
+ this->Writer ::_do_write("\\n");
+ pos = i + 1;
+ break;
+ case '\t':
+ this->Writer ::_do_write(s.range(pos, i));
+ this->Writer ::_do_write("\\t");
+ pos = i + 1;
+ break;
+ case '\\':
+ this->Writer ::_do_write(s.range(pos, i));
+ this->Writer ::_do_write("\\\\");
+ pos = i + 1;
+ break;
+ case '\r':
+ this->Writer ::_do_write(s.range(pos, i));
+ this->Writer ::_do_write("\\r");
+ pos = i + 1;
+ break;
+ case '\b':
+ this->Writer ::_do_write(s.range(pos, i));
+ this->Writer ::_do_write("\\b");
+ pos = i + 1;
+ break;
+ case '\f':
+ this->Writer ::_do_write(s.range(pos, i));
+ this->Writer ::_do_write("\\f");
+ pos = i + 1;
+ break;
+ }
+ }
+ if(pos < s.len)
+ {
+ csubstr sub = s.sub(pos);
+ this->Writer::_do_write(sub);
+ }
+ this->Writer::_do_write('"');
+ }
+}
+
+} // namespace yml
+} // namespace c4
+
+#endif /* _C4_YML_EMIT_DEF_HPP_ */