duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

emit.def.hpp (29539B)


      1 #ifndef _C4_YML_EMIT_DEF_HPP_
      2 #define _C4_YML_EMIT_DEF_HPP_
      3 
      4 #ifndef _C4_YML_EMIT_HPP_
      5 #include "c4/yml/emit.hpp"
      6 #endif
      7 
      8 namespace c4 {
      9 namespace yml {
     10 
     11 template<class Writer>
     12 substr Emitter<Writer>::emit_as(EmitType_e type, Tree const& t, size_t id, bool error_on_excess)
     13 {
     14     if(t.empty())
     15     {
     16         _RYML_CB_ASSERT(t.callbacks(), id == NONE);
     17         return {};
     18     }
     19     _RYML_CB_CHECK(t.callbacks(), id < t.capacity());
     20     m_tree = &t;
     21     if(type == EMIT_YAML)
     22         _emit_yaml(id);
     23     else if(type == EMIT_JSON)
     24         _do_visit_json(id);
     25     else
     26         _RYML_CB_ERR(m_tree->callbacks(), "unknown emit type");
     27     return this->Writer::_get(error_on_excess);
     28 }
     29 
     30 template<class Writer>
     31 substr Emitter<Writer>::emit_as(EmitType_e type, Tree const& t, bool error_on_excess)
     32 {
     33     if(t.empty())
     34         return {};
     35     return this->emit_as(type, t, t.root_id(), error_on_excess);
     36 }
     37 
     38 template<class Writer>
     39 substr Emitter<Writer>::emit_as(EmitType_e type, ConstNodeRef const& n, bool error_on_excess)
     40 {
     41     _RYML_CB_CHECK(n.tree()->callbacks(), n.valid());
     42     return this->emit_as(type, *n.tree(), n.id(), error_on_excess);
     43 }
     44 
     45 
     46 //-----------------------------------------------------------------------------
     47 
     48 template<class Writer>
     49 void Emitter<Writer>::_emit_yaml(size_t id)
     50 {
     51     // save branches in the visitor by doing the initial stream/doc
     52     // logic here, sparing the need to check stream/val/keyval inside
     53     // the visitor functions
     54     auto dispatch = [this](size_t node){
     55         NodeType ty = m_tree->type(node);
     56         if(ty.marked_flow_sl())
     57             _do_visit_flow_sl(node, 0);
     58         else if(ty.marked_flow_ml())
     59             _do_visit_flow_ml(node, 0);
     60         else
     61         {
     62             _do_visit_block(node, 0);
     63         }
     64     };
     65     if(!m_tree->is_root(id))
     66     {
     67         if(m_tree->is_container(id) && !m_tree->type(id).marked_flow())
     68         {
     69             size_t ilevel = 0;
     70             if(m_tree->has_key(id))
     71             {
     72                 this->Writer::_do_write(m_tree->key(id));
     73                 this->Writer::_do_write(":\n");
     74                 ++ilevel;
     75             }
     76             _do_visit_block_container(id, ilevel, ilevel);
     77             return;
     78         }
     79     }
     80 
     81     auto *btd = m_tree->tag_directives().b;
     82     auto *etd = m_tree->tag_directives().e;
     83     auto write_tag_directives = [&btd, etd, this](size_t next_node){
     84         auto end = btd;
     85         while(end < etd)
     86         {
     87             if(end->next_node_id > next_node)
     88                 break;
     89             ++end;
     90         }
     91         for( ; btd != end; ++btd)
     92         {
     93             if(next_node != m_tree->first_child(m_tree->parent(next_node)))
     94                 this->Writer::_do_write("...\n");
     95             this->Writer::_do_write("%TAG ");
     96             this->Writer::_do_write(btd->handle);
     97             this->Writer::_do_write(' ');
     98             this->Writer::_do_write(btd->prefix);
     99             this->Writer::_do_write('\n');
    100         }
    101     };
    102     if(m_tree->is_stream(id))
    103     {
    104         if(m_tree->first_child(id) != NONE)
    105             write_tag_directives(m_tree->first_child(id));
    106         for(size_t child = m_tree->first_child(id); child != NONE; child = m_tree->next_sibling(child))
    107         {
    108             dispatch(child);
    109             if(m_tree->next_sibling(child) != NONE)
    110                 write_tag_directives(m_tree->next_sibling(child));
    111         }
    112     }
    113     else if(m_tree->is_container(id))
    114     {
    115         dispatch(id);
    116     }
    117     else if(m_tree->is_doc(id))
    118     {
    119         _RYML_CB_ASSERT(m_tree->callbacks(), !m_tree->is_container(id)); // checked above
    120         _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_val(id)); // so it must be a val
    121         _write_doc(id);
    122     }
    123     else if(m_tree->is_keyval(id))
    124     {
    125         _writek(id, 0);
    126         this->Writer::_do_write(": ");
    127         _writev(id, 0);
    128         if(!m_tree->type(id).marked_flow())
    129             this->Writer::_do_write('\n');
    130     }
    131     else if(m_tree->is_val(id))
    132     {
    133         //this->Writer::_do_write("- ");
    134         _writev(id, 0);
    135         if(!m_tree->type(id).marked_flow())
    136             this->Writer::_do_write('\n');
    137     }
    138     else if(m_tree->type(id) == NOTYPE)
    139     {
    140         ;
    141     }
    142     else
    143     {
    144         _RYML_CB_ERR(m_tree->callbacks(), "unknown type");
    145     }
    146 }
    147 
    148 template<class Writer>
    149 void Emitter<Writer>::_write_doc(size_t id)
    150 {
    151     RYML_ASSERT(m_tree->is_doc(id));
    152     if(!m_tree->is_root(id))
    153     {
    154         RYML_ASSERT(m_tree->is_stream(m_tree->parent(id)));
    155         this->Writer::_do_write("---");
    156     }
    157     if(!m_tree->has_val(id)) // this is more frequent
    158     {
    159         if(m_tree->has_val_tag(id))
    160         {
    161             if(!m_tree->is_root(id))
    162                 this->Writer::_do_write(' ');
    163             _write_tag(m_tree->val_tag(id));
    164         }
    165         if(m_tree->has_val_anchor(id))
    166         {
    167             if(!m_tree->is_root(id))
    168                 this->Writer::_do_write(' ');
    169             this->Writer::_do_write('&');
    170             this->Writer::_do_write(m_tree->val_anchor(id));
    171         }
    172     }
    173     else // docval
    174     {
    175         RYML_ASSERT(m_tree->has_val(id));
    176         RYML_ASSERT(!m_tree->has_key(id));
    177         if(!m_tree->is_root(id))
    178             this->Writer::_do_write(' ');
    179         _writev(id, 0);
    180     }
    181     this->Writer::_do_write('\n');
    182 }
    183 
    184 template<class Writer>
    185 void Emitter<Writer>::_do_visit_flow_sl(size_t node, size_t ilevel)
    186 {
    187     RYML_ASSERT(!m_tree->is_stream(node));
    188     RYML_ASSERT(m_tree->is_container(node) || m_tree->is_doc(node));
    189     RYML_ASSERT(m_tree->is_root(node) || (m_tree->parent_is_map(node) || m_tree->parent_is_seq(node)));
    190 
    191     if(m_tree->is_doc(node))
    192     {
    193         _write_doc(node);
    194         if(!m_tree->has_children(node))
    195             return;
    196     }
    197     else if(m_tree->is_container(node))
    198     {
    199         RYML_ASSERT(m_tree->is_map(node) || m_tree->is_seq(node));
    200 
    201         bool spc = false; // write a space
    202 
    203         if(m_tree->has_key(node))
    204         {
    205             _writek(node, ilevel);
    206             this->Writer::_do_write(':');
    207             spc = true;
    208         }
    209 
    210         if(m_tree->has_val_tag(node))
    211         {
    212             if(spc)
    213                 this->Writer::_do_write(' ');
    214             _write_tag(m_tree->val_tag(node));
    215             spc = true;
    216         }
    217 
    218         if(m_tree->has_val_anchor(node))
    219         {
    220             if(spc)
    221                 this->Writer::_do_write(' ');
    222             this->Writer::_do_write('&');
    223             this->Writer::_do_write(m_tree->val_anchor(node));
    224             spc = true;
    225         }
    226 
    227         if(spc)
    228             this->Writer::_do_write(' ');
    229 
    230         if(m_tree->is_map(node))
    231         {
    232             this->Writer::_do_write('{');
    233         }
    234         else
    235         {
    236             _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_seq(node));
    237             this->Writer::_do_write('[');
    238         }
    239     } // container
    240 
    241     for(size_t child = m_tree->first_child(node), count = 0; child != NONE; child = m_tree->next_sibling(child))
    242     {
    243         if(count++)
    244             this->Writer::_do_write(',');
    245         if(m_tree->is_keyval(child))
    246         {
    247             _writek(child, ilevel);
    248             this->Writer::_do_write(": ");
    249             _writev(child, ilevel);
    250         }
    251         else if(m_tree->is_val(child))
    252         {
    253             _writev(child, ilevel);
    254         }
    255         else
    256         {
    257             // with single-line flow, we can never go back to block
    258             _do_visit_flow_sl(child, ilevel + 1);
    259         }
    260     }
    261 
    262     if(m_tree->is_map(node))
    263     {
    264         this->Writer::_do_write('}');
    265     }
    266     else if(m_tree->is_seq(node))
    267     {
    268         this->Writer::_do_write(']');
    269     }
    270 }
    271 
    272 template<class Writer>
    273 void Emitter<Writer>::_do_visit_flow_ml(size_t id, size_t ilevel, size_t do_indent)
    274 {
    275     C4_UNUSED(id);
    276     C4_UNUSED(ilevel);
    277     C4_UNUSED(do_indent);
    278     RYML_CHECK(false/*not implemented*/);
    279 }
    280 
    281 template<class Writer>
    282 void Emitter<Writer>::_do_visit_block_container(size_t node, size_t next_level, size_t do_indent)
    283 {
    284     RepC ind = indent_to(do_indent * next_level);
    285 
    286     if(m_tree->is_seq(node))
    287     {
    288         for(size_t child = m_tree->first_child(node); child != NONE; child = m_tree->next_sibling(child))
    289         {
    290             _RYML_CB_ASSERT(m_tree->callbacks(), !m_tree->has_key(child));
    291             if(m_tree->is_val(child))
    292             {
    293                 this->Writer::_do_write(ind);
    294                 this->Writer::_do_write("- ");
    295                 _writev(child, next_level);
    296                 this->Writer::_do_write('\n');
    297             }
    298             else
    299             {
    300                 _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_container(child));
    301                 NodeType ty = m_tree->type(child);
    302                 if(ty.marked_flow_sl())
    303                 {
    304                     this->Writer::_do_write(ind);
    305                     this->Writer::_do_write("- ");
    306                     _do_visit_flow_sl(child, 0u);
    307                     this->Writer::_do_write('\n');
    308                 }
    309                 else if(ty.marked_flow_ml())
    310                 {
    311                     this->Writer::_do_write(ind);
    312                     this->Writer::_do_write("- ");
    313                     _do_visit_flow_ml(child, next_level, do_indent);
    314                     this->Writer::_do_write('\n');
    315                 }
    316                 else
    317                 {
    318                     _do_visit_block(child, next_level, do_indent);
    319                 }
    320             }
    321             do_indent = true;
    322             ind = indent_to(do_indent * next_level);
    323         }
    324     }
    325     else // map
    326     {
    327         _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_map(node));
    328         for(size_t ich = m_tree->first_child(node); ich != NONE; ich = m_tree->next_sibling(ich))
    329         {
    330             _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->has_key(ich));
    331             if(m_tree->is_keyval(ich))
    332             {
    333                 this->Writer::_do_write(ind);
    334                 _writek(ich, next_level);
    335                 this->Writer::_do_write(": ");
    336                 _writev(ich, next_level);
    337                 this->Writer::_do_write('\n');
    338             }
    339             else
    340             {
    341                 _RYML_CB_ASSERT(m_tree->callbacks(), m_tree->is_container(ich));
    342                 NodeType ty = m_tree->type(ich);
    343                 if(ty.marked_flow_sl())
    344                 {
    345                     this->Writer::_do_write(ind);
    346                     _do_visit_flow_sl(ich, 0u);
    347                     this->Writer::_do_write('\n');
    348                 }
    349                 else if(ty.marked_flow_ml())
    350                 {
    351                     this->Writer::_do_write(ind);
    352                     _do_visit_flow_ml(ich, 0u);
    353                     this->Writer::_do_write('\n');
    354                 }
    355                 else
    356                 {
    357                     _do_visit_block(ich, next_level, do_indent);
    358                 }
    359             }
    360             do_indent = true;
    361             ind = indent_to(do_indent * next_level);
    362         }
    363     }
    364 }
    365 
    366 template<class Writer>
    367 void Emitter<Writer>::_do_visit_block(size_t node, size_t ilevel, size_t do_indent)
    368 {
    369     RYML_ASSERT(!m_tree->is_stream(node));
    370     RYML_ASSERT(m_tree->is_container(node) || m_tree->is_doc(node));
    371     RYML_ASSERT(m_tree->is_root(node) || (m_tree->parent_is_map(node) || m_tree->parent_is_seq(node)));
    372     RepC ind = indent_to(do_indent * ilevel);
    373 
    374     if(m_tree->is_doc(node))
    375     {
    376         _write_doc(node);
    377         if(!m_tree->has_children(node))
    378             return;
    379     }
    380     else if(m_tree->is_container(node))
    381     {
    382         RYML_ASSERT(m_tree->is_map(node) || m_tree->is_seq(node));
    383 
    384         bool spc = false; // write a space
    385         bool nl = false;  // write a newline
    386 
    387         if(m_tree->has_key(node))
    388         {
    389             this->Writer::_do_write(ind);
    390             _writek(node, ilevel);
    391             this->Writer::_do_write(':');
    392             spc = true;
    393         }
    394         else if(!m_tree->is_root(node))
    395         {
    396             this->Writer::_do_write(ind);
    397             this->Writer::_do_write('-');
    398             spc = true;
    399         }
    400 
    401         if(m_tree->has_val_tag(node))
    402         {
    403             if(spc)
    404                 this->Writer::_do_write(' ');
    405             _write_tag(m_tree->val_tag(node));
    406             spc = true;
    407             nl = true;
    408         }
    409 
    410         if(m_tree->has_val_anchor(node))
    411         {
    412             if(spc)
    413                 this->Writer::_do_write(' ');
    414             this->Writer::_do_write('&');
    415             this->Writer::_do_write(m_tree->val_anchor(node));
    416             spc = true;
    417             nl = true;
    418         }
    419 
    420         if(m_tree->has_children(node))
    421         {
    422             if(m_tree->has_key(node))
    423                 nl = true;
    424             else
    425                 if(!m_tree->is_root(node) && !nl)
    426                     spc = true;
    427         }
    428         else
    429         {
    430             if(m_tree->is_seq(node))
    431                 this->Writer::_do_write(" []\n");
    432             else if(m_tree->is_map(node))
    433                 this->Writer::_do_write(" {}\n");
    434             return;
    435         }
    436 
    437         if(spc && !nl)
    438             this->Writer::_do_write(' ');
    439 
    440         do_indent = 0;
    441         if(nl)
    442         {
    443             this->Writer::_do_write('\n');
    444             do_indent = 1;
    445         }
    446     } // container
    447 
    448     size_t next_level = ilevel + 1;
    449     if(m_tree->is_root(node) || m_tree->is_doc(node))
    450         next_level = ilevel; // do not indent at top level
    451 
    452     _do_visit_block_container(node, next_level, do_indent);
    453 }
    454 
    455 template<class Writer>
    456 void Emitter<Writer>::_do_visit_json(size_t id)
    457 {
    458     _RYML_CB_CHECK(m_tree->callbacks(), !m_tree->is_stream(id)); // JSON does not have streams
    459     if(m_tree->is_keyval(id))
    460     {
    461         _writek_json(id);
    462         this->Writer::_do_write(": ");
    463         _writev_json(id);
    464     }
    465     else if(m_tree->is_val(id))
    466     {
    467         _writev_json(id);
    468     }
    469     else if(m_tree->is_container(id))
    470     {
    471         if(m_tree->has_key(id))
    472         {
    473             _writek_json(id);
    474             this->Writer::_do_write(": ");
    475         }
    476         if(m_tree->is_seq(id))
    477             this->Writer::_do_write('[');
    478         else if(m_tree->is_map(id))
    479             this->Writer::_do_write('{');
    480     }  // container
    481 
    482     for(size_t ich = m_tree->first_child(id); ich != NONE; ich = m_tree->next_sibling(ich))
    483     {
    484         if(ich != m_tree->first_child(id))
    485             this->Writer::_do_write(',');
    486         _do_visit_json(ich);
    487     }
    488 
    489     if(m_tree->is_seq(id))
    490         this->Writer::_do_write(']');
    491     else if(m_tree->is_map(id))
    492         this->Writer::_do_write('}');
    493 }
    494 
    495 template<class Writer>
    496 void Emitter<Writer>::_write(NodeScalar const& C4_RESTRICT sc, NodeType flags, size_t ilevel)
    497 {
    498     if( ! sc.tag.empty())
    499     {
    500         _write_tag(sc.tag);
    501         this->Writer::_do_write(' ');
    502     }
    503     if(flags.has_anchor())
    504     {
    505         RYML_ASSERT(flags.is_ref() != flags.has_anchor());
    506         RYML_ASSERT( ! sc.anchor.empty());
    507         this->Writer::_do_write('&');
    508         this->Writer::_do_write(sc.anchor);
    509         this->Writer::_do_write(' ');
    510     }
    511     else if(flags.is_ref())
    512     {
    513         if(sc.anchor != "<<")
    514             this->Writer::_do_write('*');
    515         this->Writer::_do_write(sc.anchor);
    516         return;
    517     }
    518 
    519     // ensure the style flags only have one of KEY or VAL
    520     _RYML_CB_ASSERT(m_tree->callbacks(), ((flags & (_WIP_KEY_STYLE|_WIP_VAL_STYLE)) == 0) || (((flags&_WIP_KEY_STYLE) == 0) != ((flags&_WIP_VAL_STYLE) == 0)));
    521 
    522     auto style_marks = flags & (_WIP_KEY_STYLE|_WIP_VAL_STYLE);
    523     if(style_marks & (_WIP_KEY_LITERAL|_WIP_VAL_LITERAL))
    524     {
    525         _write_scalar_literal(sc.scalar, ilevel, flags.has_key());
    526     }
    527     else if(style_marks & (_WIP_KEY_FOLDED|_WIP_VAL_FOLDED))
    528     {
    529         _write_scalar_folded(sc.scalar, ilevel, flags.has_key());
    530     }
    531     else if(style_marks & (_WIP_KEY_SQUO|_WIP_VAL_SQUO))
    532     {
    533         _write_scalar_squo(sc.scalar, ilevel);
    534     }
    535     else if(style_marks & (_WIP_KEY_DQUO|_WIP_VAL_DQUO))
    536     {
    537         _write_scalar_dquo(sc.scalar, ilevel);
    538     }
    539     else if(style_marks & (_WIP_KEY_PLAIN|_WIP_VAL_PLAIN))
    540     {
    541         _write_scalar_plain(sc.scalar, ilevel);
    542     }
    543     else if(!style_marks)
    544     {
    545         size_t first_non_nl = sc.scalar.first_not_of('\n');
    546         bool all_newlines = first_non_nl == npos;
    547         bool has_leading_ws = (!all_newlines) && sc.scalar.sub(first_non_nl).begins_with_any(" \t");
    548         bool do_literal = ((!sc.scalar.empty() && all_newlines) || (has_leading_ws && !sc.scalar.trim(' ').empty()));
    549         if(do_literal)
    550         {
    551             _write_scalar_literal(sc.scalar, ilevel, flags.has_key(), /*explicit_indentation*/has_leading_ws);
    552         }
    553         else
    554         {
    555             for(size_t i = 0; i < sc.scalar.len; ++i)
    556             {
    557                 if(sc.scalar.str[i] == '\n')
    558                 {
    559                     _write_scalar_literal(sc.scalar, ilevel, flags.has_key(), /*explicit_indentation*/has_leading_ws);
    560                     goto wrote_special;
    561                 }
    562                 // todo: check for escaped characters requiring double quotes
    563             }
    564             _write_scalar(sc.scalar, flags.is_quoted());
    565         wrote_special:
    566             ;
    567         }
    568     }
    569     else
    570     {
    571         _RYML_CB_ERR(m_tree->callbacks(), "not implemented");
    572     }
    573 }
    574 template<class Writer>
    575 void Emitter<Writer>::_write_json(NodeScalar const& C4_RESTRICT sc, NodeType flags)
    576 {
    577     if(C4_UNLIKELY( ! sc.tag.empty()))
    578         _RYML_CB_ERR(m_tree->callbacks(), "JSON does not have tags");
    579     if(C4_UNLIKELY(flags.has_anchor()))
    580         _RYML_CB_ERR(m_tree->callbacks(), "JSON does not have anchors");
    581     _write_scalar_json(sc.scalar, flags.has_key(), flags.is_quoted());
    582 }
    583 
    584 #define _rymlindent_nextline() for(size_t lv = 0; lv < ilevel+1; ++lv) { this->Writer::_do_write(' '); this->Writer::_do_write(' '); }
    585 
    586 template<class Writer>
    587 void Emitter<Writer>::_write_scalar_literal(csubstr s, size_t ilevel, bool explicit_key, bool explicit_indentation)
    588 {
    589     if(explicit_key)
    590         this->Writer::_do_write("? ");
    591     csubstr trimmed = s.trimr("\n\r");
    592     size_t numnewlines_at_end = s.len - trimmed.len - s.sub(trimmed.len).count('\r');
    593     //
    594     if(!explicit_indentation)
    595         this->Writer::_do_write('|');
    596     else
    597         this->Writer::_do_write("|2");
    598     //
    599     if(numnewlines_at_end > 1 || (trimmed.len == 0 && s.len > 0)/*only newlines*/)
    600         this->Writer::_do_write("+\n");
    601     else if(numnewlines_at_end == 1)
    602         this->Writer::_do_write('\n');
    603     else
    604         this->Writer::_do_write("-\n");
    605     //
    606     if(trimmed.len)
    607     {
    608         size_t pos = 0; // tracks the last character that was already written
    609         for(size_t i = 0; i < trimmed.len; ++i)
    610         {
    611             if(trimmed[i] != '\n')
    612                 continue;
    613             // write everything up to this point
    614             csubstr since_pos = trimmed.range(pos, i+1); // include the newline
    615             _rymlindent_nextline()
    616             this->Writer::_do_write(since_pos);
    617             pos = i+1; // already written
    618         }
    619         if(pos < trimmed.len)
    620         {
    621             _rymlindent_nextline()
    622             this->Writer::_do_write(trimmed.sub(pos));
    623         }
    624         if(numnewlines_at_end)
    625         {
    626             this->Writer::_do_write('\n');
    627             --numnewlines_at_end;
    628         }
    629     }
    630     for(size_t i = 0; i < numnewlines_at_end; ++i)
    631     {
    632         _rymlindent_nextline()
    633         if(i+1 < numnewlines_at_end || explicit_key)
    634             this->Writer::_do_write('\n');
    635     }
    636     if(explicit_key && !numnewlines_at_end)
    637         this->Writer::_do_write('\n');
    638 }
    639 
    640 template<class Writer>
    641 void Emitter<Writer>::_write_scalar_folded(csubstr s, size_t ilevel, bool explicit_key)
    642 {
    643     if(explicit_key)
    644     {
    645         this->Writer::_do_write("? ");
    646     }
    647     RYML_ASSERT(s.find("\r") == csubstr::npos);
    648     csubstr trimmed = s.trimr('\n');
    649     size_t numnewlines_at_end = s.len - trimmed.len;
    650     if(numnewlines_at_end == 0)
    651     {
    652         this->Writer::_do_write(">-\n");
    653     }
    654     else if(numnewlines_at_end == 1)
    655     {
    656         this->Writer::_do_write(">\n");
    657     }
    658     else if(numnewlines_at_end > 1)
    659     {
    660         this->Writer::_do_write(">+\n");
    661     }
    662     if(trimmed.len)
    663     {
    664         size_t pos = 0; // tracks the last character that was already written
    665         for(size_t i = 0; i < trimmed.len; ++i)
    666         {
    667             if(trimmed[i] != '\n')
    668                 continue;
    669             // write everything up to this point
    670             csubstr since_pos = trimmed.range(pos, i+1); // include the newline
    671             pos = i+1; // because of the newline
    672             _rymlindent_nextline()
    673             this->Writer::_do_write(since_pos);
    674             this->Writer::_do_write('\n'); // write the newline twice
    675         }
    676         if(pos < trimmed.len)
    677         {
    678             _rymlindent_nextline()
    679             this->Writer::_do_write(trimmed.sub(pos));
    680         }
    681         if(numnewlines_at_end)
    682         {
    683             this->Writer::_do_write('\n');
    684             --numnewlines_at_end;
    685         }
    686     }
    687     for(size_t i = 0; i < numnewlines_at_end; ++i)
    688     {
    689         _rymlindent_nextline()
    690         if(i+1 < numnewlines_at_end || explicit_key)
    691             this->Writer::_do_write('\n');
    692     }
    693     if(explicit_key && !numnewlines_at_end)
    694         this->Writer::_do_write('\n');
    695 }
    696 
    697 template<class Writer>
    698 void Emitter<Writer>::_write_scalar_squo(csubstr s, size_t ilevel)
    699 {
    700     size_t pos = 0; // tracks the last character that was already written
    701     this->Writer::_do_write('\'');
    702     for(size_t i = 0; i < s.len; ++i)
    703     {
    704         if(s[i] == '\n')
    705         {
    706             csubstr sub = s.range(pos, i+1);
    707             this->Writer::_do_write(sub);  // write everything up to (including) this char
    708             this->Writer::_do_write('\n'); // write the character again
    709             if(i + 1 < s.len)
    710                 _rymlindent_nextline()     // indent the next line
    711             pos = i+1;
    712         }
    713         else if(s[i] == '\'')
    714         {
    715             csubstr sub = s.range(pos, i+1);
    716             this->Writer::_do_write(sub); // write everything up to (including) this char
    717             this->Writer::_do_write('\''); // write the character again
    718             pos = i+1;
    719         }
    720     }
    721     // write missing characters at the end of the string
    722     if(pos < s.len)
    723         this->Writer::_do_write(s.sub(pos));
    724     this->Writer::_do_write('\'');
    725 }
    726 
    727 template<class Writer>
    728 void Emitter<Writer>::_write_scalar_dquo(csubstr s, size_t ilevel)
    729 {
    730     size_t pos = 0; // tracks the last character that was already written
    731     this->Writer::_do_write('"');
    732     for(size_t i = 0; i < s.len; ++i)
    733     {
    734         const char curr = s.str[i];
    735         if(curr == '"' || curr == '\\')
    736         {
    737             csubstr sub = s.range(pos, i);
    738             this->Writer::_do_write(sub);  // write everything up to (excluding) this char
    739             this->Writer::_do_write('\\'); // write the escape
    740             this->Writer::_do_write(curr); // write the char
    741             pos = i+1;
    742         }
    743         else if(s[i] == '\n')
    744         {
    745             csubstr sub = s.range(pos, i+1);
    746             this->Writer::_do_write(sub);  // write everything up to (including) this newline
    747             this->Writer::_do_write('\n'); // write the newline again
    748             if(i + 1 < s.len)
    749                 _rymlindent_nextline()     // indent the next line
    750             pos = i+1;
    751             if(i+1 < s.len) // escape leading whitespace after the newline
    752             {
    753                 const char next = s.str[i+1];
    754                 if(next == ' ' || next == '\t')
    755                     this->Writer::_do_write('\\');
    756             }
    757         }
    758         else if(curr == ' ' || curr == '\t')
    759         {
    760             // escape trailing whitespace before a newline
    761             size_t next = s.first_not_of(" \t\r", i);
    762             if(next != npos && s[next] == '\n')
    763             {
    764                 csubstr sub = s.range(pos, i);
    765                 this->Writer::_do_write(sub);  // write everything up to (excluding) this char
    766                 this->Writer::_do_write('\\'); // escape the whitespace
    767                 pos = i;
    768             }
    769         }
    770         else if(C4_UNLIKELY(curr == '\r'))
    771         {
    772             csubstr sub = s.range(pos, i);
    773             this->Writer::_do_write(sub);  // write everything up to (excluding) this char
    774             this->Writer::_do_write("\\r"); // write the escaped char
    775             pos = i+1;
    776         }
    777     }
    778     // write missing characters at the end of the string
    779     if(pos < s.len)
    780     {
    781         csubstr sub = s.sub(pos);
    782         this->Writer::_do_write(sub);
    783     }
    784     this->Writer::_do_write('"');
    785 }
    786 
    787 template<class Writer>
    788 void Emitter<Writer>::_write_scalar_plain(csubstr s, size_t ilevel)
    789 {
    790     size_t pos = 0; // tracks the last character that was already written
    791     for(size_t i = 0; i < s.len; ++i)
    792     {
    793         const char curr = s.str[i];
    794         if(curr == '\n')
    795         {
    796             csubstr sub = s.range(pos, i+1);
    797             this->Writer::_do_write(sub);  // write everything up to (including) this newline
    798             this->Writer::_do_write('\n'); // write the newline again
    799             if(i + 1 < s.len)
    800                 _rymlindent_nextline()     // indent the next line
    801             pos = i+1;
    802         }
    803     }
    804     // write missing characters at the end of the string
    805     if(pos < s.len)
    806     {
    807         csubstr sub = s.sub(pos);
    808         this->Writer::_do_write(sub);
    809     }
    810 }
    811 
    812 #undef _rymlindent_nextline
    813 
    814 template<class Writer>
    815 void Emitter<Writer>::_write_scalar(csubstr s, bool was_quoted)
    816 {
    817     // this block of code needed to be moved to before the needs_quotes
    818     // assignment to work around a g++ optimizer bug where (s.str != nullptr)
    819     // was evaluated as true even if s.str was actually a nullptr (!!!)
    820     if(s.len == size_t(0))
    821     {
    822         if(was_quoted || s.str != nullptr)
    823             this->Writer::_do_write("''");
    824         return;
    825     }
    826 
    827     const bool needs_quotes = (
    828         was_quoted
    829         ||
    830         (
    831             ( ! s.is_number())
    832             &&
    833             (
    834                 // has leading whitespace
    835                 // looks like reference or anchor
    836                 // would be treated as a directive
    837                 // see https://www.yaml.info/learn/quote.html#noplain
    838                 s.begins_with_any(" \n\t\r*&%@`")
    839                 ||
    840                 s.begins_with("<<")
    841                 ||
    842                 // has trailing whitespace
    843                 s.ends_with_any(" \n\t\r")
    844                 ||
    845                 // has special chars
    846                 (s.first_of("#:-?,\n{}[]'\"") != npos)
    847             )
    848         )
    849     );
    850 
    851     if( ! needs_quotes)
    852     {
    853         this->Writer::_do_write(s);
    854     }
    855     else
    856     {
    857         const bool has_dquotes = s.first_of( '"') != npos;
    858         const bool has_squotes = s.first_of('\'') != npos;
    859         if(!has_squotes && has_dquotes)
    860         {
    861             this->Writer::_do_write('\'');
    862             this->Writer::_do_write(s);
    863             this->Writer::_do_write('\'');
    864         }
    865         else if(has_squotes && !has_dquotes)
    866         {
    867             RYML_ASSERT(s.count('\n') == 0);
    868             this->Writer::_do_write('"');
    869             this->Writer::_do_write(s);
    870             this->Writer::_do_write('"');
    871         }
    872         else
    873         {
    874             _write_scalar_squo(s, /*FIXME FIXME FIXME*/0);
    875         }
    876     }
    877 }
    878 template<class Writer>
    879 void Emitter<Writer>::_write_scalar_json(csubstr s, bool as_key, bool use_quotes)
    880 {
    881     if((!use_quotes)
    882        // json keys require quotes
    883        && (!as_key)
    884        && (
    885            // do not quote special cases
    886            (s == "true" || s == "false" || s == "null")
    887            || (
    888                // do not quote numbers
    889                (s.is_number()
    890                 && (
    891                     // quote integral numbers if they have a leading 0
    892                     // https://github.com/biojppm/rapidyaml/issues/291
    893                     (!(s.len > 1 && s.begins_with('0')))
    894                     // do not quote reals with leading 0
    895                     // https://github.com/biojppm/rapidyaml/issues/313
    896                     || (s.find('.') != csubstr::npos) ))
    897                )
    898            )
    899         )
    900     {
    901         this->Writer::_do_write(s);
    902     }
    903     else
    904     {
    905         size_t pos = 0;
    906         this->Writer::_do_write('"');
    907         for(size_t i = 0; i < s.len; ++i)
    908         {
    909             switch(s.str[i])
    910             {
    911             case '"':
    912               this->Writer ::_do_write(s.range(pos, i));
    913               this->Writer ::_do_write("\\\"");
    914               pos = i + 1;
    915               break;
    916             case '\n':
    917               this->Writer ::_do_write(s.range(pos, i));
    918               this->Writer ::_do_write("\\n");
    919               pos = i + 1;
    920               break;
    921             case '\t':
    922               this->Writer ::_do_write(s.range(pos, i));
    923               this->Writer ::_do_write("\\t");
    924               pos = i + 1;
    925               break;
    926             case '\\':
    927               this->Writer ::_do_write(s.range(pos, i));
    928               this->Writer ::_do_write("\\\\");
    929               pos = i + 1;
    930               break;
    931             case '\r':
    932               this->Writer ::_do_write(s.range(pos, i));
    933               this->Writer ::_do_write("\\r");
    934               pos = i + 1;
    935               break;
    936             case '\b':
    937               this->Writer ::_do_write(s.range(pos, i));
    938               this->Writer ::_do_write("\\b");
    939               pos = i + 1;
    940               break;
    941             case '\f':
    942               this->Writer ::_do_write(s.range(pos, i));
    943               this->Writer ::_do_write("\\f");
    944               pos = i + 1;
    945               break;
    946             }
    947         }
    948         if(pos < s.len)
    949         {
    950             csubstr sub = s.sub(pos);
    951             this->Writer::_do_write(sub);
    952         }
    953         this->Writer::_do_write('"');
    954     }
    955 }
    956 
    957 } // namespace yml
    958 } // namespace c4
    959 
    960 #endif /* _C4_YML_EMIT_DEF_HPP_ */