rapidyaml  0.7.0
parse and emit YAML, and do it fast
Quickstart

Example code for every feature. More...

Modules

 Sample helpers
 Functions and classes used in the examples of this sample.
 

Functions

ryml::Callbacks sample::ErrorHandlerExample::callbacks ()
 a helper to create the Callbacks object with the custom error handler More...
 
static void sample::ErrorHandlerExample::s_error (const char *msg, size_t len, ryml::Location loc, void *this_)
 
template<class Fn >
bool sample::ErrorHandlerExample::check_error_occurs (Fn &&fn) const
 
template<class Fn >
bool sample::ErrorHandlerExample::check_assertion_occurs (Fn &&fn) const
 
void sample::ErrorHandlerExample::check_effect (bool committed) const
 
 sample::ErrorHandlerExample::ErrorHandlerExample ()
 
 sample::ScopedErrorHandlerExample::ScopedErrorHandlerExample ()
 
 sample::ScopedErrorHandlerExample::~ScopedErrorHandlerExample ()
 
void sample::sample_lightning_overview ()
 a lightning tour over most features see sample_quick_overview More...
 
void sample::sample_quick_overview ()
 a brief tour over most features More...
 
void sample::sample_substr ()
 demonstrate usage of ryml::substr and ryml::csubstr More...
 
void sample::sample_parse_file ()
 demonstrate how to load a YAML file from disk to parse with ryml. More...
 
void sample::sample_parse_in_place ()
 demonstrate in-place parsing of a mutable YAML source buffer. More...
 
void sample::sample_parse_in_arena ()
 demonstrate parsing of a read-only YAML source buffer More...
 
void sample::sample_parse_reuse_tree ()
 demonstrate reuse/modification of tree when parsing More...
 
void sample::sample_parse_reuse_parser ()
 Demonstrates reuse of an existing parser. More...
 
void sample::sample_parse_reuse_tree_and_parser ()
 for ultimate speed when parsing multiple times, reuse both the tree and parser More...
 
void sample::sample_iterate_trees ()
 shows how to programatically iterate through trees More...
 
void sample::sample_create_trees ()
 shows how to programatically create trees More...
 
void sample::sample_tree_arena ()
 demonstrates explicit and implicit interaction with the tree's string arena. More...
 
void sample::sample_fundamental_types ()
 ryml provides facilities for serializing and deserializing the C++ fundamental types, including boolean and null values; this is provided by the several overloads in to_chars: generalized chars to value and from_chars: generalized chars to value. More...
 
void sample::sample_empty_null_values ()
 Shows how to deal with empty/null values. More...
 
void sample::sample_formatting ()
 ryml provides facilities for formatting/deformatting (imported from c4core into the ryml namespace). More...
 
void sample::sample_base64 ()
 demonstrates how to read and write base64-encoded blobs. More...
 
void sample::sample_user_scalar_types ()
 to add scalar types (ie leaf types converting to/from string), define the functions above for those types. More...
 
void sample::sample_user_container_types ()
 shows how to serialize/deserialize container types. More...
 
void sample::sample_std_types ()
 demonstrates usage with the std implementations provided by ryml in the ryml_std.hpp header More...
 
void sample::sample_float_precision ()
 control precision of serialized floats More...
 
void sample::sample_emit_to_container ()
 demonstrates how to emit to a linear container of char More...
 
void sample::sample_emit_to_stream ()
 demonstrates how to emit to a stream-like structure More...
 
void sample::sample_emit_to_file ()
 demonstrates how to emit to a FILE* More...
 
void sample::sample_emit_nested_node ()
 just like parsing into a nested node, you can also emit from a nested node. More...
 
void sample::sample_emit_style ()
 [experimental] pick flow/block style for certain nodes. More...
 
void sample::sample_json ()
 shows how to parse and emit JSON. More...
 
void sample::sample_anchors_and_aliases ()
 demonstrates usage with anchors and alias references. More...
 
void sample::sample_tags ()
 
void sample::sample_tag_directives ()
 
void sample::sample_docs ()
 
void sample::sample_error_handler ()
 demonstrates how to set a custom error handler for ryml More...
 
void sample::sample_global_allocator ()
 demonstrates how to set the global allocator for ryml More...
 
void sample::sample_per_tree_allocator ()
 
void sample::sample_static_trees ()
 shows how to work around the static initialization order fiasco when using a static-duration ryml tree More...
 
void sample::sample_location_tracking ()
 demonstrates how to obtain the (zero-based) location of a node from a recently parsed tree More...
 

Variables

ryml::Callbacks sample::ErrorHandlerExample::defaults
 

Detailed Description

Example code for every feature.

This file does a quick tour of ryml.

It has multiple self-contained and well-commented samples that illustrate how to use ryml, and how it works.

Although this is not a unit test, the samples are written as a sequence of actions and predicate checks to better convey what is the expected result at any stage. And to ensure the code here is correct and up to date, it's also run as part of the CI tests.

If something is unclear, please open an issue or send a pull request at https://github.com/biojppm/rapidyaml . If you have an issue while using ryml, it is also encouraged to try to reproduce the issue here, or look first through the relevant section.

Happy ryml'ing!

Some guidance on building

The directories that exist side-by-side with this file contain several examples on how to build this with cmake, such that you can hit the ground running. See the relevant section of the main README for an overview of the different choices. I suggest starting first with the add_subdirectory example, treating it just like any other self-contained cmake project.

Or very quickly, to build and run this sample on your PC, start by creating this CMakeLists.txt:

cmake_minimum_required(VERSION 3.13)
project(ryml-quickstart LANGUAGES CXX)
include(FetchContent)
FetchContent_Declare(ryml
GIT_REPOSITORY https://github.com/biojppm/rapidyaml.git
GIT_TAG v0.7.0
GIT_SHALLOW FALSE # ensure submodules are checked out
)
FetchContent_MakeAvailable(ryml)
add_executable(ryml-quickstart ${ryml_SOURCE_DIR}/samples/quickstart.cpp)
target_link_libraries(ryml-quickstart ryml::ryml)
add_custom_target(run ryml-quickstart
COMMAND $<TARGET_FILE:ryml-quickstart>
DEPENDS ryml-quickstart
COMMENT "running: $<TARGET_FILE:ryml-quickstart>")

Now run the following commands in the same folder:

# configure the project
cmake -S . -B build
# build and run
cmake --build build --target ryml-quickstart -j
# optionally, open in your IDE
cmake --open build

Function Documentation

◆ callbacks()

ryml::Callbacks sample::ErrorHandlerExample::callbacks ( )

a helper to create the Callbacks object with the custom error handler

Definition at line 5253 of file quickstart.cpp.

5254 {
5255  return ryml::Callbacks(this, nullptr, nullptr, ErrorHandlerExample::s_error);
5256 }
static void s_error(const char *msg, size_t len, ryml::Location loc, void *this_)
a c-style callbacks class.
Definition: common.hpp:375

References sample::ErrorHandlerExample::s_error().

Referenced by sample::sample_error_handler(), and sample::sample_quick_overview().

◆ s_error()

static void sample::ErrorHandlerExample::s_error ( const char *  msg,
size_t  len,
ryml::Location  loc,
void *  this_ 
)
static

◆ check_error_occurs()

template<class Fn >
bool sample::ErrorHandlerExample::check_error_occurs ( Fn &&  fn) const

◆ check_assertion_occurs()

template<class Fn >
bool sample::ErrorHandlerExample::check_assertion_occurs ( Fn &&  fn) const

◆ check_effect()

void sample::ErrorHandlerExample::check_effect ( bool  committed) const

Definition at line 5258 of file quickstart.cpp.

5259 {
5260  ryml::Callbacks const& current = ryml::get_callbacks();
5261  if(committed)
5262  {
5263  CHECK((ryml::pfn_error)current.m_error == &s_error);
5264  }
5265  else
5266  {
5267  CHECK((ryml::pfn_error)current.m_error != &s_error);
5268  }
5269  CHECK(current.m_allocate == defaults.m_allocate);
5270  CHECK(current.m_free == defaults.m_free);
5271 }
void(*)(const char *msg, size_t msg_len, Location location, void *user_data) pfn_error
the type of the function used to report errors
Definition: common.hpp:359
Callbacks const & get_callbacks()
get the global callbacks
Definition: common.cpp:118
#define CHECK(predicate)
Definition: quickstart.cpp:229
pfn_allocate m_allocate
Definition: common.hpp:377
pfn_free m_free
Definition: common.hpp:378
pfn_error m_error
Definition: common.hpp:379

References CHECK, sample::ErrorHandlerExample::defaults, c4::yml::get_callbacks(), c4::yml::Callbacks::m_allocate, c4::yml::Callbacks::m_error, c4::yml::Callbacks::m_free, and sample::ErrorHandlerExample::s_error().

Referenced by sample::sample_error_handler().

◆ ErrorHandlerExample()

sample::ErrorHandlerExample::ErrorHandlerExample ( )
inline

Definition at line 250 of file quickstart.cpp.

◆ ScopedErrorHandlerExample()

sample::ScopedErrorHandlerExample::ScopedErrorHandlerExample ( )
inline

Definition at line 256 of file quickstart.cpp.

void set_callbacks(Callbacks const &c)
set the global callbacks for the library; after a call to this function, these callbacks will be used...
Definition: common.cpp:113
void check_effect(bool committed) const
ryml::Callbacks callbacks()
a helper to create the Callbacks object with the custom error handler

References c4::yml::set_callbacks().

◆ ~ScopedErrorHandlerExample()

sample::ScopedErrorHandlerExample::~ScopedErrorHandlerExample ( )
inline

Definition at line 257 of file quickstart.cpp.

References c4::yml::set_callbacks().

◆ sample_lightning_overview()

void sample::sample_lightning_overview ( )

a lightning tour over most features see sample_quick_overview

Definition at line 269 of file quickstart.cpp.

270 {
271  // Parse YAML code in place, potentially mutating the buffer:
272  char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}";
273  ryml::Tree tree = ryml::parse_in_place(yml_buf);
274 
275  // read from the tree:
276  ryml::NodeRef bar = tree["bar"];
277  CHECK(bar[0].val() == "2");
278  CHECK(bar[1].val() == "3");
279  CHECK(bar[0].val().str == yml_buf + 15); // points at the source buffer
280  CHECK(bar[1].val().str == yml_buf + 18);
281 
282  // deserializing:
283  int bar0 = 0, bar1 = 0;
284  bar[0] >> bar0;
285  bar[1] >> bar1;
286  CHECK(bar0 == 2);
287  CHECK(bar1 == 3);
288 
289  // serializing:
290  bar[0] << 10; // creates a string in the tree's arena
291  bar[1] << 11;
292  CHECK(bar[0].val() == "10");
293  CHECK(bar[1].val() == "11");
294 
295  // add nodes
296  bar.append_child() << 12; // see also operator= (explanation below)
297  CHECK(bar[2].val() == "12");
298 
299  // emit tree
300  // to std::string
301  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"({foo: 1,bar: [10,11,12],john: doe})");
302  std::cout << tree; // emit to ostream
303  ryml::emit_yaml(tree, stdout); // emit to FILE*
304 
305  // emit node
306  ryml::ConstNodeRef foo = tree["foo"];
307  // to std::string
308  CHECK(ryml::emitrs_yaml<std::string>(foo) == "foo: 1\n");
309  std::cout << foo; // emit node to ostream
310  ryml::emit_yaml(foo, stdout); // emit node to FILE*
311 }
Holds a pointer to an existing tree, and a node id.
Definition: node.hpp:836
A reference to a node in an existing yaml tree, offering a more convenient API than the index-based A...
Definition: node.hpp:975
NodeRef append_child()
Definition: node.hpp:1385
size_t emit_yaml(Tree const &t, id_type id, EmitOptions const &opts, FILE *f)
(1) emit YAML to the given file, starting at the given node.
Definition: emit.hpp:270
void parse_in_place(Parser *parser, csubstr filename, substr yaml, Tree *t, id_type node_id)
(1) parse YAML into an existing tree node.
Definition: parse.cpp:37

References c4::yml::NodeRef::append_child(), CHECK, c4::yml::emit_yaml(), and c4::yml::parse_in_place().

◆ sample_quick_overview()

void sample::sample_quick_overview ( )

a brief tour over most features

Definition at line 317 of file quickstart.cpp.

318 {
319  // Parse YAML code in place, potentially mutating the buffer:
320  char yml_buf[] = R"(
321 foo: 1
322 bar: [2, 3]
323 john: doe)";
324  ryml::Tree tree = ryml::parse_in_place(yml_buf);
325 
326  // The resulting tree contains only views to the parsed string. If
327  // the string was parsed in place, then the string must outlive
328  // the tree! This works in this case because `yml_buf` and `tree`
329  // live on the same scope, so have the same lifetime.
330 
331  // It is also possible to:
332  //
333  // - parse a read-only buffer using parse_in_arena(). This
334  // copies the YAML buffer to the tree's arena, and spares the
335  // headache of the string's lifetime.
336  //
337  // - reuse an existing tree (advised)
338  //
339  // - reuse an existing parser (advised)
340  //
341  // - parse into an existing node deep in a tree
342  //
343  // Note: it will always be significantly faster to parse in place
344  // and reuse tree+parser.
345  //
346  // Below you will find samples that show how to achieve reuse; but
347  // please note that for brevity and clarity, many of the examples
348  // here are parsing in the arena, and not reusing tree or parser.
349 
350 
351  //------------------------------------------------------------------
352  // API overview
353 
354  // ryml has a two-level API:
355  //
356  // The lower level index API is based on the indices of nodes,
357  // where the node's id is the node's position in the tree's data
358  // array. This API is very efficient, but somewhat difficult to use:
359  ryml::id_type root_id = tree.root_id();
360  ryml::id_type bar_id = tree.find_child(root_id, "bar"); // need to get the index right
361  CHECK(tree.is_map(root_id)); // all of the index methods are in the tree
362  CHECK(tree.is_seq(bar_id)); // ... and receive the subject index
363 
364  // The node API is a lightweight abstraction sitting on top of the
365  // index API, but offering a much more convenient interaction:
366  ryml::ConstNodeRef root = tree.rootref(); // a const node reference
367  ryml::ConstNodeRef bar = tree["bar"];
368  CHECK(root.is_map());
369  CHECK(bar.is_seq());
370 
371  // A node ref is a lightweight handle to the tree and associated id:
372  CHECK(root.tree() == &tree); // a node ref points at its tree, WITHOUT refcount
373  CHECK(root.id() == root_id); // a node ref's id is the index of the node
374  CHECK(bar.id() == bar_id); // a node ref's id is the index of the node
375 
376  // The node API translates very cleanly to the index API, so most
377  // of the code examples below are using the node API.
378 
379  // WARNING. A node ref holds a raw pointer to the tree. Care must
380  // be taken to ensure the lifetimes match, so that a node will
381  // never access the tree after the goes out of scope.
382 
383 
384  //------------------------------------------------------------------
385  // To read the parsed tree
386 
387  // ConstNodeRef::operator[] does a lookup, is O(num_children[node]).
388  CHECK(tree["foo"].is_keyval());
389  CHECK(tree["foo"].val() == "1"); // get the val of a node (must be leaf node, otherwise it is a container and has no val)
390  CHECK(tree["foo"].key() == "foo"); // get the key of a node (must be child of a map, otherwise it has no key)
391  CHECK(tree["bar"].is_seq());
392  CHECK(tree["bar"].has_key());
393  CHECK(tree["bar"].key() == "bar");
394  // maps use string keys, seqs use index keys:
395  CHECK(tree["bar"][0].val() == "2");
396  CHECK(tree["bar"][1].val() == "3");
397  CHECK(tree["john"].val() == "doe");
398  // An index key is the position of the child within its parent,
399  // so even maps can also use int keys, if the key position is
400  // known.
401  CHECK(tree[0].id() == tree["foo"].id());
402  CHECK(tree[1].id() == tree["bar"].id());
403  CHECK(tree[2].id() == tree["john"].id());
404  // Tree::operator[](int) searches a ***root*** child by its position.
405  CHECK(tree[0].id() == tree["foo"].id()); // 0: first child of root
406  CHECK(tree[1].id() == tree["bar"].id()); // 1: second child of root
407  CHECK(tree[2].id() == tree["john"].id()); // 2: third child of root
408  // NodeRef::operator[](int) searches a ***node*** child by its position:
409  CHECK(bar[0].val() == "2"); // 0 means first child of bar
410  CHECK(bar[1].val() == "3"); // 1 means second child of bar
411  // NodeRef::operator[](string):
412  // A string key is the key of the node: lookup is by name. So it
413  // is only available for maps, and it is NOT available for seqs,
414  // since seq members do not have keys.
415  CHECK(tree["foo"].key() == "foo");
416  CHECK(tree["bar"].key() == "bar");
417  CHECK(tree["john"].key() == "john");
418  CHECK(bar.is_seq());
419  // CHECK(bar["BOOM!"].is_seed()); // error, seqs do not have key lookup
420 
421  // Note that maps can also use index keys as well as string keys:
422  CHECK(root["foo"].id() == root[0].id());
423  CHECK(root["bar"].id() == root[1].id());
424  CHECK(root["john"].id() == root[2].id());
425 
426  // IMPORTANT. The ryml tree uses an index-based linked list for
427  // storing children, so the complexity of
428  // `Tree::operator[csubstr]` and `Tree::operator[id_type]` is O(n),
429  // linear on the number of root children. If you use
430  // `Tree::operator[]` with a large tree where the root has many
431  // children, you will see a performance hit.
432  //
433  // To avoid this hit, you can create your own accelerator
434  // structure. For example, before doing a lookup, do a single
435  // traverse at the root level to fill an `map<csubstr,id_type>`
436  // mapping key names to node indices; with a node index, a lookup
437  // (via `Tree::get()`) is O(1), so this way you can get O(log n)
438  // lookup from a key. (But please do not use `std::map` if you
439  // care about performance; use something else like a flat map or
440  // sorted vector).
441  //
442  // As for node refs, the difference from `NodeRef::operator[]` and
443  // `ConstNodeRef::operator[]` to `Tree::operator[]` is that the
444  // latter refers to the root node, whereas the former are invoked
445  // on their target node. But the lookup process works the same for
446  // both and their algorithmic complexity is the same: they are
447  // both linear in the number of direct children. But of course,
448  // depending on the data, that number may be very different from
449  // one to another.
450 
451 
452  //------------------------------------------------------------------
453  // Hierarchy:
454 
455  {
456  ryml::ConstNodeRef foo = root.first_child();
457  ryml::ConstNodeRef john = root.last_child();
458  CHECK(tree.size() == 6); // O(1) number of nodes in the tree
459  CHECK(root.num_children() == 3); // O(num_children[root])
460  CHECK(foo.num_siblings() == 3); // O(num_children[parent(foo)])
461  CHECK(foo.parent().id() == root.id()); // parent() is O(1)
462  CHECK(root.first_child().id() == root["foo"].id()); // first_child() is O(1)
463  CHECK(root.last_child().id() == root["john"].id()); // last_child() is O(1)
464  CHECK(john.first_sibling().id() == foo.id());
465  CHECK(foo.last_sibling().id() == john.id());
466  // prev_sibling(), next_sibling(): (both are O(1))
467  CHECK(foo.num_siblings() == root.num_children());
468  CHECK(foo.prev_sibling().id() == ryml::NONE); // foo is the first_child()
469  CHECK(foo.next_sibling().key() == "bar");
470  CHECK(foo.next_sibling().next_sibling().key() == "john");
471  CHECK(foo.next_sibling().next_sibling().next_sibling().id() == ryml::NONE); // john is the last_child()
472  }
473 
474 
475  //------------------------------------------------------------------
476  // Iterating:
477  {
478  ryml::csubstr expected_keys[] = {"foo", "bar", "john"};
479  // iterate children using the high-level node API:
480  {
481  ryml::id_type count = 0;
482  for(ryml::ConstNodeRef const& child : root.children())
483  CHECK(child.key() == expected_keys[count++]);
484  }
485  // iterate siblings using the high-level node API:
486  {
487  ryml::id_type count = 0;
488  for(ryml::ConstNodeRef const& child : root["foo"].siblings())
489  CHECK(child.key() == expected_keys[count++]);
490  }
491  // iterate children using the lower-level tree index API:
492  {
493  ryml::id_type count = 0;
494  for(ryml::id_type child_id = tree.first_child(root_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id))
495  CHECK(tree.key(child_id) == expected_keys[count++]);
496  }
497  // iterate siblings using the lower-level tree index API:
498  // (notice the only difference from above is in the loop
499  // preamble, which calls tree.first_sibling(bar_id) instead of
500  // tree.first_child(root_id))
501  {
502  ryml::id_type count = 0;
503  for(ryml::id_type child_id = tree.first_sibling(bar_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id))
504  CHECK(tree.key(child_id) == expected_keys[count++]);
505  }
506  }
507 
508 
509  //------------------------------------------------------------------
510  // Gotchas:
511 
512  // ryml uses assertions to prevent you from trying to obtain
513  // things that do not exist. For example:
514 
515  {
516  ryml::ConstNodeRef seq_node = tree["bar"];
517  ryml::ConstNodeRef val_node = seq_node[0];
518 
519  CHECK(seq_node.is_seq()); // seq is a container
520  CHECK(!seq_node.has_val()); // ... so it has no val
521  //CHECK(seq_node.val() == BOOM!); // ... so attempting to get a val is undefined behavior
522 
523  CHECK(val_node.parent() == seq_node); // belongs to a seq
524  CHECK(!val_node.has_key()); // ... so it has no key
525  //CHECK(val_node.key() == BOOM!); // ... so attempting to get a key is undefined behavior
526 
527  CHECK(val_node.is_val()); // this node is a val
528  //CHECK(val_node.first_child() == BOOM!); // ... so attempting to get a child is undefined behavior
529 
530  // assertions are also present in methods that /may/ read the val:
531  CHECK(seq_node.is_seq()); // seq is a container
532  //CHECK(seq_node.val_is_null() BOOM!); // so cannot get the val to check
533  }
534 
535  // By default, assertions are enabled unless the NDEBUG macro is
536  // defined (which happens in release builds).
537  //
538  // This adheres to the pay-only-for-what-you-use philosophy: if
539  // you are sure that your intent is correct, why would you need to
540  // pay the runtime cost for the assertions?
541  //
542  // The downside, of course, is that when you are not sure, release
543  // builds may be doing something crazy.
544  //
545  // So in that case, you can either use the appropriate ryml
546  // predicates to check your intent (as in the examples above), or
547  // you can override this behavior and enable/disable assertions,
548  // by defining the macro RYML_USE_ASSERT to a proper value (see
549  // c4/yml/common.hpp).
550  //
551  // Also, to be clear, this does not apply to parse errors
552  // occurring when the YAML is parsed. Checking for these errors is
553  // always enabled and cannot be switched off.
554 
555 
556  //------------------------------------------------------------------
557  // Deserializing: use operator>>
558  {
559  int foo = 0, bar0 = 0, bar1 = 0;
560  std::string john_str;
561  std::string bar_str;
562  root["foo"] >> foo;
563  root["bar"][0] >> bar0;
564  root["bar"][1] >> bar1;
565  root["john"] >> john_str; // requires from_chars(std::string). see serialization samples below.
566  root["bar"] >> ryml::key(bar_str); // to deserialize the key, use the tag function ryml::key()
567  CHECK(foo == 1);
568  CHECK(bar0 == 2);
569  CHECK(bar1 == 3);
570  CHECK(john_str == "doe");
571  CHECK(bar_str == "bar");
572  }
573 
574 
575  //------------------------------------------------------------------
576  // Modifying existing nodes: operator= vs operator<<
577 
578  // As implied by its name, ConstNodeRef is a reference to a const
579  // node. It can be used to read from the node, but not write to it
580  // or modify the hierarchy of the node. If any modification is
581  // desired then a NodeRef must be used instead:
582  ryml::NodeRef wroot = tree.rootref(); // writeable root
583 
584  // operator= assigns an existing string to the receiving node.
585  // The contents are NOT copied, and the string pointer will be in
586  // effect until the tree goes out of scope! So BEWARE to only
587  // assign from strings outliving the tree.
588  wroot["foo"] = "says you";
589  wroot["bar"][0] = "-2";
590  wroot["bar"][1] = "-3";
591  wroot["john"] = "ron";
592  // Now the tree is _pointing_ at the memory of the strings above.
593  // In this case it is OK because those are static strings, located
594  // in the executable's static section, and will outlive the tree.
595  CHECK(root["foo"].val() == "says you");
596  CHECK(root["bar"][0].val() == "-2");
597  CHECK(root["bar"][1].val() == "-3");
598  CHECK(root["john"].val() == "ron");
599  // But WATCHOUT: do not assign from temporary objects:
600  // {
601  // std::string crash("will dangle");
602  // root["john"] = ryml::to_csubstr(crash);
603  // }
604  // CHECK(root["john"] == "dangling"); // CRASH! the string was deallocated
605 
606  // operator<<: for cases where the lifetime of the string is
607  // problematic WRT the tree, you can create and save a string in
608  // the tree using operator<<. It first serializes values to a
609  // string arena owned by the tree, then assigns the serialized
610  // string to the receiving node. This avoids constraints with the
611  // lifetime, since the arena lives with the tree.
612  CHECK(tree.arena().empty());
613  wroot["foo"] << "says who"; // requires to_chars(). see serialization samples below.
614  wroot["bar"][0] << 20;
615  wroot["bar"][1] << 30;
616  wroot["john"] << "deere";
617  CHECK(root["foo"].val() == "says who");
618  CHECK(root["bar"][0].val() == "20");
619  CHECK(root["bar"][1].val() == "30");
620  CHECK(root["john"].val() == "deere");
621  CHECK(tree.arena() == "says who2030deere"); // the result of serializations to the tree arena
622  // using operator<< instead of operator=, the crash above is avoided:
623  {
624  std::string ok("in_scope");
625  // root["john"] = ryml::to_csubstr(ok); // don't, will dangle
626  wroot["john"] << ryml::to_csubstr(ok); // OK, copy to the tree's arena
627  }
628  CHECK(root["john"].val() == "in_scope"); // OK!
629  // serializing floating points:
630  wroot["float"] << 2.4;
631  // to force a particular precision or float format:
632  // (see sample_float_precision() and sample_formatting())
633  wroot["digits"] << ryml::fmt::real(2.4, /*num_digits*/6, ryml::FTOA_FLOAT);
634  CHECK(tree.arena() == "says who2030deerein_scope2.42.400000"); // the result of serializations to the tree arena
635 
636 
637  //------------------------------------------------------------------
638  // Adding new nodes:
639 
640  // adding a keyval node to a map:
641  CHECK(root.num_children() == 5);
642  wroot["newkeyval"] = "shiny and new"; // using these strings
643  wroot.append_child() << ryml::key("newkeyval (serialized)") << "shiny and new (serialized)"; // serializes and assigns the serialization
644  CHECK(root.num_children() == 7);
645  CHECK(root["newkeyval"].key() == "newkeyval");
646  CHECK(root["newkeyval"].val() == "shiny and new");
647  CHECK(root["newkeyval (serialized)"].key() == "newkeyval (serialized)");
648  CHECK(root["newkeyval (serialized)"].val() == "shiny and new (serialized)");
649  CHECK( ! tree.in_arena(root["newkeyval"].key())); // it's using directly the static string above
650  CHECK( ! tree.in_arena(root["newkeyval"].val())); // it's using directly the static string above
651  CHECK( tree.in_arena(root["newkeyval (serialized)"].key())); // it's using a serialization of the string above
652  CHECK( tree.in_arena(root["newkeyval (serialized)"].val())); // it's using a serialization of the string above
653  // adding a val node to a seq:
654  CHECK(root["bar"].num_children() == 2);
655  wroot["bar"][2] = "oh so nice";
656  wroot["bar"][3] << "oh so nice (serialized)";
657  CHECK(root["bar"].num_children() == 4);
658  CHECK(root["bar"][2].val() == "oh so nice");
659  CHECK(root["bar"][3].val() == "oh so nice (serialized)");
660  // adding a seq node:
661  CHECK(root.num_children() == 7);
662  wroot["newseq"] |= ryml::SEQ;
663  wroot.append_child() << ryml::key("newseq (serialized)") |= ryml::SEQ;
664  CHECK(root.num_children() == 9);
665  CHECK(root["newseq"].num_children() == 0);
666  CHECK(root["newseq"].is_seq());
667  CHECK(root["newseq (serialized)"].num_children() == 0);
668  CHECK(root["newseq (serialized)"].is_seq());
669  // adding a map node:
670  CHECK(root.num_children() == 9);
671  wroot["newmap"] |= ryml::MAP;
672  wroot.append_child() << ryml::key("newmap (serialized)") |= ryml::MAP;
673  CHECK(root.num_children() == 11);
674  CHECK(root["newmap"].num_children() == 0);
675  CHECK(root["newmap"].is_map());
676  CHECK(root["newmap (serialized)"].num_children() == 0);
677  CHECK(root["newmap (serialized)"].is_map());
678  //
679  // When the tree is mutable, operator[] first searches the tree
680  // for the does not mutate the tree until the returned node is
681  // written to.
682  //
683  // Until such time, the NodeRef object keeps in itself the required
684  // information to write to the proper place in the tree. This is
685  // called being in a "seed" state.
686  //
687  // This means that passing a key/index which does not exist will
688  // not mutate the tree, but will instead store (in the node) the
689  // proper place of the tree to be able to do so, if and when it is
690  // required. This is why the node is said to be in "seed" state -
691  // it allows creating the entry in the tree in the future.
692  //
693  // This is a significant difference from eg, the behavior of
694  // std::map, which mutates the map immediately within the call to
695  // operator[].
696  //
697  // All of the points above apply only if the tree is mutable. If
698  // the tree is const, then a NodeRef cannot be obtained from it;
699  // only a ConstNodeRef, which can never be used to mutate the
700  // tree.
701  //
702  CHECK(!root.has_child("I am not nothing"));
703  ryml::NodeRef nothing;
704  CHECK(nothing.invalid()); // invalid because it points at nothing
705  nothing = wroot["I am nothing"];
706  CHECK(!nothing.invalid()); // points at the tree, and a specific place in the tree
707  CHECK(nothing.is_seed()); // ... but nothing is there yet.
708  CHECK(!root.has_child("I am nothing")); // same as above
709  CHECK(!nothing.readable()); // ... and this node cannot be used to
710  // read anything from the tree
711  ryml::NodeRef something = wroot["I am something"];
712  ryml::ConstNodeRef constsomething = wroot["I am something"];
713  CHECK(!root.has_child("I am something")); // same as above
714  CHECK(!something.invalid());
715  CHECK(something.is_seed()); // same as above
716  CHECK(!something.readable()); // same as above
717  CHECK(constsomething.invalid()); // NOTE: because a ConstNodeRef cannot be
718  // used to mutate a tree, it is only valid()
719  // if it is pointing at an existing node.
720  something = "indeed"; // this will commit the seed to the tree, mutating at the proper place
721  CHECK(root.has_child("I am something"));
722  CHECK(root["I am something"].val() == "indeed");
723  CHECK(!something.invalid()); // it was already valid
724  CHECK(!something.is_seed()); // now the tree has this node, so the
725  // ref is no longer a seed
726  CHECK(something.readable()); // and it is now readable
727  //
728  // now the constref is also valid (but it needs to be reassigned):
729  ryml::ConstNodeRef constsomethingnew = wroot["I am something"];
730  CHECK(!constsomethingnew.invalid());
731  CHECK(constsomethingnew.readable());
732  // note that the old constref is now stale, because it only keeps
733  // the state at creation:
734  CHECK(constsomething.invalid());
735  CHECK(!constsomething.readable());
736  //
737  // -----------------------------------------------------------
738  // Remember: a seed node cannot be used to read from the tree!
739  // -----------------------------------------------------------
740  //
741  // The seed node needs to be created and become readable first.
742  //
743  // Trying to invoke any tree-reading method on a node that is not
744  // readable will cause an assertion (see RYML_USE_ASSERT).
745  //
746  // It is your responsibility to verify that the preconditions are
747  // met. If you are not sure about the structure of your data,
748  // write your code defensively to signify your full intent:
749  //
750  ryml::NodeRef wbar = wroot["bar"];
751  if(wbar.readable() && wbar.is_seq()) // .is_seq() requires .readable()
752  {
753  CHECK(wbar[0].readable() && wbar[0].val() == "20");
754  CHECK( ! wbar[100].readable());
755  CHECK( ! wbar[100].readable() || wbar[100].val() == "100"); // <- no crash because it is not .readable(), so never tries to call .val()
756  // this would work as well:
757  CHECK( ! wbar[0].is_seed() && wbar[0].val() == "20");
758  CHECK(wbar[100].is_seed() || wbar[100].val() == "100");
759  }
760 
761 
762  //------------------------------------------------------------------
763  // .operator[]() vs .at()
764 
765  // (Const)NodeRef::operator[]() is an analogue to std::vector::operator[].
766  // (Const)NodeRef::at() is an analogue to std::vector::at()
767  //
768  // at() will always check the subject node is .readable().
769  //
770  // [] is meant for the happy path, and unverified in Release
771  // builds.
772  {
773  // in this example we will be checking errors, so set up a
774  // temporary error handler to catch them:
775  ScopedErrorHandlerExample errh;
776  // instantiate the tree after errh
777  ryml::Tree err_tree = ryml::parse_in_arena("{foo: bar}");
778  // ... so that the tree uses the current callbacks:
779  CHECK(err_tree.callbacks() == errh.callbacks());
780  // node does not exist, only a seed node
781  ryml::NodeRef seed_node = err_tree["this"];
782  // ... therefore not .readable()
783  CHECK(!seed_node.readable());
784  // using .at() reliably produces an error:
785  CHECK(errh.check_error_occurs([&]{
786  return seed_node.at("is").at("an").at("invalid").at("operation");
787  // ^
788  // error occurs here because it is unreadable
789  }));
790  // ... but using [] fails only when RYML_USE_ASSERT is
791  // defined. otherwise, it's the dreaded Undefined Behavior:
792  CHECK(errh.check_assertion_occurs([&]{
793  return seed_node["is"]["an"]["invalid"]["operation"];
794  // ^
795  // assertion occurs here because it is unreadable
796  }));
797  }
798 
799 
800  //------------------------------------------------------------------
801  // Emitting:
802 
803  ryml::csubstr expected_result = R"(foo: says who
804 bar: [20,30,oh so nice,oh so nice (serialized)]
805 john: in_scope
806 float: 2.4
807 digits: 2.400000
808 newkeyval: shiny and new
809 newkeyval (serialized): shiny and new (serialized)
810 newseq: []
811 newseq (serialized): []
812 newmap: {}
813 newmap (serialized): {}
814 I am something: indeed
815 )";
816 
817  // emit to a FILE*
818  ryml::emit_yaml(tree, stdout);
819  // emit to a stream
820  std::stringstream ss;
821  ss << tree;
822  std::string stream_result = ss.str();
823  // emit to a buffer:
824  std::string str_result = ryml::emitrs_yaml<std::string>(tree);
825  // can emit to any given buffer:
826  char buf[1024];
827  ryml::csubstr buf_result = ryml::emit_yaml(tree, buf);
828  // now check
829  CHECK(buf_result == expected_result);
830  CHECK(str_result == expected_result);
831  CHECK(stream_result == expected_result);
832  // There are many possibilities to emit to buffer;
833  // please look at the emit sample functions below.
834 
835 
836  //------------------------------------------------------------------
837  // ConstNodeRef vs NodeRef
838 
839  ryml::NodeRef noderef = tree["bar"][0];
840  ryml::ConstNodeRef constnoderef = tree["bar"][0];
841 
842  // ConstNodeRef cannot be used to mutate the tree:
843  //constnoderef = "21"; // compile error
844  //constnoderef << "22"; // compile error
845  // ... but a NodeRef can:
846  noderef = "21"; // ok, can assign because it's not const
847  CHECK(tree["bar"][0].val() == "21");
848  noderef << "22"; // ok, can serialize and assign because it's not const
849  CHECK(tree["bar"][0].val() == "22");
850 
851  // it is not possible to obtain a NodeRef from a ConstNodeRef:
852  // noderef = constnoderef; // compile error
853 
854  // it is always possible to obtain a ConstNodeRef from a NodeRef:
855  constnoderef = noderef; // ok can assign const <- nonconst
856 
857  // If a tree is const, then only ConstNodeRef's can be
858  // obtained from that tree:
859  ryml::Tree const& consttree = tree;
860  //noderef = consttree["bar"][0]; // compile error
861  noderef = tree["bar"][0]; // ok
862  constnoderef = consttree["bar"][0]; // ok
863 
864  // ConstNodeRef and NodeRef can be compared for equality.
865  // Equality means they point at the same node.
866  CHECK(constnoderef == noderef);
867  CHECK(!(constnoderef != noderef));
868 
869 
870  //------------------------------------------------------------------
871  // Getting the location of nodes in the source:
872  //
873  // Location tracking is opt-in:
874  ryml::EventHandlerTree evt_handler = {};
875  ryml::Parser parser(&evt_handler, ryml::ParserOptions().locations(true));
876  // Now the parser will start by building the accelerator structure:
877  ryml::Tree tree2 = parse_in_arena(&parser, "expected.yml", expected_result);
878  // ... and use it when querying
879  ryml::ConstNodeRef subject_node = tree2["bar"][1];
880  CHECK(subject_node.val() == "30");
881  ryml::Location loc = parser.location(subject_node);
882  CHECK(parser.location_contents(loc).begins_with("30"));
883  CHECK(loc.line == 1u);
884  CHECK(loc.col == 9u);
885  // For further details in location tracking,
886  // refer to the sample function below.
887 
888  //------------------------------------------------------------------
889  // Dealing with UTF8
890  ryml::Tree langs = ryml::parse_in_arena(R"(
891 en: Planet (Gas)
892 fr: Planète (Gazeuse)
893 ru: Планета (Газ)
894 ja: 惑星(ガス)
895 zh: 行星(气体)
896 # UTF8 decoding only happens in double-quoted strings,
897 # as per the YAML standard
898 decode this: "\u263A \xE2\x98\xBA"
899 and this as well: "\u2705 \U0001D11E"
900 not decoded: '\u263A \xE2\x98\xBA'
901 neither this: '\u2705 \U0001D11E'
902 )");
903  // in-place UTF8 just works:
904  CHECK(langs["en"].val() == "Planet (Gas)");
905  CHECK(langs["fr"].val() == "Planète (Gazeuse)");
906  CHECK(langs["ru"].val() == "Планета (Газ)");
907  CHECK(langs["ja"].val() == "惑星(ガス)");
908  CHECK(langs["zh"].val() == "行星(气体)");
909  // and \x \u \U codepoints are decoded, but only when they appear
910  // inside double-quoted strings, as dictated by the YAML
911  // standard:
912  CHECK(langs["decode this"].val() == "☺ ☺");
913  CHECK(langs["and this as well"].val() == "✅ 𝄞");
914  CHECK(langs["not decoded"].val() == "\\u263A \\xE2\\x98\\xBA");
915  CHECK(langs["neither this"].val() == "\\u2705 \\U0001D11E");
916 }
Tree const * tree() const noexcept
Definition: node.hpp:908
id_type id() const noexcept
Definition: node.hpp:909
bool invalid() const noexcept
Definition: node.hpp:891
bool readable() const noexcept
because a ConstNodeRef cannot be used to write to the tree, readable() has the same meaning as !...
Definition: node.hpp:894
bool invalid() const noexcept
true if the object is not referring to any existing or seed node.
Definition: node.hpp:1046
bool readable() const noexcept
true if the object is not invalid and not in seed state.
Definition: node.hpp:1050
bool is_seed() const noexcept
true if the object is not invalid and in seed state.
Definition: node.hpp:1048
This is the main driver of parsing logic: it scans the YAML or JSON source for tokens,...
id_type first_child(id_type node) const
Definition: tree.hpp:499
NodeRef rootref()
Get the root as a NodeRef.
Definition: tree.cpp:20
bool is_map(id_type node) const
Definition: tree.hpp:403
bool in_arena(csubstr s) const
return true if the given substring is part of the tree's string arena
Definition: tree.hpp:833
Callbacks const & callbacks() const
Definition: tree.hpp:278
id_type next_sibling(id_type node) const
Definition: tree.hpp:494
csubstr const & key(id_type node) const
Definition: tree.hpp:377
bool is_seq(id_type node) const
Definition: tree.hpp:404
id_type find_child(id_type node, csubstr const &key) const
Definition: tree.cpp:1166
id_type root_id()
Get the id of the root node.
Definition: tree.hpp:325
id_type first_sibling(id_type node) const
Definition: tree.hpp:510
csubstr arena() const
get the current arena
Definition: tree.hpp:828
id_type size() const
Definition: tree.hpp:274
@ FTOA_FLOAT
print the real number in floating point format (like f)
Definition: charconv.hpp:202
@ MAP
a map: a parent of KEYVAL/KEYSEQ/KEYMAP nodes
Definition: node_type.hpp:35
@ SEQ
a seq: a parent of VAL/SEQ/MAP nodes
Definition: node_type.hpp:36
void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, Tree *t, id_type node_id)
(1) parse YAML into an existing tree node. The filename will be used in any error messages arising du...
Definition: parse.cpp:91
real_< T > real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT)
Definition: format.hpp:360
Key< K > key(K &k)
Definition: node.hpp:43
csubstr to_csubstr(substr s) noexcept
neutral version for use in generic code
Definition: substr.hpp:2189
RYML_ID_TYPE id_type
The type of a node id in the YAML tree; to override the default type, define the macro RYML_ID_TYPE t...
Definition: common.hpp:252
@ NONE
an index to none
Definition: common.hpp:259
The event handler to create a ryml Tree.
a source file position
Definition: common.hpp:296
size_t col
column
Definition: common.hpp:302
size_t line
line
Definition: common.hpp:300
Options to give to the parser to control its behavior.
auto first_child() RYML_NOEXCEPT -> Impl
Forward to Tree::first_child().
Definition: node.hpp:339
auto children() RYML_NOEXCEPT -> children_view
get an iterable view over children
Definition: node.hpp:745
bool is_val() const RYML_NOEXCEPT
Forward to Tree::is_val().
Definition: node.hpp:243
auto last_child() RYML_NOEXCEPT -> Impl
Forward to Tree::last_child().
Definition: node.hpp:343
auto parent() RYML_NOEXCEPT -> Impl
Forward to Tree::parent().
Definition: node.hpp:335
bool has_child(ConstImpl const &n) const RYML_NOEXCEPT
Forward to Tree::has_child().
Definition: node.hpp:308
auto next_sibling() RYML_NOEXCEPT -> Impl
Forward to Tree::next_sibling().
Definition: node.hpp:359
auto last_sibling() RYML_NOEXCEPT -> Impl
Forward to Tree::last_sibling().
Definition: node.hpp:367
bool is_map() const RYML_NOEXCEPT
Forward to Tree::is_map().
Definition: node.hpp:239
id_type num_siblings() const RYML_NOEXCEPT
O(num_children).
Definition: node.hpp:379
bool has_key() const RYML_NOEXCEPT
Forward to Tree::has_key().
Definition: node.hpp:242
auto first_sibling() RYML_NOEXCEPT -> Impl
Forward to Tree::first_sibling().
Definition: node.hpp:363
csubstr val() const RYML_NOEXCEPT
Forward to Tree::val().
Definition: node.hpp:214
id_type num_children() const RYML_NOEXCEPT
O(num_children).
Definition: node.hpp:378
bool is_seq() const RYML_NOEXCEPT
Forward to Tree::is_seq().
Definition: node.hpp:240
bool has_val() const RYML_NOEXCEPT
Forward to Tree::has_val().
Definition: node.hpp:241
auto prev_sibling() RYML_NOEXCEPT -> Impl
Forward to Tree::prev_sibling().
Definition: node.hpp:355

References c4::yml::NodeRef::append_child(), c4::yml::Tree::arena(), sample::ErrorHandlerExample::callbacks(), c4::yml::Tree::callbacks(), CHECK, sample::ErrorHandlerExample::check_assertion_occurs(), sample::ErrorHandlerExample::check_error_occurs(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::children(), c4::yml::Location::col, c4::yml::emit_yaml(), c4::yml::Tree::find_child(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::first_child(), c4::yml::Tree::first_child(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::first_sibling(), c4::yml::Tree::first_sibling(), c4::FTOA_FLOAT, c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::has_child(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::has_key(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::has_val(), c4::yml::ConstNodeRef::id(), c4::yml::Tree::in_arena(), c4::yml::ConstNodeRef::invalid(), c4::yml::NodeRef::invalid(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::is_map(), c4::yml::Tree::is_map(), c4::yml::NodeRef::is_seed(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::is_seq(), c4::yml::Tree::is_seq(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::is_val(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::key(), c4::yml::Tree::key(), c4::yml::key(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::last_child(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::last_sibling(), c4::yml::Location::line, c4::yml::ParseEngine< EventHandler >::location(), c4::yml::ParseEngine< EventHandler >::location_contents(), c4::yml::MAP, c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::next_sibling(), c4::yml::Tree::next_sibling(), c4::yml::NONE, c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::num_children(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::num_siblings(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::parent(), c4::yml::parse_in_arena(), c4::yml::parse_in_place(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::prev_sibling(), c4::yml::ConstNodeRef::readable(), c4::yml::NodeRef::readable(), c4::fmt::real(), c4::yml::Tree::root_id(), c4::yml::Tree::rootref(), c4::yml::SEQ, c4::yml::Tree::size(), c4::to_csubstr(), c4::yml::ConstNodeRef::tree(), and c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::val().

◆ sample_substr()

void sample::sample_substr ( )

demonstrate usage of ryml::substr and ryml::csubstr

These types are imported from the c4core library into the ryml namespace You may have noticed above the use of a csubstr class. This class is defined in another library, c4core, which is imported by ryml. This is a library I use with my projects consisting of multiplatform low-level utilities. One of these is c4::csubstr (the name comes from "constant substring") which is a non-owning read-only string view, with many methods that make it practical to use (I would certainly argue more practical than std::string). In fact, c4::csubstr and its writeable counterpart c4::substr are the workhorses of the ryml parsing and serialization code.

See also
Substring: read/write string views

Definition at line 936 of file quickstart.cpp.

937 {
938  // substr is a mutable view: pointer and length to a string in memory.
939  // csubstr is a const-substr (immutable).
940 
941  // construct from explicit args
942  {
943  const char foobar_str[] = "foobar";
944  auto s = ryml::csubstr(foobar_str, strlen(foobar_str));
945  CHECK(s == "foobar");
946  CHECK(s.size() == 6);
947  CHECK(s.data() == foobar_str);
948  CHECK(s.size() == s.len);
949  CHECK(s.data() == s.str);
950  }
951 
952  // construct from a string array
953  {
954  const char foobar_str[] = "foobar";
955  ryml::csubstr s = foobar_str;
956  CHECK(s == "foobar");
957  CHECK(s != "foobar0");
958  CHECK(s.size() == 6); // does not include the terminating \0
959  CHECK(s.data() == foobar_str);
960  CHECK(s.size() == s.len);
961  CHECK(s.data() == s.str);
962  }
963  // you can also declare directly in-place from an array:
964  {
965  ryml::csubstr s = "foobar";
966  CHECK(s == "foobar");
967  CHECK(s != "foobar0");
968  CHECK(s.size() == 6);
969  CHECK(s.size() == s.len);
970  CHECK(s.data() == s.str);
971  }
972 
973  // construct from a C-string:
974  //
975  // Since the input is only a pointer, the string length can only
976  // be found with a call to strlen(). To make this cost evident, we
977  // require calling to_csubstr():
978  {
979  const char *foobar_str = "foobar";
980  ryml::csubstr s = ryml::to_csubstr(foobar_str);
981  CHECK(s == "foobar");
982  CHECK(s != "foobar0");
983  CHECK(s.size() == 6);
984  CHECK(s.size() == s.len);
985  CHECK(s.data() == s.str);
986  }
987 
988  // construct from a std::string: same approach as above.
989  // requires inclusion of the <ryml/std/string.hpp> header
990  // or of the umbrella header <ryml_std.hpp>.
991  //
992  // not including <string> in the default header is a deliberate
993  // design choice to avoid including the heavy std:: allocation
994  // machinery
995  {
996  std::string foobar_str = "foobar";
997  ryml::csubstr s = ryml::to_csubstr(foobar_str); // defined in <ryml/std/string.hpp>
998  CHECK(s == "foobar");
999  CHECK(s != "foobar0");
1000  CHECK(s.size() == 6);
1001  CHECK(s.size() == s.len);
1002  CHECK(s.data() == s.str);
1003  }
1004 
1005  // convert substr -> csubstr
1006  {
1007  char buf[] = "foo";
1008  ryml::substr foo = buf;
1009  CHECK(foo.len == 3);
1010  CHECK(foo.data() == buf);
1011  ryml::csubstr cfoo = foo;
1012  CHECK(cfoo.data() == buf);
1013  }
1014  // cannot convert csubstr -> substr:
1015  {
1016  // ryml::substr foo2 = cfoo; // compile error: cannot write to csubstr
1017  }
1018 
1019  // construct from char[]/const char[]: mutable vs immutable memory
1020  {
1021  char const foobar_str_ro[] = "foobar"; // ro := read-only
1022  char foobar_str_rw[] = "foobar"; // rw := read-write
1023  static_assert(std::is_array<decltype(foobar_str_ro)>::value, "this is an array");
1024  static_assert(std::is_array<decltype(foobar_str_rw)>::value, "this is an array");
1025  // csubstr <- read-only memory
1026  {
1027  ryml::csubstr foobar = foobar_str_ro;
1028  CHECK(foobar.data() == foobar_str_ro);
1029  CHECK(foobar.size() == strlen(foobar_str_ro));
1030  CHECK(foobar == "foobar"); // AKA strcmp
1031  }
1032  // csubstr <- read-write memory: you can create an immutable csubstr from mutable memory
1033  {
1034  ryml::csubstr foobar = foobar_str_rw;
1035  CHECK(foobar.data() == foobar_str_rw);
1036  CHECK(foobar.size() == strlen(foobar_str_rw));
1037  CHECK(foobar == "foobar"); // AKA strcmp
1038  }
1039  // substr <- read-write memory.
1040  {
1041  ryml::substr foobar = foobar_str_rw;
1042  CHECK(foobar.data() == foobar_str_rw);
1043  CHECK(foobar.size() == strlen(foobar_str_rw));
1044  CHECK(foobar == "foobar"); // AKA strcmp
1045  }
1046  // substr <- ro is impossible.
1047  {
1048  //ryml::substr foobar = foobar_str_ro; // compile error!
1049  }
1050  }
1051 
1052  // construct from char*/const char*: mutable vs immutable memory.
1053  // use to_substr()/to_csubstr()
1054  {
1055  char const* foobar_str_ro = "foobar"; // ro := read-only
1056  char foobar_str_rw_[] = "foobar"; // rw := read-write
1057  char * foobar_str_rw = foobar_str_rw_; // rw := read-write
1058  static_assert(!std::is_array<decltype(foobar_str_ro)>::value, "this is a decayed pointer");
1059  static_assert(!std::is_array<decltype(foobar_str_rw)>::value, "this is a decayed pointer");
1060  // csubstr <- read-only memory
1061  {
1062  //ryml::csubstr foobar = foobar_str_ro; // compile error: length is not known
1063  ryml::csubstr foobar = ryml::to_csubstr(foobar_str_ro);
1064  CHECK(foobar.data() == foobar_str_ro);
1065  CHECK(foobar.size() == strlen(foobar_str_ro));
1066  CHECK(foobar == "foobar"); // AKA strcmp
1067  }
1068  // csubstr <- read-write memory: you can create an immutable csubstr from mutable memory
1069  {
1070  ryml::csubstr foobar = ryml::to_csubstr(foobar_str_rw);
1071  CHECK(foobar.data() == foobar_str_rw);
1072  CHECK(foobar.size() == strlen(foobar_str_rw));
1073  CHECK(foobar == "foobar"); // AKA strcmp
1074  }
1075  // substr <- read-write memory.
1076  {
1077  ryml::substr foobar = ryml::to_substr(foobar_str_rw);
1078  CHECK(foobar.data() == foobar_str_rw);
1079  CHECK(foobar.size() == strlen(foobar_str_rw));
1080  CHECK(foobar == "foobar"); // AKA strcmp
1081  }
1082  // substr <- read-only is impossible.
1083  {
1084  //ryml::substr foobar = ryml::to_substr(foobar_str_ro); // compile error!
1085  }
1086  }
1087 
1088  // substr is mutable, without changing the size:
1089  {
1090  char buf[] = "foobar";
1091  ryml::substr foobar = buf;
1092  CHECK(foobar == "foobar");
1093  foobar[0] = 'F'; CHECK(foobar == "Foobar");
1094  foobar.back() = 'R'; CHECK(foobar == "FoobaR");
1095  foobar.reverse(); CHECK(foobar == "RabooF");
1096  foobar.reverse(); CHECK(foobar == "FoobaR");
1097  foobar.reverse_sub(1, 4); CHECK(foobar == "FabooR");
1098  foobar.reverse_sub(1, 4); CHECK(foobar == "FoobaR");
1099  foobar.reverse_range(2, 5); CHECK(foobar == "FoaboR");
1100  foobar.reverse_range(2, 5); CHECK(foobar == "FoobaR");
1101  foobar.replace('o', '0'); CHECK(foobar == "F00baR");
1102  foobar.replace('a', '_'); CHECK(foobar == "F00b_R");
1103  foobar.replace("_0b", 'a'); CHECK(foobar == "FaaaaR");
1104  foobar.toupper(); CHECK(foobar == "FAAAAR");
1105  foobar.tolower(); CHECK(foobar == "faaaar");
1106  foobar.fill('.'); CHECK(foobar == "......");
1107  // see also:
1108  // - .erase()
1109  // - .replace_all()
1110  }
1111 
1112  // sub-views
1113  {
1114  ryml::csubstr s = "fooFOObarBAR";
1115  CHECK(s.len == 12u);
1116  // sub(): <- first,[num]
1117  CHECK(s.sub(0) == "fooFOObarBAR");
1118  CHECK(s.sub(0, 12) == "fooFOObarBAR");
1119  CHECK(s.sub(0, 3) == "foo" );
1120  CHECK(s.sub(3) == "FOObarBAR");
1121  CHECK(s.sub(3, 3) == "FOO" );
1122  CHECK(s.sub(6) == "barBAR");
1123  CHECK(s.sub(6, 3) == "bar" );
1124  CHECK(s.sub(9) == "BAR");
1125  CHECK(s.sub(9, 3) == "BAR");
1126  // first(): <- length
1127  CHECK(s.first(0) == "" );
1128  CHECK(s.first(1) == "f" );
1129  CHECK(s.first(2) != "f" );
1130  CHECK(s.first(2) == "fo" );
1131  CHECK(s.first(3) == "foo");
1132  // last(): <- length
1133  CHECK(s.last(0) == "");
1134  CHECK(s.last(1) == "R");
1135  CHECK(s.last(2) == "AR");
1136  CHECK(s.last(3) == "BAR");
1137  // range(): <- first, last
1138  CHECK(s.range(0, 12) == "fooFOObarBAR");
1139  CHECK(s.range(1, 12) == "ooFOObarBAR");
1140  CHECK(s.range(1, 11) == "ooFOObarBA" );
1141  CHECK(s.range(2, 10) == "oFOObarB" );
1142  CHECK(s.range(3, 9) == "FOObar" );
1143  // offs(): offset from beginning, end
1144  CHECK(s.offs(0, 0) == "fooFOObarBAR");
1145  CHECK(s.offs(1, 0) == "ooFOObarBAR");
1146  CHECK(s.offs(1, 1) == "ooFOObarBA" );
1147  CHECK(s.offs(2, 1) == "oFOObarBA" );
1148  CHECK(s.offs(2, 2) == "oFOObarB" );
1149  CHECK(s.offs(3, 3) == "FOObar" );
1150  // right_of(): <- pos, include_pos
1151  CHECK(s.right_of(0, true) == "fooFOObarBAR");
1152  CHECK(s.right_of(0, false) == "ooFOObarBAR");
1153  CHECK(s.right_of(1, true) == "ooFOObarBAR");
1154  CHECK(s.right_of(1, false) == "oFOObarBAR");
1155  CHECK(s.right_of(2, true) == "oFOObarBAR");
1156  CHECK(s.right_of(2, false) == "FOObarBAR");
1157  CHECK(s.right_of(3, true) == "FOObarBAR");
1158  CHECK(s.right_of(3, false) == "OObarBAR");
1159  // left_of() <- pos, include_pos
1160  CHECK(s.left_of(12, false) == "fooFOObarBAR");
1161  CHECK(s.left_of(11, true) == "fooFOObarBAR");
1162  CHECK(s.left_of(11, false) == "fooFOObarBA" );
1163  CHECK(s.left_of(10, true) == "fooFOObarBA" );
1164  CHECK(s.left_of(10, false) == "fooFOObarB" );
1165  CHECK(s.left_of( 9, true) == "fooFOObarB" );
1166  CHECK(s.left_of( 9, false) == "fooFOObar" );
1167  // left_of(),right_of() <- substr
1168  ryml::csubstr FOO = s.sub(3, 3);
1169  CHECK(s.is_super(FOO)); // required for the following
1170  CHECK(s.left_of(FOO) == "foo");
1171  CHECK(s.right_of(FOO) == "barBAR");
1172  }
1173 
1174  // printing a substr/csubstr using printf-like
1175  {
1176  ryml::csubstr s = "some substring";
1177  ryml::csubstr some = s.first(4);
1178  CHECK(some == "some");
1179  CHECK(s == "some substring");
1180  // To print a csubstr using printf(), use the %.*s format specifier:
1181  {
1182  char result[32] = {0};
1183  std::snprintf(result, sizeof(result), "%.*s", (int)some.len, some.str);
1184  printf("~~~%s~~~\n", result);
1185  CHECK(ryml::to_csubstr((const char*)result) == "some");
1186  CHECK(ryml::to_csubstr((const char*)result) == some);
1187  }
1188  // But NOTE: because this is a string view type, in general
1189  // the C-string is NOT zero terminated. So NEVER print it
1190  // directly, or it will overflow past the end of the given
1191  // substr, with a potential unbounded access. For example,
1192  // this is bad:
1193  {
1194  char result[32] = {0};
1195  std::snprintf(result, sizeof(result), "%s", some.str); // ERROR! do not print the c-string directly
1196  CHECK(ryml::to_csubstr((const char*)result) == "some substring");
1197  CHECK(ryml::to_csubstr((const char*)result) == s);
1198  }
1199  }
1200 
1201  // printing a substr/csubstr using ostreams
1202  {
1203  ryml::csubstr s = "some substring";
1204  ryml::csubstr some = s.first(4);
1205  CHECK(some == "some");
1206  CHECK(s == "some substring");
1207  // simple! just use plain operator<<
1208  {
1209  std::stringstream ss;
1210  ss << s;
1211  CHECK(ss.str() == "some substring"); // as expected
1212  CHECK(ss.str() == s); // as expected
1213  }
1214  // But NOTE: because this is a string view type, in general
1215  // the C-string is NOT zero terminated. So NEVER print it
1216  // directly, or it will overflow past the end of the given
1217  // substr, with a potential unbounded access. For example,
1218  // this is bad:
1219  {
1220  std::stringstream ss;
1221  ss << some.str; // ERROR! do not print the c-string directly
1222  CHECK(ss.str() == "some substring"); // NOT "some"
1223  CHECK(ss.str() == s); // NOT some
1224  }
1225  // this is also bad (the same)
1226  {
1227  std::stringstream ss;
1228  ss << some.data(); // ERROR! do not print the c-string directly
1229  CHECK(ss.str() == "some substring"); // NOT "some"
1230  CHECK(ss.str() == s); // NOT some
1231  }
1232  // this is ok:
1233  {
1234  std::stringstream ss;
1235  ss << some;
1236  CHECK(ss.str() == "some"); // ok
1237  CHECK(ss.str() == some); // ok
1238  }
1239  }
1240 
1241  // is_sub(),is_super()
1242  {
1243  ryml::csubstr foobar = "foobar";
1244  ryml::csubstr foo = foobar.first(3);
1245  CHECK(foo.is_sub(foobar));
1246  CHECK(foo.is_sub(foo));
1247  CHECK(!foo.is_super(foobar));
1248  CHECK(!foobar.is_sub(foo));
1249  // identity comparison is true:
1250  CHECK(foo.is_super(foo));
1251  CHECK(foo.is_sub(foo));
1252  CHECK(foobar.is_sub(foobar));
1253  CHECK(foobar.is_super(foobar));
1254  }
1255 
1256  // overlaps()
1257  {
1258  ryml::csubstr foobar = "foobar";
1259  ryml::csubstr foo = foobar.first(3);
1260  ryml::csubstr oba = foobar.offs(2, 1);
1261  ryml::csubstr abc = "abc";
1262  CHECK(foobar.overlaps(foo));
1263  CHECK(foobar.overlaps(oba));
1264  CHECK(foo.overlaps(foobar));
1265  CHECK(foo.overlaps(oba));
1266  CHECK(!foo.overlaps(abc));
1267  CHECK(!abc.overlaps(foo));
1268  }
1269 
1270  // triml(): trim characters from the left
1271  // trimr(): trim characters from the right
1272  // trim(): trim characters from left AND right
1273  {
1274  CHECK(ryml::csubstr(" \t\n\rcontents without whitespace\t \n\r").trim("\t \n\r") == "contents without whitespace");
1275  ryml::csubstr aaabbb = "aaabbb";
1276  ryml::csubstr aaa___bbb = "aaa___bbb";
1277  // trim a character:
1278  CHECK(aaabbb.triml('a') == aaabbb.last(3)); // bbb
1279  CHECK(aaabbb.trimr('a') == aaabbb);
1280  CHECK(aaabbb.trim ('a') == aaabbb.last(3)); // bbb
1281  CHECK(aaabbb.triml('b') == aaabbb);
1282  CHECK(aaabbb.trimr('b') == aaabbb.first(3)); // aaa
1283  CHECK(aaabbb.trim ('b') == aaabbb.first(3)); // aaa
1284  CHECK(aaabbb.triml('c') == aaabbb);
1285  CHECK(aaabbb.trimr('c') == aaabbb);
1286  CHECK(aaabbb.trim ('c') == aaabbb);
1287  CHECK(aaa___bbb.triml('a') == aaa___bbb.last(6)); // ___bbb
1288  CHECK(aaa___bbb.trimr('a') == aaa___bbb);
1289  CHECK(aaa___bbb.trim ('a') == aaa___bbb.last(6)); // ___bbb
1290  CHECK(aaa___bbb.triml('b') == aaa___bbb);
1291  CHECK(aaa___bbb.trimr('b') == aaa___bbb.first(6)); // aaa___
1292  CHECK(aaa___bbb.trim ('b') == aaa___bbb.first(6)); // aaa___
1293  CHECK(aaa___bbb.triml('c') == aaa___bbb);
1294  CHECK(aaa___bbb.trimr('c') == aaa___bbb);
1295  CHECK(aaa___bbb.trim ('c') == aaa___bbb);
1296  // trim ANY of the characters:
1297  CHECK(aaabbb.triml("ab") == "");
1298  CHECK(aaabbb.trimr("ab") == "");
1299  CHECK(aaabbb.trim ("ab") == "");
1300  CHECK(aaabbb.triml("ba") == "");
1301  CHECK(aaabbb.trimr("ba") == "");
1302  CHECK(aaabbb.trim ("ba") == "");
1303  CHECK(aaabbb.triml("cd") == aaabbb);
1304  CHECK(aaabbb.trimr("cd") == aaabbb);
1305  CHECK(aaabbb.trim ("cd") == aaabbb);
1306  CHECK(aaa___bbb.triml("ab") == aaa___bbb.last(6)); // ___bbb
1307  CHECK(aaa___bbb.triml("ba") == aaa___bbb.last(6)); // ___bbb
1308  CHECK(aaa___bbb.triml("cd") == aaa___bbb);
1309  CHECK(aaa___bbb.trimr("ab") == aaa___bbb.first(6)); // aaa___
1310  CHECK(aaa___bbb.trimr("ba") == aaa___bbb.first(6)); // aaa___
1311  CHECK(aaa___bbb.trimr("cd") == aaa___bbb);
1312  CHECK(aaa___bbb.trim ("ab") == aaa___bbb.range(3, 6)); // ___
1313  CHECK(aaa___bbb.trim ("ba") == aaa___bbb.range(3, 6)); // ___
1314  CHECK(aaa___bbb.trim ("cd") == aaa___bbb);
1315  }
1316 
1317  // unquoted():
1318  {
1319  CHECK(ryml::csubstr(R"('this is is single quoted')").unquoted() == "this is is single quoted");
1320  CHECK(ryml::csubstr(R"("this is is double quoted")").unquoted() == "this is is double quoted");
1321  }
1322 
1323  // stripl(): remove pattern from the left
1324  // stripr(): remove pattern from the right
1325  {
1326  ryml::csubstr abc___cba = "abc___cba";
1327  ryml::csubstr abc___abc = "abc___abc";
1328  CHECK(abc___cba.stripl("abc") == abc___cba.last(6)); // ___cba
1329  CHECK(abc___cba.stripr("abc") == abc___cba);
1330  CHECK(abc___cba.stripl("ab") == abc___cba.last(7)); // c___cba
1331  CHECK(abc___cba.stripr("ab") == abc___cba);
1332  CHECK(abc___cba.stripl("a") == abc___cba.last(8)); // bc___cba, same as triml('a')
1333  CHECK(abc___cba.stripr("a") == abc___cba.first(8));
1334  CHECK(abc___abc.stripl("abc") == abc___abc.last(6)); // ___abc
1335  CHECK(abc___abc.stripr("abc") == abc___abc.first(6)); // abc___
1336  CHECK(abc___abc.stripl("ab") == abc___abc.last(7)); // c___cba
1337  CHECK(abc___abc.stripr("ab") == abc___abc);
1338  CHECK(abc___abc.stripl("a") == abc___abc.last(8)); // bc___cba, same as triml('a')
1339  CHECK(abc___abc.stripr("a") == abc___abc);
1340  }
1341 
1342  // begins_with()/ends_with()
1343  // begins_with_any()/ends_with_any()
1344  {
1345  ryml::csubstr s = "foobar123";
1346  // char overloads
1347  CHECK(s.begins_with('f'));
1348  CHECK(s.ends_with('3'));
1349  CHECK(!s.ends_with('2'));
1350  CHECK(!s.ends_with('o'));
1351  // char[] overloads
1352  CHECK(s.begins_with("foobar"));
1353  CHECK(s.begins_with("foo"));
1354  CHECK(s.begins_with_any("foo"));
1355  CHECK(!s.begins_with("oof"));
1356  CHECK(s.begins_with_any("oof"));
1357  CHECK(s.ends_with("23"));
1358  CHECK(s.ends_with("123"));
1359  CHECK(s.ends_with_any("123"));
1360  CHECK(!s.ends_with("321"));
1361  CHECK(s.ends_with_any("231"));
1362  }
1363 
1364  // select()
1365  {
1366  ryml::csubstr s = "0123456789";
1367  CHECK(s.select('0') == s.sub(0, 1));
1368  CHECK(s.select('1') == s.sub(1, 1));
1369  CHECK(s.select('2') == s.sub(2, 1));
1370  CHECK(s.select('8') == s.sub(8, 1));
1371  CHECK(s.select('9') == s.sub(9, 1));
1372  CHECK(s.select("0123") == s.range(0, 4));
1373  CHECK(s.select("012" ) == s.range(0, 3));
1374  CHECK(s.select("01" ) == s.range(0, 2));
1375  CHECK(s.select("0" ) == s.range(0, 1));
1376  CHECK(s.select( "123") == s.range(1, 4));
1377  CHECK(s.select( "23") == s.range(2, 4));
1378  CHECK(s.select( "3") == s.range(3, 4));
1379  }
1380 
1381  // find()
1382  {
1383  ryml::csubstr s012345 = "012345";
1384  // find single characters:
1385  CHECK(s012345.find('a') == ryml::npos);
1386  CHECK(s012345.find('0' ) == 0u);
1387  CHECK(s012345.find('0', 1u) == ryml::npos);
1388  CHECK(s012345.find('1' ) == 1u);
1389  CHECK(s012345.find('1', 2u) == ryml::npos);
1390  CHECK(s012345.find('2' ) == 2u);
1391  CHECK(s012345.find('2', 3u) == ryml::npos);
1392  CHECK(s012345.find('3' ) == 3u);
1393  CHECK(s012345.find('3', 4u) == ryml::npos);
1394  // find patterns
1395  CHECK(s012345.find("ab" ) == ryml::npos);
1396  CHECK(s012345.find("01" ) == 0u);
1397  CHECK(s012345.find("01", 1u) == ryml::npos);
1398  CHECK(s012345.find("12" ) == 1u);
1399  CHECK(s012345.find("12", 2u) == ryml::npos);
1400  CHECK(s012345.find("23" ) == 2u);
1401  CHECK(s012345.find("23", 3u) == ryml::npos);
1402  }
1403 
1404  // count(): count the number of occurrences of a character
1405  {
1406  ryml::csubstr buf = "00110022003300440055";
1407  CHECK(buf.count('1' ) == 2u);
1408  CHECK(buf.count('1', 0u) == 2u);
1409  CHECK(buf.count('1', 1u) == 2u);
1410  CHECK(buf.count('1', 2u) == 2u);
1411  CHECK(buf.count('1', 3u) == 1u);
1412  CHECK(buf.count('1', 4u) == 0u);
1413  CHECK(buf.count('1', 5u) == 0u);
1414  CHECK(buf.count('0' ) == 10u);
1415  CHECK(buf.count('0', 0u) == 10u);
1416  CHECK(buf.count('0', 1u) == 9u);
1417  CHECK(buf.count('0', 2u) == 8u);
1418  CHECK(buf.count('0', 3u) == 8u);
1419  CHECK(buf.count('0', 4u) == 8u);
1420  CHECK(buf.count('0', 5u) == 7u);
1421  CHECK(buf.count('0', 6u) == 6u);
1422  CHECK(buf.count('0', 7u) == 6u);
1423  CHECK(buf.count('0', 8u) == 6u);
1424  CHECK(buf.count('0', 9u) == 5u);
1425  CHECK(buf.count('0', 10u) == 4u);
1426  CHECK(buf.count('0', 11u) == 4u);
1427  CHECK(buf.count('0', 12u) == 4u);
1428  CHECK(buf.count('0', 13u) == 3u);
1429  CHECK(buf.count('0', 14u) == 2u);
1430  CHECK(buf.count('0', 15u) == 2u);
1431  CHECK(buf.count('0', 16u) == 2u);
1432  CHECK(buf.count('0', 17u) == 1u);
1433  CHECK(buf.count('0', 18u) == 0u);
1434  CHECK(buf.count('0', 19u) == 0u);
1435  CHECK(buf.count('0', 20u) == 0u);
1436  }
1437 
1438  // first_of(),last_of()
1439  {
1440  ryml::csubstr s012345 = "012345";
1441  CHECK(s012345.first_of('a') == ryml::npos);
1442  CHECK(s012345.first_of("ab") == ryml::npos);
1443  CHECK(s012345.first_of('0') == 0u);
1444  CHECK(s012345.first_of("0") == 0u);
1445  CHECK(s012345.first_of("01") == 0u);
1446  CHECK(s012345.first_of("10") == 0u);
1447  CHECK(s012345.first_of("012") == 0u);
1448  CHECK(s012345.first_of("210") == 0u);
1449  CHECK(s012345.first_of("0123") == 0u);
1450  CHECK(s012345.first_of("3210") == 0u);
1451  CHECK(s012345.first_of("01234") == 0u);
1452  CHECK(s012345.first_of("43210") == 0u);
1453  CHECK(s012345.first_of("012345") == 0u);
1454  CHECK(s012345.first_of("543210") == 0u);
1455  CHECK(s012345.first_of('5') == 5u);
1456  CHECK(s012345.first_of("5") == 5u);
1457  CHECK(s012345.first_of("45") == 4u);
1458  CHECK(s012345.first_of("54") == 4u);
1459  CHECK(s012345.first_of("345") == 3u);
1460  CHECK(s012345.first_of("543") == 3u);
1461  CHECK(s012345.first_of("2345") == 2u);
1462  CHECK(s012345.first_of("5432") == 2u);
1463  CHECK(s012345.first_of("12345") == 1u);
1464  CHECK(s012345.first_of("54321") == 1u);
1465  CHECK(s012345.first_of("012345") == 0u);
1466  CHECK(s012345.first_of("543210") == 0u);
1467  CHECK(s012345.first_of('0', 6u) == ryml::npos);
1468  CHECK(s012345.first_of('5', 6u) == ryml::npos);
1469  CHECK(s012345.first_of("012345", 6u) == ryml::npos);
1470  //
1471  CHECK(s012345.last_of('a') == ryml::npos);
1472  CHECK(s012345.last_of("ab") == ryml::npos);
1473  CHECK(s012345.last_of('0') == 0u);
1474  CHECK(s012345.last_of("0") == 0u);
1475  CHECK(s012345.last_of("01") == 1u);
1476  CHECK(s012345.last_of("10") == 1u);
1477  CHECK(s012345.last_of("012") == 2u);
1478  CHECK(s012345.last_of("210") == 2u);
1479  CHECK(s012345.last_of("0123") == 3u);
1480  CHECK(s012345.last_of("3210") == 3u);
1481  CHECK(s012345.last_of("01234") == 4u);
1482  CHECK(s012345.last_of("43210") == 4u);
1483  CHECK(s012345.last_of("012345") == 5u);
1484  CHECK(s012345.last_of("543210") == 5u);
1485  CHECK(s012345.last_of('5') == 5u);
1486  CHECK(s012345.last_of("5") == 5u);
1487  CHECK(s012345.last_of("45") == 5u);
1488  CHECK(s012345.last_of("54") == 5u);
1489  CHECK(s012345.last_of("345") == 5u);
1490  CHECK(s012345.last_of("543") == 5u);
1491  CHECK(s012345.last_of("2345") == 5u);
1492  CHECK(s012345.last_of("5432") == 5u);
1493  CHECK(s012345.last_of("12345") == 5u);
1494  CHECK(s012345.last_of("54321") == 5u);
1495  CHECK(s012345.last_of("012345") == 5u);
1496  CHECK(s012345.last_of("543210") == 5u);
1497  CHECK(s012345.last_of('0', 6u) == 0u);
1498  CHECK(s012345.last_of('5', 6u) == 5u);
1499  CHECK(s012345.last_of("012345", 6u) == 5u);
1500  }
1501 
1502  // first_not_of(), last_not_of()
1503  {
1504  ryml::csubstr s012345 = "012345";
1505  CHECK(s012345.first_not_of('a') == 0u);
1506  CHECK(s012345.first_not_of("ab") == 0u);
1507  CHECK(s012345.first_not_of('0') == 1u);
1508  CHECK(s012345.first_not_of("0") == 1u);
1509  CHECK(s012345.first_not_of("01") == 2u);
1510  CHECK(s012345.first_not_of("10") == 2u);
1511  CHECK(s012345.first_not_of("012") == 3u);
1512  CHECK(s012345.first_not_of("210") == 3u);
1513  CHECK(s012345.first_not_of("0123") == 4u);
1514  CHECK(s012345.first_not_of("3210") == 4u);
1515  CHECK(s012345.first_not_of("01234") == 5u);
1516  CHECK(s012345.first_not_of("43210") == 5u);
1517  CHECK(s012345.first_not_of("012345") == ryml::npos);
1518  CHECK(s012345.first_not_of("543210") == ryml::npos);
1519  CHECK(s012345.first_not_of('5') == 0u);
1520  CHECK(s012345.first_not_of("5") == 0u);
1521  CHECK(s012345.first_not_of("45") == 0u);
1522  CHECK(s012345.first_not_of("54") == 0u);
1523  CHECK(s012345.first_not_of("345") == 0u);
1524  CHECK(s012345.first_not_of("543") == 0u);
1525  CHECK(s012345.first_not_of("2345") == 0u);
1526  CHECK(s012345.first_not_of("5432") == 0u);
1527  CHECK(s012345.first_not_of("12345") == 0u);
1528  CHECK(s012345.first_not_of("54321") == 0u);
1529  CHECK(s012345.first_not_of("012345") == ryml::npos);
1530  CHECK(s012345.first_not_of("543210") == ryml::npos);
1531  CHECK(s012345.last_not_of('a') == 5u);
1532  CHECK(s012345.last_not_of("ab") == 5u);
1533  CHECK(s012345.last_not_of('5') == 4u);
1534  CHECK(s012345.last_not_of("5") == 4u);
1535  CHECK(s012345.last_not_of("45") == 3u);
1536  CHECK(s012345.last_not_of("54") == 3u);
1537  CHECK(s012345.last_not_of("345") == 2u);
1538  CHECK(s012345.last_not_of("543") == 2u);
1539  CHECK(s012345.last_not_of("2345") == 1u);
1540  CHECK(s012345.last_not_of("5432") == 1u);
1541  CHECK(s012345.last_not_of("12345") == 0u);
1542  CHECK(s012345.last_not_of("54321") == 0u);
1543  CHECK(s012345.last_not_of("012345") == ryml::npos);
1544  CHECK(s012345.last_not_of("543210") == ryml::npos);
1545  CHECK(s012345.last_not_of('0') == 5u);
1546  CHECK(s012345.last_not_of("0") == 5u);
1547  CHECK(s012345.last_not_of("01") == 5u);
1548  CHECK(s012345.last_not_of("10") == 5u);
1549  CHECK(s012345.last_not_of("012") == 5u);
1550  CHECK(s012345.last_not_of("210") == 5u);
1551  CHECK(s012345.last_not_of("0123") == 5u);
1552  CHECK(s012345.last_not_of("3210") == 5u);
1553  CHECK(s012345.last_not_of("01234") == 5u);
1554  CHECK(s012345.last_not_of("43210") == 5u);
1555  CHECK(s012345.last_not_of("012345") == ryml::npos);
1556  CHECK(s012345.last_not_of("543210") == ryml::npos);
1557  }
1558 
1559  // first_non_empty_span()
1560  {
1561  CHECK(ryml::csubstr("foo bar").first_non_empty_span() == "foo");
1562  CHECK(ryml::csubstr(" foo bar").first_non_empty_span() == "foo");
1563  CHECK(ryml::csubstr("\n \r \t foo bar").first_non_empty_span() == "foo");
1564  CHECK(ryml::csubstr("\n \r \t foo\n\r\t bar").first_non_empty_span() == "foo");
1565  CHECK(ryml::csubstr("\n \r \t foo\n\r\t bar").first_non_empty_span() == "foo");
1566  CHECK(ryml::csubstr(",\n \r \t foo\n\r\t bar").first_non_empty_span() == ",");
1567  }
1568  // first_uint_span()
1569  {
1570  CHECK(ryml::csubstr("1234 asdkjh").first_uint_span() == "1234");
1571  CHECK(ryml::csubstr("1234\rasdkjh").first_uint_span() == "1234");
1572  CHECK(ryml::csubstr("1234\tasdkjh").first_uint_span() == "1234");
1573  CHECK(ryml::csubstr("1234\nasdkjh").first_uint_span() == "1234");
1574  CHECK(ryml::csubstr("1234]asdkjh").first_uint_span() == "1234");
1575  CHECK(ryml::csubstr("1234)asdkjh").first_uint_span() == "1234");
1576  CHECK(ryml::csubstr("1234gasdkjh").first_uint_span() == "");
1577  }
1578  // first_int_span()
1579  {
1580  CHECK(ryml::csubstr("-1234 asdkjh").first_int_span() == "-1234");
1581  CHECK(ryml::csubstr("-1234\rasdkjh").first_int_span() == "-1234");
1582  CHECK(ryml::csubstr("-1234\tasdkjh").first_int_span() == "-1234");
1583  CHECK(ryml::csubstr("-1234\nasdkjh").first_int_span() == "-1234");
1584  CHECK(ryml::csubstr("-1234]asdkjh").first_int_span() == "-1234");
1585  CHECK(ryml::csubstr("-1234)asdkjh").first_int_span() == "-1234");
1586  CHECK(ryml::csubstr("-1234gasdkjh").first_int_span() == "");
1587  }
1588  // first_real_span()
1589  {
1590  CHECK(ryml::csubstr("-1234 asdkjh").first_real_span() == "-1234");
1591  CHECK(ryml::csubstr("-1234\rasdkjh").first_real_span() == "-1234");
1592  CHECK(ryml::csubstr("-1234\tasdkjh").first_real_span() == "-1234");
1593  CHECK(ryml::csubstr("-1234\nasdkjh").first_real_span() == "-1234");
1594  CHECK(ryml::csubstr("-1234]asdkjh").first_real_span() == "-1234");
1595  CHECK(ryml::csubstr("-1234)asdkjh").first_real_span() == "-1234");
1596  CHECK(ryml::csubstr("-1234gasdkjh").first_real_span() == "");
1597  CHECK(ryml::csubstr("1.234 asdkjh").first_real_span() == "1.234");
1598  CHECK(ryml::csubstr("1.234e+5 asdkjh").first_real_span() == "1.234e+5");
1599  CHECK(ryml::csubstr("1.234e-5 asdkjh").first_real_span() == "1.234e-5");
1600  CHECK(ryml::csubstr("1.234 asdkjh").first_real_span() == "1.234");
1601  CHECK(ryml::csubstr("1.234e+5 asdkjh").first_real_span() == "1.234e+5");
1602  CHECK(ryml::csubstr("1.234e-5 asdkjh").first_real_span() == "1.234e-5");
1603  CHECK(ryml::csubstr("-1.234 asdkjh").first_real_span() == "-1.234");
1604  CHECK(ryml::csubstr("-1.234e+5 asdkjh").first_real_span() == "-1.234e+5");
1605  CHECK(ryml::csubstr("-1.234e-5 asdkjh").first_real_span() == "-1.234e-5");
1606  // hexadecimal real numbers
1607  CHECK(ryml::csubstr("0x1.e8480p+19 asdkjh").first_real_span() == "0x1.e8480p+19");
1608  CHECK(ryml::csubstr("0x1.e8480p-19 asdkjh").first_real_span() == "0x1.e8480p-19");
1609  CHECK(ryml::csubstr("-0x1.e8480p+19 asdkjh").first_real_span() == "-0x1.e8480p+19");
1610  CHECK(ryml::csubstr("-0x1.e8480p-19 asdkjh").first_real_span() == "-0x1.e8480p-19");
1611  CHECK(ryml::csubstr("+0x1.e8480p+19 asdkjh").first_real_span() == "+0x1.e8480p+19");
1612  CHECK(ryml::csubstr("+0x1.e8480p-19 asdkjh").first_real_span() == "+0x1.e8480p-19");
1613  // binary real numbers
1614  CHECK(ryml::csubstr("0b101.011p+19 asdkjh").first_real_span() == "0b101.011p+19");
1615  CHECK(ryml::csubstr("0b101.011p-19 asdkjh").first_real_span() == "0b101.011p-19");
1616  CHECK(ryml::csubstr("-0b101.011p+19 asdkjh").first_real_span() == "-0b101.011p+19");
1617  CHECK(ryml::csubstr("-0b101.011p-19 asdkjh").first_real_span() == "-0b101.011p-19");
1618  CHECK(ryml::csubstr("+0b101.011p+19 asdkjh").first_real_span() == "+0b101.011p+19");
1619  CHECK(ryml::csubstr("+0b101.011p-19 asdkjh").first_real_span() == "+0b101.011p-19");
1620  // octal real numbers
1621  CHECK(ryml::csubstr("0o173.045p+19 asdkjh").first_real_span() == "0o173.045p+19");
1622  CHECK(ryml::csubstr("0o173.045p-19 asdkjh").first_real_span() == "0o173.045p-19");
1623  CHECK(ryml::csubstr("-0o173.045p+19 asdkjh").first_real_span() == "-0o173.045p+19");
1624  CHECK(ryml::csubstr("-0o173.045p-19 asdkjh").first_real_span() == "-0o173.045p-19");
1625  CHECK(ryml::csubstr("+0o173.045p+19 asdkjh").first_real_span() == "+0o173.045p+19");
1626  CHECK(ryml::csubstr("+0o173.045p-19 asdkjh").first_real_span() == "+0o173.045p-19");
1627  }
1628  // see also is_number()
1629 
1630  // basename(), dirname(), extshort(), extlong()
1631  {
1632  CHECK(ryml::csubstr("/path/to/file.tar.gz").basename() == "file.tar.gz");
1633  CHECK(ryml::csubstr("/path/to/file.tar.gz").dirname() == "/path/to/");
1634  CHECK(ryml::csubstr("C:\\path\\to\\file.tar.gz").basename('\\') == "file.tar.gz");
1635  CHECK(ryml::csubstr("C:\\path\\to\\file.tar.gz").dirname('\\') == "C:\\path\\to\\");
1636  CHECK(ryml::csubstr("/path/to/file.tar.gz").extshort() == "gz");
1637  CHECK(ryml::csubstr("/path/to/file.tar.gz").extlong() == "tar.gz");
1638  CHECK(ryml::csubstr("/path/to/file.tar.gz").name_wo_extshort() == "/path/to/file.tar");
1639  CHECK(ryml::csubstr("/path/to/file.tar.gz").name_wo_extlong() == "/path/to/file");
1640  }
1641 
1642  // split()
1643  {
1644  using namespace ryml;
1645  csubstr parts[] = {"aa", "bb", "cc", "dd", "ee", "ff"};
1646  {
1647  size_t count = 0;
1648  for(csubstr part : csubstr("aa/bb/cc/dd/ee/ff").split('/'))
1649  CHECK(part == parts[count++]);
1650  CHECK(count == 6u);
1651  }
1652  {
1653  size_t count = 0;
1654  for(csubstr part : csubstr("aa.bb.cc.dd.ee.ff").split('.'))
1655  CHECK(part == parts[count++]);
1656  CHECK(count == 6u);
1657  }
1658  {
1659  size_t count = 0;
1660  for(csubstr part : csubstr("aa-bb-cc-dd-ee-ff").split('-'))
1661  CHECK(part == parts[count++]);
1662  CHECK(count == 6u);
1663  }
1664  // see also next_split()
1665  }
1666 
1667  // pop_left(), pop_right() --- non-greedy version
1668  // gpop_left(), gpop_right() --- greedy version
1669  {
1670  const bool skip_empty = true;
1671  // pop_left(): pop the last element from the left
1672  CHECK(ryml::csubstr( "0/1/2" ). pop_left('/' ) == "0" );
1673  CHECK(ryml::csubstr( "/0/1/2" ). pop_left('/' ) == "" );
1674  CHECK(ryml::csubstr("//0/1/2" ). pop_left('/' ) == "" );
1675  CHECK(ryml::csubstr( "0/1/2" ). pop_left('/', skip_empty) == "0" );
1676  CHECK(ryml::csubstr( "/0/1/2" ). pop_left('/', skip_empty) == "/0" );
1677  CHECK(ryml::csubstr("//0/1/2" ). pop_left('/', skip_empty) == "//0" );
1678  // gpop_left(): pop all but the first element (greedy pop)
1679  CHECK(ryml::csubstr( "0/1/2" ).gpop_left('/' ) == "0/1" );
1680  CHECK(ryml::csubstr( "/0/1/2" ).gpop_left('/' ) == "/0/1" );
1681  CHECK(ryml::csubstr("//0/1/2" ).gpop_left('/' ) == "//0/1" );
1682  CHECK(ryml::csubstr( "0/1/2/" ).gpop_left('/' ) == "0/1/2");
1683  CHECK(ryml::csubstr( "/0/1/2/" ).gpop_left('/' ) == "/0/1/2");
1684  CHECK(ryml::csubstr("//0/1/2/" ).gpop_left('/' ) == "//0/1/2");
1685  CHECK(ryml::csubstr( "0/1/2//" ).gpop_left('/' ) == "0/1/2/");
1686  CHECK(ryml::csubstr( "/0/1/2//" ).gpop_left('/' ) == "/0/1/2/");
1687  CHECK(ryml::csubstr("//0/1/2//" ).gpop_left('/' ) == "//0/1/2/");
1688  CHECK(ryml::csubstr( "0/1/2" ).gpop_left('/', skip_empty) == "0/1" );
1689  CHECK(ryml::csubstr( "/0/1/2" ).gpop_left('/', skip_empty) == "/0/1" );
1690  CHECK(ryml::csubstr("//0/1/2" ).gpop_left('/', skip_empty) == "//0/1" );
1691  CHECK(ryml::csubstr( "0/1/2/" ).gpop_left('/', skip_empty) == "0/1" );
1692  CHECK(ryml::csubstr( "/0/1/2/" ).gpop_left('/', skip_empty) == "/0/1" );
1693  CHECK(ryml::csubstr("//0/1/2/" ).gpop_left('/', skip_empty) == "//0/1" );
1694  CHECK(ryml::csubstr( "0/1/2//" ).gpop_left('/', skip_empty) == "0/1" );
1695  CHECK(ryml::csubstr( "/0/1/2//" ).gpop_left('/', skip_empty) == "/0/1" );
1696  CHECK(ryml::csubstr("//0/1/2//" ).gpop_left('/', skip_empty) == "//0/1" );
1697  // pop_right(): pop the last element from the right
1698  CHECK(ryml::csubstr( "0/1/2" ). pop_right('/' ) == "2" );
1699  CHECK(ryml::csubstr( "0/1/2/" ). pop_right('/' ) == "" );
1700  CHECK(ryml::csubstr( "0/1/2//" ). pop_right('/' ) == "" );
1701  CHECK(ryml::csubstr( "0/1/2" ). pop_right('/', skip_empty) == "2" );
1702  CHECK(ryml::csubstr( "0/1/2/" ). pop_right('/', skip_empty) == "2/" );
1703  CHECK(ryml::csubstr( "0/1/2//" ). pop_right('/', skip_empty) == "2//" );
1704  // gpop_right(): pop all but the first element (greedy pop)
1705  CHECK(ryml::csubstr( "0/1/2" ).gpop_right('/' ) == "1/2");
1706  CHECK(ryml::csubstr( "0/1/2/" ).gpop_right('/' ) == "1/2/" );
1707  CHECK(ryml::csubstr( "0/1/2//" ).gpop_right('/' ) == "1/2//" );
1708  CHECK(ryml::csubstr( "/0/1/2" ).gpop_right('/' ) == "0/1/2");
1709  CHECK(ryml::csubstr( "/0/1/2/" ).gpop_right('/' ) == "0/1/2/" );
1710  CHECK(ryml::csubstr( "/0/1/2//" ).gpop_right('/' ) == "0/1/2//" );
1711  CHECK(ryml::csubstr("//0/1/2" ).gpop_right('/' ) == "/0/1/2");
1712  CHECK(ryml::csubstr("//0/1/2/" ).gpop_right('/' ) == "/0/1/2/" );
1713  CHECK(ryml::csubstr("//0/1/2//" ).gpop_right('/' ) == "/0/1/2//" );
1714  CHECK(ryml::csubstr( "0/1/2" ).gpop_right('/', skip_empty) == "1/2");
1715  CHECK(ryml::csubstr( "0/1/2/" ).gpop_right('/', skip_empty) == "1/2/" );
1716  CHECK(ryml::csubstr( "0/1/2//" ).gpop_right('/', skip_empty) == "1/2//" );
1717  CHECK(ryml::csubstr( "/0/1/2" ).gpop_right('/', skip_empty) == "1/2");
1718  CHECK(ryml::csubstr( "/0/1/2/" ).gpop_right('/', skip_empty) == "1/2/" );
1719  CHECK(ryml::csubstr( "/0/1/2//" ).gpop_right('/', skip_empty) == "1/2//" );
1720  CHECK(ryml::csubstr("//0/1/2" ).gpop_right('/', skip_empty) == "1/2");
1721  CHECK(ryml::csubstr("//0/1/2/" ).gpop_right('/', skip_empty) == "1/2/" );
1722  CHECK(ryml::csubstr("//0/1/2//" ).gpop_right('/', skip_empty) == "1/2//" );
1723  }
1724 }
substr to_substr(substr s) noexcept
neutral version for use in generic code
Definition: substr.hpp:2187
@ npos
a null string position
Definition: common.hpp:266
Definition: ryml.hpp:6

References CHECK, c4::yml::npos, c4::to_csubstr(), and c4::to_substr().

◆ sample_parse_file()

void sample::sample_parse_file ( )

demonstrate how to load a YAML file from disk to parse with ryml.

ryml offers no overload to directly parse files from disk; it only parses source buffers (which may be mutable or immutable). It is up to the caller to load the file contents into a buffer before parsing with ryml.

But that does not mean that loading a file is unimportant. There are many ways to achieve this in C++, but for convenience and to enable you to quickly get up to speed, here is an example implementation loading a file from disk and then parsing the resulting buffer with ryml.

See also
Parse utilities

Definition at line 1743 of file quickstart.cpp.

1744 {
1745  const char filename[] = "ryml_example.yml";
1746 
1747  // because this is a minimal sample, it assumes nothing on the
1748  // environment/OS (other than that it can read/write files). So we
1749  // create the file on the fly:
1750  file_put_contents(filename, ryml::csubstr("foo: 1\nbar:\n - 2\n - 3\n"));
1751 
1752  // now we can load it into a std::string (for example):
1753  {
1754  std::string contents = file_get_contents<std::string>(filename);
1755  ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(contents)); // immutable (csubstr) overload
1756  CHECK(tree["foo"].val() == "1");
1757  CHECK(tree["bar"][0].val() == "2");
1758  CHECK(tree["bar"][1].val() == "3");
1759  }
1760 
1761  // or we can use a vector<char> instead:
1762  {
1763  std::vector<char> contents = file_get_contents<std::vector<char>>(filename);
1764  ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(contents)); // mutable (csubstr) overload
1765  CHECK(tree["foo"].val() == "1");
1766  CHECK(tree["bar"][0].val() == "2");
1767  CHECK(tree["bar"][1].val() == "3");
1768  }
1769 
1770  // generally, any contiguous char container can be used with ryml,
1771  // provided that the ryml::substr/ryml::csubstr view can be
1772  // created out of it.
1773  //
1774  // ryml provides the overloads above for these two containers, but
1775  // if you are using another container it should be very easy (only
1776  // requires pointer and length).
1777 }
void file_put_contents(const char *filename, const char *buf, size_t sz, const char *access)
save a buffer into a file

References CHECK, sample::file_put_contents(), c4::yml::parse_in_arena(), c4::yml::parse_in_place(), c4::to_csubstr(), and c4::to_substr().

◆ sample_parse_in_place()

void sample::sample_parse_in_place ( )

demonstrate in-place parsing of a mutable YAML source buffer.

See also
Parse utilities

Definition at line 1784 of file quickstart.cpp.

1785 {
1786  // Like the name suggests, parse_in_place() directly mutates the
1787  // source buffer in place
1788  char src[] = "{foo: 1, bar: [2, 3]}"; // ryml can parse in situ
1789  ryml::substr srcview = src; // a mutable view to the source buffer
1790  ryml::Tree tree = ryml::parse_in_place(srcview); // you can also reuse the tree and/or parser
1791  ryml::ConstNodeRef root = tree.crootref(); // get a constant reference to the root
1792 
1793  CHECK(root.is_map());
1794  CHECK(root["foo"].is_keyval());
1795  CHECK(root["foo"].key() == "foo");
1796  CHECK(root["foo"].val() == "1");
1797  CHECK(root["bar"].is_seq());
1798  CHECK(root["bar"].has_key());
1799  CHECK(root["bar"].key() == "bar");
1800  CHECK(root["bar"][0].val() == "2");
1801  CHECK(root["bar"][1].val() == "3");
1802 
1803  // deserializing:
1804  int foo = 0, bar0 = 0, bar1 = 0;
1805  root["foo"] >> foo;
1806  root["bar"][0] >> bar0;
1807  root["bar"][1] >> bar1;
1808  CHECK(foo == 1);
1809  CHECK(bar0 == 2);
1810  CHECK(bar1 == 3);
1811 
1812  // after parsing, the tree holds views to the source buffer:
1813  CHECK(root["foo"].val().data() == src + strlen("{foo: "));
1814  CHECK(root["foo"].val().begin() == src + strlen("{foo: "));
1815  CHECK(root["foo"].val().end() == src + strlen("{foo: 1"));
1816  CHECK(root["foo"].val().is_sub(srcview)); // equivalent to the previous three assertions
1817  CHECK(root["bar"][0].val().data() == src + strlen("{foo: 1, bar: ["));
1818  CHECK(root["bar"][0].val().begin() == src + strlen("{foo: 1, bar: ["));
1819  CHECK(root["bar"][0].val().end() == src + strlen("{foo: 1, bar: [2"));
1820  CHECK(root["bar"][0].val().is_sub(srcview)); // equivalent to the previous three assertions
1821  CHECK(root["bar"][1].val().data() == src + strlen("{foo: 1, bar: [2, "));
1822  CHECK(root["bar"][1].val().begin() == src + strlen("{foo: 1, bar: [2, "));
1823  CHECK(root["bar"][1].val().end() == src + strlen("{foo: 1, bar: [2, 3"));
1824  CHECK(root["bar"][1].val().is_sub(srcview)); // equivalent to the previous three assertions
1825 
1826  // NOTE. parse_in_place() cannot accept ryml::csubstr
1827  // so this will cause a /compile/ error:
1828  ryml::csubstr csrcview = srcview; // ok, can assign from mutable to immutable
1829  //tree = ryml::parse_in_place(csrcview); // compile error, cannot mutate an immutable view
1830  (void)csrcview;
1831 }
ConstNodeRef crootref() const
Get the root as a ConstNodeRef.
Definition: tree.cpp:29

References CHECK, c4::yml::Tree::crootref(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::is_map(), c4::yml::key(), and c4::yml::parse_in_place().

◆ sample_parse_in_arena()

void sample::sample_parse_in_arena ( )

demonstrate parsing of a read-only YAML source buffer

See also
Parse utilities

Definition at line 1838 of file quickstart.cpp.

1839 {
1840  // to parse read-only memory, ryml will copy first to the tree's
1841  // arena, and then parse the copied buffer:
1842  ryml::Tree tree = ryml::parse_in_arena("{foo: 1, bar: [2, 3]}");
1843  ryml::ConstNodeRef root = tree.crootref(); // get a const reference to the root
1844 
1845  CHECK(root.is_map());
1846  CHECK(root["foo"].is_keyval());
1847  CHECK(root["foo"].key() == "foo");
1848  CHECK(root["foo"].val() == "1");
1849  CHECK(root["bar"].is_seq());
1850  CHECK(root["bar"].has_key());
1851  CHECK(root["bar"].key() == "bar");
1852  CHECK(root["bar"][0].val() == "2");
1853  CHECK(root["bar"][1].val() == "3");
1854 
1855  // deserializing:
1856  int foo = 0, bar0 = 0, bar1 = 0;
1857  root["foo"] >> foo;
1858  root["bar"][0] >> bar0;
1859  root["bar"][1] >> bar1;
1860  CHECK(foo == 1);
1861  CHECK(bar0 == 2);
1862  CHECK(bar1 == 3);
1863 
1864  // NOTE. parse_in_arena() cannot accept ryml::substr. Overloads
1865  // receiving substr buffers are declared, but intentionally left
1866  // undefined, so this will cause a /linker/ error
1867  char src[] = "{foo: is it really true}";
1868  ryml::substr srcview = src;
1869  //tree = ryml::parse_in_place(srcview); // linker error, overload intentionally undefined
1870 
1871  // If you really intend to parse a mutable buffer in the arena,
1872  // then simply convert it to immutable prior to calling
1873  // parse_in_arena():
1874  ryml::csubstr csrcview = srcview; // assigning from src also works
1875  tree = ryml::parse_in_arena(csrcview); // OK! csrcview is immutable
1876  CHECK(tree["foo"].val() == "is it really true");
1877 }

References CHECK, c4::yml::Tree::crootref(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::is_map(), c4::yml::key(), and c4::yml::parse_in_arena().

◆ sample_parse_reuse_tree()

void sample::sample_parse_reuse_tree ( )

demonstrate reuse/modification of tree when parsing

See also
Parse utilities

Definition at line 1884 of file quickstart.cpp.

1885 {
1886  ryml::Tree tree;
1887 
1888  // it will always be faster if the tree's size is conveniently reserved:
1889  tree.reserve(30); // reserve 30 nodes (good enough for this sample)
1890  // if you are using the tree's arena to serialize data,
1891  // then reserve also the arena's size:
1892  tree.reserve_arena(256); // reserve 256 characters (good enough for this sample)
1893 
1894  // now parse into the tree:
1895  ryml::csubstr yaml = R"(foo: 1
1896 bar: [2, 3]
1897 )";
1898  ryml::parse_in_arena(yaml, &tree);
1899 
1900  ryml::ConstNodeRef root = tree.crootref();
1901  CHECK(root.num_children() == 2);
1902  CHECK(root.is_map());
1903  CHECK(root["foo"].is_keyval());
1904  CHECK(root["foo"].key() == "foo");
1905  CHECK(root["foo"].val() == "1");
1906  CHECK(root["bar"].is_seq());
1907  CHECK(root["bar"].has_key());
1908  CHECK(root["bar"].key() == "bar");
1909  CHECK(root["bar"][0].val() == "2");
1910  CHECK(root["bar"][1].val() == "3");
1911  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(foo: 1
1912 bar: [2,3]
1913 )");
1914 
1915  // WATCHOUT: parsing into an existing tree will APPEND to it:
1916  ryml::parse_in_arena("{foo2: 12, bar2: [22, 32]}", &tree);
1917  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(foo: 1
1918 bar: [2,3]
1919 foo2: 12
1920 bar2: [22,32]
1921 )");
1922  CHECK(root.num_children() == 4);
1923  CHECK(root["foo2"].is_keyval());
1924  CHECK(root["foo2"].key() == "foo2");
1925  CHECK(root["foo2"].val() == "12");
1926  CHECK(root["bar2"].is_seq());
1927  CHECK(root["bar2"].has_key());
1928  CHECK(root["bar2"].key() == "bar2");
1929  CHECK(root["bar2"][0].val() == "22");
1930  CHECK(root["bar2"][1].val() == "32");
1931 
1932  // clear first before parsing into an existing tree.
1933  tree.clear();
1934  tree.clear_arena(); // you may or may not want to clear the arena
1935  ryml::parse_in_arena("- a\n- b\n- {x0: 1, x1: 2}", &tree);
1936  CHECK(ryml::emitrs_yaml<std::string>(tree) == "- a\n- b\n- {x0: 1,x1: 2}\n");
1937  CHECK(root.is_seq());
1938  CHECK(root[0].val() == "a");
1939  CHECK(root[1].val() == "b");
1940  CHECK(root[2].is_map());
1941  CHECK(root[2]["x0"].val() == "1");
1942  CHECK(root[2]["x1"].val() == "2");
1943 
1944  // we can parse directly into a node nested deep in an existing tree:
1945  ryml::NodeRef mroot = tree.rootref(); // modifiable root
1946  ryml::parse_in_arena("champagne: Dom Perignon\ncoffee: Arabica", mroot.append_child());
1947  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(- a
1948 - b
1949 - {x0: 1,x1: 2}
1950 - champagne: Dom Perignon
1951  coffee: Arabica
1952 )");
1953  CHECK(root.is_seq());
1954  CHECK(root[0].val() == "a");
1955  CHECK(root[1].val() == "b");
1956  CHECK(root[2].is_map());
1957  CHECK(root[2]["x0"].val() == "1");
1958  CHECK(root[2]["x1"].val() == "2");
1959  CHECK(root[3].is_map());
1960  CHECK(root[3]["champagne"].val() == "Dom Perignon");
1961  CHECK(root[3]["coffee"].val() == "Arabica");
1962 
1963  // watchout: to add to an existing node within a map, the node's
1964  // key must be separately set first:
1965  ryml::NodeRef more = mroot[3].append_child({ryml::KEYMAP, "more"});
1966  ryml::NodeRef beer = mroot[3].append_child({ryml::KEYSEQ, "beer"});
1967  ryml::NodeRef always = mroot[3].append_child({ryml::KEY, "always"});
1968  ryml::parse_in_arena("{vinho verde: Soalheiro, vinho tinto: Redoma 2017}", more);
1969  ryml::parse_in_arena("- Rochefort 10\n- Busch\n- Leffe Rituel", beer);
1970  ryml::parse_in_arena("lots\nof\nwater", always);
1971  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(- a
1972 - b
1973 - {x0: 1,x1: 2}
1974 - champagne: Dom Perignon
1975  coffee: Arabica
1976  more:
1977  vinho verde: Soalheiro
1978  vinho tinto: Redoma 2017
1979  beer:
1980  - Rochefort 10
1981  - Busch
1982  - Leffe Rituel
1983  always: lots of water
1984 )");
1985 
1986  // can append at the top:
1987  ryml::parse_in_arena("- foo\n- bar\n- baz\n- bat", mroot);
1988  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(- a
1989 - b
1990 - {x0: 1,x1: 2}
1991 - champagne: Dom Perignon
1992  coffee: Arabica
1993  more:
1994  vinho verde: Soalheiro
1995  vinho tinto: Redoma 2017
1996  beer:
1997  - Rochefort 10
1998  - Busch
1999  - Leffe Rituel
2000  always: lots of water
2001 - foo
2002 - bar
2003 - baz
2004 - bat
2005 )");
2006 
2007  // or nested:
2008  ryml::parse_in_arena("[Kasteel Donker]", beer);
2009  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(- a
2010 - b
2011 - {x0: 1,x1: 2}
2012 - champagne: Dom Perignon
2013  coffee: Arabica
2014  more:
2015  vinho verde: Soalheiro
2016  vinho tinto: Redoma 2017
2017  beer:
2018  - Rochefort 10
2019  - Busch
2020  - Leffe Rituel
2021  - Kasteel Donker
2022  always: lots of water
2023 - foo
2024 - bar
2025 - baz
2026 - bat
2027 )");
2028 }
void reserve_arena(size_t arena_cap)
ensure the tree's internal string arena is at least the given capacity
Definition: tree.hpp:990
void clear()
clear the tree and zero every node
Definition: tree.cpp:285
void clear_arena()
Definition: tree.hpp:270
void reserve(id_type node_capacity)
Definition: tree.cpp:247
@ KEY
is member of a map, must have non-empty key
Definition: node_type.hpp:33

References c4::yml::NodeRef::append_child(), CHECK, c4::yml::Tree::clear(), c4::yml::Tree::clear_arena(), c4::yml::Tree::crootref(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::is_map(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::is_seq(), c4::yml::key(), c4::yml::KEY, c4::yml::KEYMAP, c4::yml::KEYSEQ, c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::num_children(), c4::yml::parse_in_arena(), c4::yml::Tree::reserve(), c4::yml::Tree::reserve_arena(), and c4::yml::Tree::rootref().

◆ sample_parse_reuse_parser()

void sample::sample_parse_reuse_parser ( )

Demonstrates reuse of an existing parser.

Doing this is recommended when multiple files are parsed.

See also
Parse utilities

Definition at line 2036 of file quickstart.cpp.

2037 {
2038  ryml::EventHandlerTree evt_handler = {};
2039  ryml::Parser parser(&evt_handler);
2040 
2041  // it is also advised to reserve the parser depth
2042  // to the expected depth of the data tree:
2043  parser.reserve_stack(10); // uses small storage optimization
2044  // defaulting to 16 depth, so this
2045  // instruction is a no-op, and the stack
2046  // will located in the parser object.
2047  parser.reserve_stack(20); // But this will cause an allocation
2048  // because it is above 16.
2049 
2050  ryml::Tree champagnes = parse_in_arena(&parser, "champagnes.yml", "[Dom Perignon, Gosset Grande Reserve, Jacquesson 742]");
2051  CHECK(ryml::emitrs_yaml<std::string>(champagnes) == "[Dom Perignon,Gosset Grande Reserve,Jacquesson 742]");
2052 
2053  ryml::Tree beers = parse_in_arena(&parser, "beers.yml", "[Rochefort 10, Busch, Leffe Rituel, Kasteel Donker]");
2054  CHECK(ryml::emitrs_yaml<std::string>(beers) == "[Rochefort 10,Busch,Leffe Rituel,Kasteel Donker]");
2055 }

References CHECK, c4::yml::parse_in_arena(), and c4::yml::ParseEngine< EventHandler >::reserve_stack().

◆ sample_parse_reuse_tree_and_parser()

void sample::sample_parse_reuse_tree_and_parser ( )

for ultimate speed when parsing multiple times, reuse both the tree and parser

See also
Parse utilities

Definition at line 2063 of file quickstart.cpp.

2064 {
2065  ryml::Tree tree;
2066  // it will always be faster if the tree's size is conveniently reserved:
2067  tree.reserve(30); // reserve 30 nodes (good enough for this sample)
2068  // if you are using the tree's arena to serialize data,
2069  // then reserve also the arena's size:
2070  tree.reserve(256); // reserve 256 characters (good enough for this sample)
2071 
2072  ryml::EventHandlerTree evt_handler;
2073  ryml::Parser parser(&evt_handler);
2074  // it is also advised to reserve the parser depth
2075  // to the expected depth of the data tree:
2076  parser.reserve_stack(10); // the parser uses small storage
2077  // optimization defaulting to 16 depth,
2078  // so this instruction is a no-op, and
2079  // the stack will be located in the
2080  // parser object.
2081  parser.reserve_stack(20); // But this will cause an allocation
2082  // because it is above 16.
2083 
2084  ryml::csubstr champagnes = "- Dom Perignon\n- Gosset Grande Reserve\n- Jacquesson 742";
2085  ryml::csubstr beers = "- Rochefort 10\n- Busch\n- Leffe Rituel\n- Kasteel Donker";
2086  ryml::csubstr wines = "- Soalheiro\n- Niepoort Redoma 2017\n- Vina Esmeralda";
2087 
2088  parse_in_arena(&parser, "champagnes.yml", champagnes, &tree);
2089  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(- Dom Perignon
2090 - Gosset Grande Reserve
2091 - Jacquesson 742
2092 )");
2093 
2094  // watchout: this will APPEND to the given tree:
2095  parse_in_arena(&parser, "beers.yml", beers, &tree);
2096  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(- Dom Perignon
2097 - Gosset Grande Reserve
2098 - Jacquesson 742
2099 - Rochefort 10
2100 - Busch
2101 - Leffe Rituel
2102 - Kasteel Donker
2103 )");
2104 
2105  // if you don't wish to append, clear the tree first:
2106  tree.clear();
2107  parse_in_arena(&parser, "wines.yml", wines, &tree);
2108  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(- Soalheiro
2109 - Niepoort Redoma 2017
2110 - Vina Esmeralda
2111 )");
2112 }

References CHECK, c4::yml::Tree::clear(), c4::yml::parse_in_arena(), c4::yml::Tree::reserve(), and c4::yml::ParseEngine< EventHandler >::reserve_stack().

◆ sample_iterate_trees()

void sample::sample_iterate_trees ( )

shows how to programatically iterate through trees

See also
Tree utilities
Node classes

Definition at line 2121 of file quickstart.cpp.

2122 {
2123  const ryml::Tree tree = ryml::parse_in_arena(R"(doe: "a deer, a female deer"
2124 ray: "a drop of golden sun"
2125 pi: 3.14159
2126 xmas: true
2127 french-hens: 3
2128 calling-birds:
2129  - huey
2130  - dewey
2131  - louie
2132  - fred
2133 xmas-fifth-day:
2134  calling-birds: four
2135  french-hens: 3
2136  golden-rings: 5
2137  partridges:
2138  count: 1
2139  location: a pear tree
2140  turtle-doves: two
2141 cars: GTO
2142 )");
2143  ryml::ConstNodeRef root = tree.crootref();
2144 
2145  // iterate children
2146  {
2147  std::vector<ryml::csubstr> keys, vals; // to store all the root-level keys, vals
2148  for(ryml::ConstNodeRef n : root.children())
2149  {
2150  keys.emplace_back(n.key());
2151  vals.emplace_back(n.has_val() ? n.val() : ryml::csubstr{});
2152  }
2153  CHECK(keys[0] == "doe");
2154  CHECK(vals[0] == "a deer, a female deer");
2155  CHECK(keys[1] == "ray");
2156  CHECK(vals[1] == "a drop of golden sun");
2157  CHECK(keys[2] == "pi");
2158  CHECK(vals[2] == "3.14159");
2159  CHECK(keys[3] == "xmas");
2160  CHECK(vals[3] == "true");
2161  CHECK(root[5].has_key());
2162  CHECK(root[5].is_seq());
2163  CHECK(root[5].key() == "calling-birds");
2164  CHECK(!root[5].has_val()); // it is a map, so not a val
2165  //CHECK(root[5].val() == ""); // ERROR! node does not have a val.
2166  CHECK(keys[5] == "calling-birds");
2167  CHECK(vals[5] == "");
2168  }
2169 
2170  // iterate siblings
2171  {
2172  size_t count = 0;
2173  ryml::csubstr calling_birds[] = {"huey", "dewey", "louie", "fred"};
2174  for(ryml::ConstNodeRef n : root["calling-birds"][2].siblings())
2175  CHECK(n.val() == calling_birds[count++]);
2176  CHECK(count == 4u);
2177  }
2178 }

References CHECK, c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::children(), c4::yml::Tree::crootref(), c4::yml::key(), and c4::yml::parse_in_arena().

◆ sample_create_trees()

void sample::sample_create_trees ( )

shows how to programatically create trees

See also
Tree utilities
Node classes

Definition at line 2187 of file quickstart.cpp.

2188 {
2189  ryml::NodeRef doe;
2190  CHECK(doe.invalid()); // it's pointing at nowhere
2191 
2192  ryml::Tree tree;
2193  ryml::NodeRef root = tree.rootref();
2194  root |= ryml::MAP; // mark root as a map
2195  doe = root["doe"];
2196  CHECK(!doe.invalid()); // it's now pointing at the tree
2197  CHECK(doe.is_seed()); // but the tree has nothing there, so this is only a seed
2198 
2199  // set the value of the node
2200  const char a_deer[] = "a deer, a female deer";
2201  doe = a_deer;
2202  // now the node really exists in the tree, and this ref is no
2203  // longer a seed:
2204  CHECK(!doe.is_seed());
2205  // WATCHOUT for lifetimes:
2206  CHECK(doe.val().str == a_deer); // it is pointing at the initial string
2207  // If you need to avoid lifetime dependency, serialize the data:
2208  {
2209  std::string a_drop = "a drop of golden sun";
2210  // this will copy the string to the tree's arena:
2211  // (see the serialization samples below)
2212  root["ray"] << a_drop;
2213  // and now you can modify the original string without changing
2214  // the tree:
2215  a_drop[0] = 'Z';
2216  a_drop[1] = 'Z';
2217  }
2218  CHECK(root["ray"].val() == "a drop of golden sun");
2219 
2220  // etc.
2221  root["pi"] << ryml::fmt::real(3.141592654, 5);
2222  root["xmas"] << ryml::fmt::boolalpha(true);
2223  root["french-hens"] << 3;
2224  ryml::NodeRef calling_birds = root["calling-birds"];
2225  calling_birds |= ryml::SEQ;
2226  calling_birds.append_child() = "huey";
2227  calling_birds.append_child() = "dewey";
2228  calling_birds.append_child() = "louie";
2229  calling_birds.append_child() = "fred";
2230  ryml::NodeRef xmas5 = root["xmas-fifth-day"];
2231  xmas5 |= ryml::MAP;
2232  xmas5["calling-birds"] = "four";
2233  xmas5["french-hens"] << 3;
2234  xmas5["golden-rings"] << 5;
2235  xmas5["partridges"] |= ryml::MAP;
2236  xmas5["partridges"]["count"] << 1;
2237  xmas5["partridges"]["location"] = "a pear tree";
2238  xmas5["turtle-doves"] = "two";
2239  root["cars"] = "GTO";
2240 
2241  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(doe: 'a deer, a female deer'
2242 ray: a drop of golden sun
2243 pi: 3.14159
2244 xmas: true
2245 french-hens: 3
2246 calling-birds:
2247  - huey
2248  - dewey
2249  - louie
2250  - fred
2251 xmas-fifth-day:
2252  calling-birds: four
2253  french-hens: 3
2254  golden-rings: 5
2255  partridges:
2256  count: 1
2257  location: a pear tree
2258  turtle-doves: two
2259 cars: GTO
2260 )");
2261 }
boolalpha_< T > boolalpha(T const &val, bool strict_read=false)
Definition: format.hpp:72

References c4::yml::NodeRef::append_child(), c4::fmt::boolalpha(), CHECK, c4::yml::NodeRef::invalid(), c4::yml::NodeRef::is_seed(), c4::yml::MAP, c4::fmt::real(), c4::yml::Tree::rootref(), c4::yml::SEQ, and c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::val().

◆ sample_tree_arena()

void sample::sample_tree_arena ( )

demonstrates explicit and implicit interaction with the tree's string arena.

Notice that ryml only holds strings in the tree's nodes.

Definition at line 2268 of file quickstart.cpp.

2269 {
2270  // mutable buffers are parsed in situ:
2271  {
2272  char buf[] = "[a, b, c, d]";
2273  ryml::substr yml = buf;
2274  ryml::Tree tree = ryml::parse_in_place(yml);
2275  // notice the arena is empty:
2276  CHECK(tree.arena().empty());
2277  // and the tree is pointing at the original buffer:
2278  ryml::NodeRef root = tree.rootref();
2279  CHECK(root[0].val().is_sub(yml));
2280  CHECK(root[1].val().is_sub(yml));
2281  CHECK(root[2].val().is_sub(yml));
2282  CHECK(root[3].val().is_sub(yml));
2283  CHECK(yml.is_super(root[0].val()));
2284  CHECK(yml.is_super(root[1].val()));
2285  CHECK(yml.is_super(root[2].val()));
2286  CHECK(yml.is_super(root[3].val()));
2287  }
2288 
2289  // when parsing immutable buffers, the buffer is first copied to the
2290  // tree's arena; the copy in the arena is then the buffer which is
2291  // actually parsed
2292  {
2293  ryml::csubstr yml = "[a, b, c, d]";
2294  ryml::Tree tree = ryml::parse_in_arena(yml);
2295  // notice the buffer was copied to the arena:
2296  CHECK(tree.arena().data() != yml.data());
2297  CHECK(tree.arena() == yml);
2298  // and the tree is pointing at the arena instead of to the
2299  // original buffer:
2300  ryml::NodeRef root = tree.rootref();
2301  ryml::csubstr arena = tree.arena();
2302  CHECK(root[0].val().is_sub(arena));
2303  CHECK(root[1].val().is_sub(arena));
2304  CHECK(root[2].val().is_sub(arena));
2305  CHECK(root[3].val().is_sub(arena));
2306  CHECK(arena.is_super(root[0].val()));
2307  CHECK(arena.is_super(root[1].val()));
2308  CHECK(arena.is_super(root[2].val()));
2309  CHECK(arena.is_super(root[3].val()));
2310  }
2311 
2312  // the arena is also used when the data is serialized to string
2313  // with NodeRef::operator<<(): mutable buffer
2314  {
2315  char buf[] = "[a, b, c, d]"; // mutable
2316  ryml::substr yml = buf;
2317  ryml::Tree tree = ryml::parse_in_place(yml);
2318  // notice the arena is empty:
2319  CHECK(tree.arena().empty());
2320  ryml::NodeRef root = tree.rootref();
2321 
2322  // serialize an integer, and mutate the tree
2323  CHECK(root[2].val() == "c");
2324  CHECK(root[2].val().is_sub(yml)); // val is first pointing at the buffer
2325  root[2] << 12345;
2326  CHECK(root[2].val() == "12345");
2327  CHECK(root[2].val().is_sub(tree.arena())); // now val is pointing at the arena
2328  // notice the serialized string was appended to the tree's arena:
2329  CHECK(tree.arena() == "12345");
2330 
2331  // serialize an integer, and mutate the tree
2332  CHECK(root[3].val() == "d");
2333  CHECK(root[3].val().is_sub(yml)); // val is first pointing at the buffer
2334  root[3] << 67890;
2335  CHECK(root[3].val() == "67890");
2336  CHECK(root[3].val().is_sub(tree.arena())); // now val is pointing at the arena
2337  // notice the serialized string was appended to the tree's arena:
2338  CHECK(tree.arena() == "1234567890");
2339  }
2340  // the arena is also used when the data is serialized to string
2341  // with NodeRef::operator<<(): immutable buffer
2342  {
2343  ryml::csubstr yml = "[a, b, c, d]"; // immutable
2344  ryml::Tree tree = ryml::parse_in_arena(yml);
2345  // notice the buffer was copied to the arena:
2346  CHECK(tree.arena().data() != yml.data());
2347  CHECK(tree.arena() == yml);
2348  ryml::NodeRef root = tree.rootref();
2349 
2350  // serialize an integer, and mutate the tree
2351  CHECK(root[2].val() == "c");
2352  root[2] << 12345; // serialize an integer
2353  CHECK(root[2].val() == "12345");
2354  // notice the serialized string was appended to the tree's arena:
2355  // notice also the previous values remain there.
2356  // RYML DOES NOT KEEP TRACK OF REFERENCES TO THE ARENA.
2357  CHECK(tree.arena() == "[a, b, c, d]12345");
2358  // old values: --------------^
2359 
2360  // serialize an integer, and mutate the tree
2361  root[3] << 67890;
2362  CHECK(root[3].val() == "67890");
2363  // notice the serialized string was appended to the tree's arena:
2364  // notice also the previous values remain there.
2365  // RYML DOES NOT KEEP TRACK OF REFERENCES TO THE ARENA.
2366  CHECK(tree.arena() == "[a, b, c, d]1234567890");
2367  // old values: --------------^ ---^^^^^
2368  }
2369 
2370  // to_arena(): directly serialize values to the arena:
2371  {
2372  ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2373  ryml::csubstr c10 = tree.to_arena(10101010);
2374  CHECK(c10 == "10101010");
2375  CHECK(c10.is_sub(tree.arena()));
2376  CHECK(tree.arena() == "{a: b}10101010");
2377  CHECK(tree.key(1) == "a");
2378  CHECK(tree.val(1) == "b");
2379  tree.set_val(1, c10);
2380  CHECK(tree.val(1) == c10);
2381  // and you can also do it through a node:
2382  ryml::NodeRef root = tree.rootref();
2383  root["a"].set_val_serialized(2222);
2384  CHECK(root["a"].val() == "2222");
2385  CHECK(tree.arena() == "{a: b}101010102222");
2386  }
2387 
2388  // copy_to_arena(): manually copy a string to the arena:
2389  {
2390  ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2391  ryml::csubstr mystr = "Gosset Grande Reserve";
2392  ryml::csubstr copied = tree.copy_to_arena(mystr);
2393  CHECK(!copied.overlaps(mystr));
2394  CHECK(copied == mystr);
2395  CHECK(tree.arena() == "{a: b}Gosset Grande Reserve");
2396  }
2397 
2398  // alloc_arena(): allocate a buffer from the arena:
2399  {
2400  ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2401  ryml::csubstr mystr = "Gosset Grande Reserve";
2402  ryml::substr copied = tree.alloc_arena(mystr.size());
2403  CHECK(!copied.overlaps(mystr));
2404  memcpy(copied.str, mystr.str, mystr.len);
2405  CHECK(copied == mystr);
2406  CHECK(tree.arena() == "{a: b}Gosset Grande Reserve");
2407  }
2408 
2409  // reserve_arena(): ensure the arena has a certain size to avoid reallocations
2410  {
2411  ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2412  CHECK(tree.arena().size() == strlen("{a: b}"));
2413  tree.reserve_arena(100);
2414  CHECK(tree.arena_capacity() >= 100);
2415  CHECK(tree.arena().size() == strlen("{a: b}"));
2416  tree.to_arena(123456);
2417  CHECK(tree.arena().first(12) == "{a: b}123456");
2418  }
2419 }
size_t set_val_serialized(T const &v)
Definition: node.hpp:1234
substr alloc_arena(size_t sz)
grow the tree's string arena by the given size and return a substr of the added portion
Definition: tree.hpp:978
size_t arena_capacity() const
get the current capacity of the tree's internal arena
Definition: tree.hpp:823
void set_val(id_type node, csubstr val)
Definition: tree.hpp:570
csubstr const & val(id_type node) const
Definition: tree.hpp:383
std::enable_if< std::is_floating_point< T >::value, csubstr >::type to_arena(T const &a)
serialize the given floating-point variable to the tree's arena, growing it as needed to accomodate t...
Definition: tree.hpp:850
substr copy_to_arena(csubstr s)
copy the given substr to the tree's arena, growing it by the required size
Definition: tree.hpp:950

References c4::yml::Tree::alloc_arena(), c4::yml::Tree::arena(), c4::yml::Tree::arena_capacity(), CHECK, c4::yml::Tree::copy_to_arena(), c4::yml::Tree::key(), c4::yml::parse_in_arena(), c4::yml::parse_in_place(), c4::yml::Tree::reserve_arena(), c4::yml::Tree::rootref(), c4::yml::Tree::set_val(), c4::yml::NodeRef::set_val_serialized(), c4::yml::Tree::to_arena(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::val(), and c4::yml::Tree::val().

◆ sample_fundamental_types()

void sample::sample_fundamental_types ( )

ryml provides facilities for serializing and deserializing the C++ fundamental types, including boolean and null values; this is provided by the several overloads in to_chars: generalized chars to value and from_chars: generalized chars to value.

To add serialization for user scalar types (ie, those types that should be serialized as strings in leaf nodes), you just need to define the appropriate overloads of to_chars and from_chars for those types; see sample_user_scalar_types for an example on how to achieve this, and see Serialization/deserialization for more information on serialization.

Definition at line 2433 of file quickstart.cpp.

2434 {
2435  ryml::Tree tree;
2436  CHECK(tree.arena().empty());
2437  CHECK(tree.to_arena('a') == "a"); CHECK(tree.arena() == "a");
2438  CHECK(tree.to_arena("bcde") == "bcde"); CHECK(tree.arena() == "abcde");
2439  CHECK(tree.to_arena(unsigned(0)) == "0"); CHECK(tree.arena() == "abcde0");
2440  CHECK(tree.to_arena(int(1)) == "1"); CHECK(tree.arena() == "abcde01");
2441  CHECK(tree.to_arena(uint8_t(0)) == "0"); CHECK(tree.arena() == "abcde010");
2442  CHECK(tree.to_arena(uint16_t(1)) == "1"); CHECK(tree.arena() == "abcde0101");
2443  CHECK(tree.to_arena(uint32_t(2)) == "2"); CHECK(tree.arena() == "abcde01012");
2444  CHECK(tree.to_arena(uint64_t(3)) == "3"); CHECK(tree.arena() == "abcde010123");
2445  CHECK(tree.to_arena(int8_t( 4)) == "4"); CHECK(tree.arena() == "abcde0101234");
2446  CHECK(tree.to_arena(int8_t(-4)) == "-4"); CHECK(tree.arena() == "abcde0101234-4");
2447  CHECK(tree.to_arena(int16_t( 5)) == "5"); CHECK(tree.arena() == "abcde0101234-45");
2448  CHECK(tree.to_arena(int16_t(-5)) == "-5"); CHECK(tree.arena() == "abcde0101234-45-5");
2449  CHECK(tree.to_arena(int32_t( 6)) == "6"); CHECK(tree.arena() == "abcde0101234-45-56");
2450  CHECK(tree.to_arena(int32_t(-6)) == "-6"); CHECK(tree.arena() == "abcde0101234-45-56-6");
2451  CHECK(tree.to_arena(int64_t( 7)) == "7"); CHECK(tree.arena() == "abcde0101234-45-56-67");
2452  CHECK(tree.to_arena(int64_t(-7)) == "-7"); CHECK(tree.arena() == "abcde0101234-45-56-67-7");
2453  CHECK(tree.to_arena((void*)1) == "0x1"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x1");
2454  CHECK(tree.to_arena(float(0.124)) == "0.124"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.124");
2455  CHECK(tree.to_arena(double(0.234)) == "0.234"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.234");
2456 
2457  // write boolean values - see also sample_formatting()
2458  CHECK(tree.to_arena(bool(true)) == "1"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.2341");
2459  CHECK(tree.to_arena(bool(false)) == "0"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410");
2460  CHECK(tree.to_arena(c4::fmt::boolalpha(true)) == "true"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410true");
2461  CHECK(tree.to_arena(c4::fmt::boolalpha(false)) == "false"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse");
2462 
2463  // write special float values
2464  // see also sample_float_precision()
2465  const float fnan = std::numeric_limits<float >::quiet_NaN();
2466  const double dnan = std::numeric_limits<double>::quiet_NaN();
2467  const float finf = std::numeric_limits<float >::infinity();
2468  const double dinf = std::numeric_limits<double>::infinity();
2469  CHECK(tree.to_arena( finf) == ".inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf");
2470  CHECK(tree.to_arena( dinf) == ".inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf");
2471  CHECK(tree.to_arena(-finf) == "-.inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf");
2472  CHECK(tree.to_arena(-dinf) == "-.inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf");
2473  CHECK(tree.to_arena( fnan) == ".nan"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf.nan");
2474  CHECK(tree.to_arena( dnan) == ".nan"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf.nan.nan");
2475 
2476  // read special float values
2477  // see also sample_float_precision()
2478  C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wfloat-equal");
2479  tree = ryml::parse_in_arena(R"({ninf: -.inf, pinf: .inf, nan: .nan})");
2480  float f = 0.f;
2481  double d = 0.;
2482  CHECK(f == 0.f);
2483  CHECK(d == 0.);
2484  tree["ninf"] >> f; CHECK(f == -finf);
2485  tree["ninf"] >> d; CHECK(d == -dinf);
2486  tree["pinf"] >> f; CHECK(f == finf);
2487  tree["pinf"] >> d; CHECK(d == dinf);
2488  tree["nan" ] >> f; CHECK(std::isnan(f));
2489  tree["nan" ] >> d; CHECK(std::isnan(d));
2490  C4_SUPPRESS_WARNING_GCC_CLANG_POP
2491 }

References c4::yml::Tree::arena(), c4::fmt::boolalpha(), CHECK, c4::yml::parse_in_arena(), and c4::yml::Tree::to_arena().

◆ sample_empty_null_values()

void sample::sample_empty_null_values ( )

Shows how to deal with empty/null values.

See also c4::yml::Tree::val_is_null

Definition at line 2498 of file quickstart.cpp.

2499 {
2500  // reading empty/null values - see also sample_formatting()
2501  ryml::Tree tree = ryml::parse_in_arena(R"(
2502 plain:
2503 squoted: ''
2504 dquoted: ""
2505 literal: |
2506 folded: >
2507 all_null: [~, null, Null, NULL]
2508 non_null: [nULL, non_null, non null, null it is not]
2509 )");
2510  // first, remember that .has_val() is a structural predicate
2511  // indicating the node is a leaf, and not a container.
2512  CHECK(tree["plain"].has_val()); // has a val, even if it's empty!
2513  CHECK(tree["squoted"].has_val());
2514  CHECK(tree["dquoted"].has_val());
2515  CHECK(tree["literal"].has_val());
2516  CHECK(tree["folded"].has_val());
2517  CHECK( ! tree["all_null"].has_val());
2518  CHECK( ! tree["non_null"].has_val());
2519  // In essence, has_val() is the logical opposite of is_container()
2520  CHECK( ! tree["plain"].is_container());
2521  CHECK( ! tree["squoted"].is_container());
2522  CHECK( ! tree["dquoted"].is_container());
2523  CHECK( ! tree["literal"].is_container());
2524  CHECK( ! tree["folded"].is_container());
2525  CHECK(tree["all_null"].is_container());
2526  CHECK(tree["non_null"].is_container());
2527  //
2528  // Right. How about the contents of each val?
2529  //
2530  // all of these scalars have zero-length:
2531  CHECK(tree["plain"].val().len == 0);
2532  CHECK(tree["squoted"].val().len == 0);
2533  CHECK(tree["dquoted"].val().len == 0);
2534  CHECK(tree["literal"].val().len == 0);
2535  CHECK(tree["folded"].val().len == 0);
2536  // but only the empty scalar has null string:
2537  CHECK(tree["plain"].val().str == nullptr);
2538  CHECK(tree["squoted"].val().str != nullptr);
2539  CHECK(tree["dquoted"].val().str != nullptr);
2540  CHECK(tree["literal"].val().str != nullptr);
2541  CHECK(tree["folded"].val().str != nullptr);
2542  // likewise, scalar comparison to nullptr has the same results:
2543  // (remember that .val() gives you the scalar value, node must
2544  // have a val, ie must be a leaf node, not a container)
2545  CHECK(tree["plain"].val() == nullptr);
2546  CHECK(tree["squoted"].val() != nullptr);
2547  CHECK(tree["dquoted"].val() != nullptr);
2548  CHECK(tree["literal"].val() != nullptr);
2549  CHECK(tree["folded"].val() != nullptr);
2550  // the tree and node classes provide the corresponding predicate
2551  // functions .key_is_null() and .val_is_null().
2552  // (note that these functions have the same preconditions as .val(),
2553  // because they need get the val to look into its contents)
2554  CHECK(tree["plain"].val_is_null());
2555  CHECK( ! tree["squoted"].val_is_null());
2556  CHECK( ! tree["dquoted"].val_is_null());
2557  CHECK( ! tree["literal"].val_is_null());
2558  CHECK( ! tree["folded"].val_is_null());
2559  // matching to null is case-sensitive. only the cases shown here
2560  // match to null:
2561  for(ryml::ConstNodeRef child : tree["all_null"].children())
2562  {
2563  CHECK(child.val() != nullptr); // it is pointing at a string, so it is not nullptr!
2564  CHECK(child.val_is_null());
2565  }
2566  for(ryml::ConstNodeRef child : tree["non_null"].children())
2567  {
2568  CHECK(child.val() != nullptr);
2569  CHECK( ! child.val_is_null());
2570  }
2571  //
2572  //
2573  // Because the meaning of null/~/empty will vary from application
2574  // to application, ryml makes no assumption on what should be
2575  // serialized as null. It leaves this decision to the user. But
2576  // it also provides the proper toolbox for the user to implement
2577  // its intended solution.
2578  //
2579  // writing/disambiguating null values:
2580  ryml::csubstr null = {};
2581  ryml::csubstr nonnull = "";
2582  ryml::csubstr strnull = "null";
2583  ryml::csubstr tilde = "~";
2584  CHECK(null .len == 0); CHECK(null .str == nullptr); CHECK(null == nullptr);
2585  CHECK(nonnull.len == 0); CHECK(nonnull.str != nullptr); CHECK(nonnull != nullptr);
2586  CHECK(strnull.len != 0); CHECK(strnull.str != nullptr); CHECK(strnull != nullptr);
2587  CHECK(tilde .len != 0); CHECK(tilde .str != nullptr); CHECK(tilde != nullptr);
2588  tree.clear();
2589  tree.clear_arena();
2590  tree.rootref() |= ryml::MAP;
2591  // serializes as an empty plain scalar:
2592  tree["empty_null"] << null; CHECK(tree.arena() == "");
2593  // serializes as an empty quoted scalar:
2594  tree["empty_nonnull"] << nonnull; CHECK(tree.arena() == "");
2595  // serializes as the normal 'null' string:
2596  tree["str_null"] << strnull; CHECK(tree.arena() == "null");
2597  // serializes as the normal '~' string:
2598  tree["str_tilde"] << tilde; CHECK(tree.arena() == "null~");
2599  // this is the resulting yaml:
2600  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(empty_null:
2601 empty_nonnull: ''
2602 str_null: null
2603 str_tilde: ~
2604 )");
2605  // To enforce a particular concept of what is a null string, you
2606  // can use the appropriate condition based on pointer nulity or
2607  // other appropriate criteria.
2608  //
2609  // As an example, proper comparison to nullptr:
2610  auto null_if_nullptr = [](ryml::csubstr s) {
2611  return s.str == nullptr ? "null" : s;
2612  };
2613  tree["empty_null"] << null_if_nullptr(null);
2614  tree["empty_nonnull"] << null_if_nullptr(nonnull);
2615  tree["str_null"] << null_if_nullptr(strnull);
2616  tree["str_tilde"] << null_if_nullptr(tilde);
2617  // this is the resulting yaml:
2618  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(empty_null: null
2619 empty_nonnull: ''
2620 str_null: null
2621 str_tilde: ~
2622 )");
2623  //
2624  // As another example, nulity check based on the YAML nulity
2625  // predicate:
2626  auto null_if_predicate = [](ryml::csubstr s) {
2627  return ryml::scalar_is_null(s) ? "null" : s;
2628  };
2629  tree["empty_null"] << null_if_predicate(null);
2630  tree["empty_nonnull"] << null_if_predicate(nonnull);
2631  tree["str_null"] << null_if_predicate(strnull);
2632  tree["str_tilde"] << null_if_predicate(tilde);
2633  // this is the resulting yaml:
2634  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(empty_null: null
2635 empty_nonnull: ''
2636 str_null: null
2637 str_tilde: null
2638 )");
2639  //
2640  // As another example, nulity check based on the YAML nulity
2641  // predicate, but returning "~" to simbolize nulity:
2642  auto tilde_if_predicate = [](ryml::csubstr s) {
2643  return ryml::scalar_is_null(s) ? "~" : s;
2644  };
2645  tree["empty_null"] << tilde_if_predicate(null);
2646  tree["empty_nonnull"] << tilde_if_predicate(nonnull);
2647  tree["str_null"] << tilde_if_predicate(strnull);
2648  tree["str_tilde"] << tilde_if_predicate(tilde);
2649  // this is the resulting yaml:
2650  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(empty_null: ~
2651 empty_nonnull: ''
2652 str_null: ~
2653 str_tilde: ~
2654 )");
2655 }
bool scalar_is_null(csubstr s) noexcept
YAML-sense query of nullity.
Definition: node_type.hpp:251

References c4::yml::Tree::arena(), CHECK, c4::yml::Tree::clear(), c4::yml::Tree::clear_arena(), c4::yml::MAP, c4::yml::parse_in_arena(), c4::yml::Tree::rootref(), and c4::yml::scalar_is_null().

◆ sample_formatting()

void sample::sample_formatting ( )

ryml provides facilities for formatting/deformatting (imported from c4core into the ryml namespace).

See Format utilities . These functions are very useful to serialize and deserialize scalar types; see Serialization/deserialization .

Definition at line 2665 of file quickstart.cpp.

2666 {
2667  // format(), format_sub(), formatrs(): format arguments
2668  {
2669  char buf_[256] = {};
2670  ryml::substr buf = buf_;
2671  size_t size = ryml::format(buf, "a={} foo {} {} bar {}", 0.1, 10, 11, 12);
2672  CHECK(size == strlen("a=0.1 foo 10 11 bar 12"));
2673  CHECK(buf.first(size) == "a=0.1 foo 10 11 bar 12");
2674  // it is safe to call on an empty buffer:
2675  // returns the size needed for the result, and no overflow occurs:
2676  size = ryml::format({} , "a={} foo {} {} bar {}", "this_is_a", 10, 11, 12);
2677  CHECK(size == ryml::format(buf, "a={} foo {} {} bar {}", "this_is_a", 10, 11, 12));
2678  CHECK(size == strlen("a=this_is_a foo 10 11 bar 12"));
2679  // it is also safe to call on an insufficient buffer:
2680  char smallbuf[8] = {};
2681  size = ryml::format(smallbuf, "{} is too large {}", "this", "for the buffer");
2682  CHECK(size == strlen("this is too large for the buffer"));
2683  // ... and the result is truncated at the buffer size:
2684  CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "this is\0");
2685 
2686  // format_sub() directly returns the written string:
2687  ryml::csubstr result = ryml::format_sub(buf, "b={}, damn it.", 1);
2688  CHECK(result == "b=1, damn it.");
2689  CHECK(result.is_sub(buf));
2690 
2691  // formatrs() means FORMAT & ReSize:
2692  //
2693  // Instead of a substr, it receives any owning linear char container
2694  // for which to_substr() is defined (using ADL).
2695  // <ryml_std.hpp> has to_substr() definitions for std::string and
2696  // std::vector<char>.
2697  //
2698  // formatrs() starts by calling format(), and if needed, resizes the container
2699  // and calls format() again.
2700  //
2701  // Note that unless the container is previously sized, this
2702  // may cause an allocation, which will make your code slower.
2703  // Make sure to call .reserve() on the container for real
2704  // production code.
2705  std::string sbuf;
2706  ryml::formatrs(&sbuf, "and c={} seems about right", 2);
2707  CHECK(sbuf == "and c=2 seems about right");
2708  std::vector<char> vbuf; // works with any linear char container
2709  ryml::formatrs(&vbuf, "and c={} seems about right", 2);
2710  CHECK(sbuf == "and c=2 seems about right");
2711  // with formatrs() it is also possible to append:
2712  ryml::formatrs_append(&sbuf, ", and finally d={} - done", 3);
2713  CHECK(sbuf == "and c=2 seems about right, and finally d=3 - done");
2714  }
2715 
2716  // unformat(): read arguments - opposite of format()
2717  {
2718  char buf_[256];
2719 
2720  int a = 0, b = 1, c = 2;
2721  ryml::csubstr result = ryml::format_sub(buf_, "{} and {} and {}", a, b, c);
2722  CHECK(result == "0 and 1 and 2");
2723  int aa = -1, bb = -2, cc = -3;
2724  size_t num_characters = ryml::unformat(result, "{} and {} and {}", aa, bb, cc);
2725  CHECK(num_characters != ryml::csubstr::npos); // if a conversion fails, returns ryml::csubstr::npos
2726  CHECK(num_characters == result.size());
2727  CHECK(aa == a);
2728  CHECK(bb == b);
2729  CHECK(cc == c);
2730 
2731  result = ryml::format_sub(buf_, "{} and {} and {}", 10, 20, 30);
2732  CHECK(result == "10 and 20 and 30");
2733  num_characters = ryml::unformat(result, "{} and {} and {}", aa, bb, cc);
2734  CHECK(num_characters != ryml::csubstr::npos); // if a conversion fails, returns ryml::csubstr::npos
2735  CHECK(num_characters == result.size());
2736  CHECK(aa == 10);
2737  CHECK(bb == 20);
2738  CHECK(cc == 30);
2739  }
2740 
2741  // cat(), cat_sub(), catrs(): concatenate arguments
2742  {
2743  char buf_[256] = {};
2744  ryml::substr buf = buf_;
2745  size_t size = ryml::cat(buf, "a=", 0.1, "foo", 10, 11, "bar", 12);
2746  CHECK(size == strlen("a=0.1foo1011bar12"));
2747  CHECK(buf.first(size) == "a=0.1foo1011bar12");
2748  // it is safe to call on an empty buffer:
2749  // returns the size needed for the result, and no overflow occurs:
2750  CHECK(ryml::cat({}, "a=", 0) == 3);
2751  // it is also safe to call on an insufficient buffer:
2752  char smallbuf[8] = {};
2753  size = ryml::cat(smallbuf, "this", " is too large ", "for the buffer");
2754  CHECK(size == strlen("this is too large for the buffer"));
2755  // ... and the result is truncated at the buffer size:
2756  CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "this is\0");
2757 
2758  // cat_sub() directly returns the written string:
2759  ryml::csubstr result = ryml::cat_sub(buf, "b=", 1, ", damn it.");
2760  CHECK(result == "b=1, damn it.");
2761  CHECK(result.is_sub(buf));
2762 
2763  // catrs() means CAT & ReSize:
2764  //
2765  // Instead of a substr, it receives any owning linear char container
2766  // for which to_substr() is defined (using ADL).
2767  // <ryml_std.hpp> has to_substr() definitions for std::string and
2768  // std::vector<char>.
2769  //
2770  // catrs() starts by calling cat(), and if needed, resizes the container
2771  // and calls cat() again.
2772  //
2773  // Note that unless the container is previously sized, this
2774  // may cause an allocation, which will make your code slower.
2775  // Make sure to call .reserve() on the container for real
2776  // production code.
2777  std::string sbuf;
2778  ryml::catrs(&sbuf, "and c=", 2, " seems about right");
2779  CHECK(sbuf == "and c=2 seems about right");
2780  std::vector<char> vbuf; // works with any linear char container
2781  ryml::catrs(&vbuf, "and c=", 2, " seems about right");
2782  CHECK(sbuf == "and c=2 seems about right");
2783  // with catrs() it is also possible to append:
2784  ryml::catrs_append(&sbuf, ", and finally d=", 3, " - done");
2785  CHECK(sbuf == "and c=2 seems about right, and finally d=3 - done");
2786  }
2787 
2788  // uncat(): read arguments - opposite of cat()
2789  {
2790  char buf_[256];
2791 
2792  int a = 0, b = 1, c = 2;
2793  ryml::csubstr result = ryml::cat_sub(buf_, a, ' ', b, ' ', c);
2794  CHECK(result == "0 1 2");
2795  int aa = -1, bb = -2, cc = -3;
2796  char sep1 = 'a', sep2 = 'b';
2797  size_t num_characters = ryml::uncat(result, aa, sep1, bb, sep2, cc);
2798  CHECK(num_characters == result.size());
2799  CHECK(aa == a);
2800  CHECK(bb == b);
2801  CHECK(cc == c);
2802  CHECK(sep1 == ' ');
2803  CHECK(sep2 == ' ');
2804 
2805  result = ryml::cat_sub(buf_, 10, ' ', 20, ' ', 30);
2806  CHECK(result == "10 20 30");
2807  num_characters = ryml::uncat(result, aa, sep1, bb, sep2, cc);
2808  CHECK(num_characters == result.size());
2809  CHECK(aa == 10);
2810  CHECK(bb == 20);
2811  CHECK(cc == 30);
2812  CHECK(sep1 == ' ');
2813  CHECK(sep2 == ' ');
2814  }
2815 
2816  // catsep(), catsep_sub(), catseprs(): concatenate arguments, with a separator
2817  {
2818  char buf_[256] = {};
2819  ryml::substr buf = buf_;
2820  // use ' ' as a separator
2821  size_t size = ryml::catsep(buf, ' ', "a=", 0, "b=", 1, "c=", 2, 45, 67);
2822  CHECK(buf.first(size) == "a= 0 b= 1 c= 2 45 67");
2823  // any separator may be used
2824  // use " and " as a separator
2825  size = ryml::catsep(buf, " and ", "a=0", "b=1", "c=2", 45, 67);
2826  CHECK(buf.first(size) == "a=0 and b=1 and c=2 and 45 and 67");
2827  // use " ... " as a separator
2828  size = ryml::catsep(buf, " ... ", "a=0", "b=1", "c=2", 45, 67);
2829  CHECK(buf.first(size) == "a=0 ... b=1 ... c=2 ... 45 ... 67");
2830  // use '/' as a separator
2831  size = ryml::catsep(buf, '/', "a=", 0, "b=", 1, "c=", 2, 45, 67);
2832  CHECK(buf.first(size) == "a=/0/b=/1/c=/2/45/67");
2833  // use 888 as a separator
2834  size = ryml::catsep(buf, 888, "a=0", "b=1", "c=2", 45, 67);
2835  CHECK(buf.first(size) == "a=0888b=1888c=28884588867");
2836 
2837  // it is safe to call on an empty buffer:
2838  // returns the size needed for the result, and no overflow occurs:
2839  CHECK(size == ryml::catsep({}, 888, "a=0", "b=1", "c=2", 45, 67));
2840  // it is also safe to call on an insufficient buffer:
2841  char smallbuf[8] = {};
2842  CHECK(size == ryml::catsep(smallbuf, 888, "a=0", "b=1", "c=2", 45, 67));
2843  CHECK(size == strlen("a=0888b=1888c=28884588867"));
2844  // ... and the result is truncated:
2845  CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "a=0888b\0");
2846 
2847  // catsep_sub() directly returns the written substr:
2848  ryml::csubstr result = ryml::catsep_sub(buf, " and ", "a=0", "b=1", "c=2", 45, 67);
2849  CHECK(result == "a=0 and b=1 and c=2 and 45 and 67");
2850  CHECK(result.is_sub(buf));
2851 
2852  // catseprs() means CATSEP & ReSize:
2853  //
2854  // Instead of a substr, it receives any owning linear char container
2855  // for which to_substr() is defined (using ADL).
2856  // <ryml_std.hpp> has to_substr() definitions for std::string and
2857  // std::vector<char>.
2858  //
2859  // catseprs() starts by calling catsep(), and if needed, resizes the container
2860  // and calls catsep() again.
2861  //
2862  // Note that unless the container is previously sized, this
2863  // may cause an allocation, which will make your code slower.
2864  // Make sure to call .reserve() on the container for real
2865  // production code.
2866  std::string sbuf;
2867  ryml::catseprs(&sbuf, " and ", "a=0", "b=1", "c=2", 45, 67);
2868  CHECK(sbuf == "a=0 and b=1 and c=2 and 45 and 67");
2869  std::vector<char> vbuf; // works with any linear char container
2870  ryml::catseprs(&vbuf, " and ", "a=0", "b=1", "c=2", 45, 67);
2871  CHECK(ryml::to_csubstr(vbuf) == "a=0 and b=1 and c=2 and 45 and 67");
2872 
2873  // with catseprs() it is also possible to append:
2874  ryml::catseprs_append(&sbuf, " well ", " --- a=0", "b=11", "c=12", 145, 167);
2875  CHECK(sbuf == "a=0 and b=1 and c=2 and 45 and 67 --- a=0 well b=11 well c=12 well 145 well 167");
2876  }
2877 
2878  // uncatsep(): read arguments with a separator - opposite of catsep()
2879  {
2880  char buf_[256] = {};
2881 
2882  int a = 0, b = 1, c = 2;
2883  ryml::csubstr result = ryml::catsep_sub(buf_, ' ', a, b, c);
2884  CHECK(result == "0 1 2");
2885  int aa = -1, bb = -2, cc = -3;
2886  char sep = 'b';
2887  size_t num_characters = ryml::uncatsep(result, sep, aa, bb, cc);
2888  CHECK(num_characters == result.size());
2889  CHECK(aa == a);
2890  CHECK(bb == b);
2891  CHECK(cc == c);
2892  CHECK(sep == ' ');
2893 
2894  sep = '_';
2895  result = ryml::catsep_sub(buf_, ' ', 10, 20, 30);
2896  CHECK(result == "10 20 30");
2897  num_characters = ryml::uncatsep(result, sep, aa, bb, cc);
2898  CHECK(num_characters == result.size());
2899  CHECK(aa == 10);
2900  CHECK(bb == 20);
2901  CHECK(cc == 30);
2902  CHECK(sep == ' ');
2903  }
2904 
2905  // formatting individual arguments
2906  {
2907  using namespace ryml; // all the symbols below are in the ryml namespace.
2908  char buf_[256] = {}; // all the results below are written in this buffer
2909  substr buf = buf_;
2910  // --------------------------------------
2911  // fmt::boolalpha(): format as true/false
2912  // --------------------------------------
2913  // just as with std streams, printing a bool will output the integer value:
2914  CHECK("0" == cat_sub(buf, false));
2915  CHECK("1" == cat_sub(buf, true));
2916  // to force a "true"/"false", use fmt::boolalpha:
2917  CHECK("false" == cat_sub(buf, fmt::boolalpha(false)));
2918  CHECK("true" == cat_sub(buf, fmt::boolalpha(true)));
2919 
2920  // ---------------------------------
2921  // fmt::hex(): format as hexadecimal
2922  // ---------------------------------
2923  CHECK("0xff" == cat_sub(buf, fmt::hex(255)));
2924  CHECK("0x100" == cat_sub(buf, fmt::hex(256)));
2925  CHECK("-0xff" == cat_sub(buf, fmt::hex(-255)));
2926  CHECK("-0x100" == cat_sub(buf, fmt::hex(-256)));
2927  CHECK("3735928559" == cat_sub(buf, UINT32_C(0xdeadbeef)));
2928  CHECK("0xdeadbeef" == cat_sub(buf, fmt::hex(UINT32_C(0xdeadbeef))));
2929  // ----------------------------
2930  // fmt::bin(): format as binary
2931  // ----------------------------
2932  CHECK("0b1000" == cat_sub(buf, fmt::bin(8)));
2933  CHECK("0b1001" == cat_sub(buf, fmt::bin(9)));
2934  CHECK("0b10001" == cat_sub(buf, fmt::bin(17)));
2935  CHECK("0b11001" == cat_sub(buf, fmt::bin(25)));
2936  CHECK("-0b1000" == cat_sub(buf, fmt::bin(-8)));
2937  CHECK("-0b1001" == cat_sub(buf, fmt::bin(-9)));
2938  CHECK("-0b10001" == cat_sub(buf, fmt::bin(-17)));
2939  CHECK("-0b11001" == cat_sub(buf, fmt::bin(-25)));
2940  // ---------------------------
2941  // fmt::bin(): format as octal
2942  // ---------------------------
2943  CHECK("0o77" == cat_sub(buf, fmt::oct(63)));
2944  CHECK("0o100" == cat_sub(buf, fmt::oct(64)));
2945  CHECK("0o377" == cat_sub(buf, fmt::oct(255)));
2946  CHECK("0o400" == cat_sub(buf, fmt::oct(256)));
2947  CHECK("0o1000" == cat_sub(buf, fmt::oct(512)));
2948  CHECK("-0o77" == cat_sub(buf, fmt::oct(-63)));
2949  CHECK("-0o100" == cat_sub(buf, fmt::oct(-64)));
2950  CHECK("-0o377" == cat_sub(buf, fmt::oct(-255)));
2951  CHECK("-0o400" == cat_sub(buf, fmt::oct(-256)));
2952  CHECK("-0o1000" == cat_sub(buf, fmt::oct(-512)));
2953  // ---------------------------
2954  // fmt::zpad(): pad with zeros
2955  // ---------------------------
2956  CHECK("000063" == cat_sub(buf, fmt::zpad(63, 6)));
2957  CHECK( "00063" == cat_sub(buf, fmt::zpad(63, 5)));
2958  CHECK( "0063" == cat_sub(buf, fmt::zpad(63, 4)));
2959  CHECK( "063" == cat_sub(buf, fmt::zpad(63, 3)));
2960  CHECK( "63" == cat_sub(buf, fmt::zpad(63, 2)));
2961  CHECK( "63" == cat_sub(buf, fmt::zpad(63, 1))); // will never trim the result
2962  CHECK( "63" == cat_sub(buf, fmt::zpad(63, 0))); // will never trim the result
2963  CHECK("0x00003f" == cat_sub(buf, fmt::zpad(fmt::hex(63), 6)));
2964  CHECK("0o000077" == cat_sub(buf, fmt::zpad(fmt::oct(63), 6)));
2965  CHECK("0b00011001" == cat_sub(buf, fmt::zpad(fmt::bin(25), 8)));
2966  // ------------------------------------------------
2967  // fmt::left(): align left with a given field width
2968  // ------------------------------------------------
2969  CHECK("63 " == cat_sub(buf, fmt::left(63, 6)));
2970  CHECK("63 " == cat_sub(buf, fmt::left(63, 5)));
2971  CHECK("63 " == cat_sub(buf, fmt::left(63, 4)));
2972  CHECK("63 " == cat_sub(buf, fmt::left(63, 3)));
2973  CHECK("63" == cat_sub(buf, fmt::left(63, 2)));
2974  CHECK("63" == cat_sub(buf, fmt::left(63, 1))); // will never trim the result
2975  CHECK("63" == cat_sub(buf, fmt::left(63, 0))); // will never trim the result
2976  // the fill character can be specified (defaults to ' '):
2977  CHECK("63----" == cat_sub(buf, fmt::left(63, 6, '-')));
2978  CHECK("63++++" == cat_sub(buf, fmt::left(63, 6, '+')));
2979  CHECK("63////" == cat_sub(buf, fmt::left(63, 6, '/')));
2980  CHECK("630000" == cat_sub(buf, fmt::left(63, 6, '0')));
2981  CHECK("63@@@@" == cat_sub(buf, fmt::left(63, 6, '@')));
2982  CHECK("0x003f " == cat_sub(buf, fmt::left(fmt::zpad(fmt::hex(63), 4), 10)));
2983  // --------------------------------------------------
2984  // fmt::right(): align right with a given field width
2985  // --------------------------------------------------
2986  CHECK(" 63" == cat_sub(buf, fmt::right(63, 6)));
2987  CHECK(" 63" == cat_sub(buf, fmt::right(63, 5)));
2988  CHECK(" 63" == cat_sub(buf, fmt::right(63, 4)));
2989  CHECK(" 63" == cat_sub(buf, fmt::right(63, 3)));
2990  CHECK("63" == cat_sub(buf, fmt::right(63, 2)));
2991  CHECK("63" == cat_sub(buf, fmt::right(63, 1))); // will never trim the result
2992  CHECK("63" == cat_sub(buf, fmt::right(63, 0))); // will never trim the result
2993  // the fill character can be specified (defaults to ' '):
2994  CHECK("----63" == cat_sub(buf, fmt::right(63, 6, '-')));
2995  CHECK("++++63" == cat_sub(buf, fmt::right(63, 6, '+')));
2996  CHECK("////63" == cat_sub(buf, fmt::right(63, 6, '/')));
2997  CHECK("000063" == cat_sub(buf, fmt::right(63, 6, '0')));
2998  CHECK("@@@@63" == cat_sub(buf, fmt::right(63, 6, '@')));
2999  CHECK(" 0x003f" == cat_sub(buf, fmt::right(fmt::zpad(fmt::hex(63), 4), 10)));
3000 
3001  // ------------------------------------------
3002  // fmt::real(): format floating point numbers
3003  // ------------------------------------------
3004  // see also sample_float_precision()
3005  CHECK("0" == cat_sub(buf, fmt::real(0.01f, 0)));
3006  CHECK("0.0" == cat_sub(buf, fmt::real(0.01f, 1)));
3007  CHECK("0.01" == cat_sub(buf, fmt::real(0.01f, 2)));
3008  CHECK("0.010" == cat_sub(buf, fmt::real(0.01f, 3)));
3009  CHECK("0.0100" == cat_sub(buf, fmt::real(0.01f, 4)));
3010  CHECK("0.01000" == cat_sub(buf, fmt::real(0.01f, 5)));
3011  CHECK("1" == cat_sub(buf, fmt::real(1.01f, 0)));
3012  CHECK("1.0" == cat_sub(buf, fmt::real(1.01f, 1)));
3013  CHECK("1.01" == cat_sub(buf, fmt::real(1.01f, 2)));
3014  CHECK("1.010" == cat_sub(buf, fmt::real(1.01f, 3)));
3015  CHECK("1.0100" == cat_sub(buf, fmt::real(1.01f, 4)));
3016  CHECK("1.01000" == cat_sub(buf, fmt::real(1.01f, 5)));
3017  CHECK("1" == cat_sub(buf, fmt::real(1.234234234, 0)));
3018  CHECK("1.2" == cat_sub(buf, fmt::real(1.234234234, 1)));
3019  CHECK("1.23" == cat_sub(buf, fmt::real(1.234234234, 2)));
3020  CHECK("1.234" == cat_sub(buf, fmt::real(1.234234234, 3)));
3021  CHECK("1.2342" == cat_sub(buf, fmt::real(1.234234234, 4)));
3022  CHECK("1.23423" == cat_sub(buf, fmt::real(1.234234234, 5)));
3023  CHECK("1000000.00000" == cat_sub(buf, fmt::real(1000000.000000000, 5)));
3024  CHECK("1234234.23423" == cat_sub(buf, fmt::real(1234234.234234234, 5)));
3025  // AKA %f
3026  CHECK("1000000.00000" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_FLOAT))); // AKA %f, same as above
3027  CHECK("1234234.23423" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_FLOAT))); // AKA %f
3028  CHECK("1234234.2342" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_FLOAT))); // AKA %f
3029  CHECK("1234234.234" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_FLOAT))); // AKA %f
3030  CHECK("1234234.23" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_FLOAT))); // AKA %f
3031  // AKA %e
3032  CHECK("1.00000e+06" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_SCIENT))); // AKA %e
3033  CHECK("1.23423e+06" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_SCIENT))); // AKA %e
3034  CHECK("1.2342e+06" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_SCIENT))); // AKA %e
3035  CHECK("1.234e+06" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_SCIENT))); // AKA %e
3036  CHECK("1.23e+06" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_SCIENT))); // AKA %e
3037  // AKA %g
3038  CHECK("1e+06" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_FLEX))); // AKA %g
3039  CHECK("1.2342e+06" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_FLEX))); // AKA %g
3040  CHECK("1.234e+06" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_FLEX))); // AKA %g
3041  CHECK("1.23e+06" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_FLEX))); // AKA %g
3042  CHECK("1.2e+06" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_FLEX))); // AKA %g
3043  // FTOA_HEXA: AKA %a (hexadecimal formatting of floats)
3044  CHECK("0x1.e8480p+19" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_HEXA))); // AKA %a
3045  CHECK("0x1.2d53ap+20" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_HEXA))); // AKA %a
3046 
3047  // --------------------------------------------------------------
3048  // fmt::raw(): dump data in machine format (respecting alignment)
3049  // --------------------------------------------------------------
3050  {
3051  C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wcast-align") // we're casting the values directly, so alignment is strictly respected.
3052  const uint32_t payload[] = {10, 20, 30, 40, UINT32_C(0xdeadbeef)};
3053  // (package payload as a substr, for comparison only)
3054  csubstr expected = csubstr((const char *)payload, sizeof(payload));
3055  csubstr actual = cat_sub(buf, fmt::raw(payload));
3056  CHECK(!actual.overlaps(expected));
3057  CHECK(0 == memcmp(expected.str, actual.str, expected.len));
3058  // also possible with variables:
3059  for(const uint32_t value : payload)
3060  {
3061  // (package payload as a substr, for comparison only)
3062  expected = csubstr((const char *)&value, sizeof(value));
3063  actual = cat_sub(buf, fmt::raw(value));
3064  CHECK(actual.size() == sizeof(uint32_t));
3065  CHECK(!actual.overlaps(expected));
3066  CHECK(0 == memcmp(expected.str, actual.str, expected.len));
3067  // with non-const data, fmt::craw() may be needed for disambiguation:
3068  actual = cat_sub(buf, fmt::craw(value));
3069  CHECK(actual.size() == sizeof(uint32_t));
3070  CHECK(!actual.overlaps(expected));
3071  CHECK(0 == memcmp(expected.str, actual.str, expected.len));
3072  //
3073  // read back:
3074  uint32_t result;
3075  auto reader = fmt::raw(result); // keeps a reference to result
3076  CHECK(&result == (uint32_t*)reader.buf);
3077  CHECK(reader.len == sizeof(uint32_t));
3078  uncat(actual, reader);
3079  // and compare:
3080  // (vs2017/release/32bit does not reload result from cache, so force it)
3081  result = *(uint32_t*)reader.buf;
3082  CHECK(result == value); // roundtrip completed successfully
3083  }
3084  C4_SUPPRESS_WARNING_GCC_CLANG_POP
3085  }
3086 
3087  // -------------------------
3088  // fmt::base64(): see below!
3089  // -------------------------
3090  }
3091 }
left_< T > left(T val, size_t width, char padchar=' ')
mark an argument to be aligned left
Definition: format.hpp:515
right_< T > right(T val, size_t width, char padchar=' ')
mark an argument to be aligned right
Definition: format.hpp:522
substr cat_sub(substr buf, Args &&...args)
like c4::cat() but return a substr instead of a size
Definition: format.hpp:594
csubstr catrs_append(CharOwningContainer *cont, Args const &...args)
cat+resize+append: like c4::cat(), but receives a container, and appends to it instead of overwriting...
Definition: format.hpp:919
size_t cat(substr buf, Arg const &a, Args const &...more)
serialize the arguments, concatenating them to the given fixed-size buffer.
Definition: format.hpp:584
void catrs(CharOwningContainer *cont, Args const &...args)
cat+resize: like c4::cat(), but receives a container, and resizes it as needed to contain the result.
Definition: format.hpp:888
csubstr catseprs_append(CharOwningContainer *cont, Sep const &sep, Args const &...args)
catsep+resize+append: like catsep(), but receives a container, and appends the arguments,...
Definition: format.hpp:973
void catseprs(CharOwningContainer *cont, Sep const &sep, Args const &...args)
catsep+resize: like c4::catsep(), but receives a container, and resizes it as needed to contain the r...
Definition: format.hpp:941
size_t catsep(substr buf, Sep const &sep, Arg const &a, Args const &...more)
serialize the arguments, concatenating them to the given fixed-size buffer, using a separator between...
Definition: format.hpp:717
substr catsep_sub(substr buf, Args &&...args)
like c4::catsep() but return a substr instead of a size
Definition: format.hpp:728
@ FTOA_FLEX
print the real number in flexible format (like g)
Definition: charconv.hpp:206
@ FTOA_SCIENT
print the real number in scientific format (like e)
Definition: charconv.hpp:204
@ FTOA_HEXA
print the real number in hexadecimal format (like a)
Definition: charconv.hpp:208
csubstr formatrs_append(CharOwningContainer *cont, csubstr fmt, Args const &...args)
format+resize+append: like format(), but receives a container, and appends the arguments,...
Definition: format.hpp:1025
substr format_sub(substr buf, csubstr fmt, Args const &...args)
like c4::format() but return a substr instead of a size
Definition: format.hpp:826
size_t format(substr buf, csubstr fmt, Arg const &a, Args const &...more)
using a format string, serialize the arguments into the given fixed-size buffer.
Definition: format.hpp:806
void formatrs(CharOwningContainer *cont, csubstr fmt, Args const &...args)
format+resize: like c4::format(), but receives a container, and resizes it as needed to contain the r...
Definition: format.hpp:995
integral_< intptr_t > hex(std::nullptr_t)
format null as an hexadecimal value
Definition: format.hpp:163
integral_< intptr_t > bin(std::nullptr_t)
format null as a binary 0-1 value
Definition: format.hpp:215
integral_< intptr_t > oct(std::nullptr_t)
format null as an octal value
Definition: format.hpp:188
const_raw_wrapper raw(cblob data, size_t alignment=alignof(max_align_t))
mark a variable to be written in raw binary format, using memcpy
Definition: format.hpp:416
const_raw_wrapper craw(cblob data, size_t alignment=alignof(max_align_t))
mark a variable to be written in raw binary format, using memcpy
Definition: format.hpp:410
size_t uncat(csubstr buf, Arg &a, Args &...more)
deserialize the arguments from the given buffer.
Definition: format.hpp:625
size_t uncatsep(csubstr buf, Sep &sep, Arg &a, Args &...more)
deserialize the arguments from the given buffer.
Definition: format.hpp:758
size_t unformat(csubstr buf, csubstr fmt, Arg &a, Args &...more)
using a format string, deserialize the arguments from the given buffer.
Definition: format.hpp:855
integral_padded_< T > zpad(T val, size_t num_digits)
pad the argument with zeroes on the left, with decimal radix
Definition: format.hpp:235

References c4::fmt::bin, c4::fmt::boolalpha(), c4::cat(), c4::cat_sub(), c4::catrs(), c4::catrs_append(), c4::catsep(), c4::catsep_sub(), c4::catseprs(), c4::catseprs_append(), CHECK, c4::fmt::craw(), c4::format(), c4::format_sub(), c4::formatrs(), c4::formatrs_append(), c4::FTOA_FLEX, c4::FTOA_FLOAT, c4::FTOA_HEXA, c4::FTOA_SCIENT, c4::fmt::hex, c4::fmt::left(), c4::yml::npos, c4::fmt::oct, c4::fmt::raw(), c4::fmt::real(), c4::fmt::right(), c4::to_csubstr(), c4::uncat(), c4::uncatsep(), c4::unformat(), and c4::fmt::zpad().

◆ sample_base64()

void sample::sample_base64 ( )

demonstrates how to read and write base64-encoded blobs.

See also
Base64 encoding/decoding

Definition at line 3099 of file quickstart.cpp.

3100 {
3101  ryml::Tree tree;
3102  tree.rootref() |= ryml::MAP;
3103  struct text_and_base64 { ryml::csubstr text, base64; };
3104  text_and_base64 cases[] = {
3105  {{"Love all, trust a few, do wrong to none."}, {"TG92ZSBhbGwsIHRydXN0IGEgZmV3LCBkbyB3cm9uZyB0byBub25lLg=="}},
3106  {{"The fool doth think he is wise, but the wise man knows himself to be a fool."}, {"VGhlIGZvb2wgZG90aCB0aGluayBoZSBpcyB3aXNlLCBidXQgdGhlIHdpc2UgbWFuIGtub3dzIGhpbXNlbGYgdG8gYmUgYSBmb29sLg=="}},
3107  {{"Brevity is the soul of wit."}, {"QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu"}},
3108  {{"All that glitters is not gold."}, {"QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu"}},
3109  };
3110  // to encode base64 and write the result to val:
3111  for(text_and_base64 c : cases)
3112  {
3113  tree[c.text] << ryml::fmt::base64(c.text);
3114  CHECK(tree[c.text].val() == c.base64);
3115  }
3116  // to encode base64 and write the result to key:
3117  for(text_and_base64 c : cases)
3118  {
3119  tree.rootref().append_child() << ryml::key(ryml::fmt::base64(c.text)) << c.text;
3120  CHECK(tree[c.base64].val() == c.text);
3121  }
3122  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"('Love all, trust a few, do wrong to none.': TG92ZSBhbGwsIHRydXN0IGEgZmV3LCBkbyB3cm9uZyB0byBub25lLg==
3123 'The fool doth think he is wise, but the wise man knows himself to be a fool.': VGhlIGZvb2wgZG90aCB0aGluayBoZSBpcyB3aXNlLCBidXQgdGhlIHdpc2UgbWFuIGtub3dzIGhpbXNlbGYgdG8gYmUgYSBmb29sLg==
3124 Brevity is the soul of wit.: QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu
3125 All that glitters is not gold.: QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu
3126 TG92ZSBhbGwsIHRydXN0IGEgZmV3LCBkbyB3cm9uZyB0byBub25lLg==: 'Love all, trust a few, do wrong to none.'
3127 VGhlIGZvb2wgZG90aCB0aGluayBoZSBpcyB3aXNlLCBidXQgdGhlIHdpc2UgbWFuIGtub3dzIGhpbXNlbGYgdG8gYmUgYSBmb29sLg==: 'The fool doth think he is wise, but the wise man knows himself to be a fool.'
3128 QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu: Brevity is the soul of wit.
3129 QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu: All that glitters is not gold.
3130 )");
3131  char buf1_[128], buf2_[128];
3132  ryml::substr buf1 = buf1_; // this is where we will write the result (using >>)
3133  ryml::substr buf2 = buf2_; // this is where we will write the result (using deserialize_val()/deserialize_key())
3134  std::string result = {}; // show also how to decode to a std::string
3135  // to decode the val base64 and write the result to buf:
3136  for(const text_and_base64 c : cases)
3137  {
3138  // write the decoded result into the given buffer
3139  tree[c.text] >> ryml::fmt::base64(buf1); // cannot know the needed size
3140  size_t len = tree[c.text].deserialize_val(ryml::fmt::base64(buf2)); // returns the needed size
3141  CHECK(len <= buf1.len);
3142  CHECK(len <= buf2.len);
3143  CHECK(c.text.len == len);
3144  CHECK(buf1.first(len) == c.text);
3145  CHECK(buf2.first(len) == c.text);
3146  //
3147  // interop with std::string: using substr
3148  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3149  len = tree[c.text].deserialize_val(ryml::fmt::base64(ryml::to_substr(result))); // returns the needed size
3150  if(len > result.size()) // the size was not enough; resize and call again
3151  {
3152  result.resize(len);
3153  len = tree[c.text].deserialize_val(ryml::fmt::base64(ryml::to_substr(result))); // returns the needed size
3154  }
3155  result.resize(len); // trim to the length of the decoded buffer
3156  CHECK(result == c.text);
3157  //
3158  // interop with std::string: using blob
3159  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3160  ryml::blob strblob(&result[0], result.size());
3161  CHECK(strblob.buf == result.data());
3162  CHECK(strblob.len == result.size());
3163  len = tree[c.text].deserialize_val(ryml::fmt::base64(strblob)); // returns the needed size
3164  if(len > result.size()) // the size was not enough; resize and call again
3165  {
3166  result.resize(len);
3167  strblob = {&result[0], result.size()};
3168  CHECK(strblob.buf == result.data());
3169  CHECK(strblob.len == result.size());
3170  len = tree[c.text].deserialize_val(ryml::fmt::base64(strblob)); // returns the needed size
3171  }
3172  result.resize(len); // trim to the length of the decoded buffer
3173  CHECK(result == c.text);
3174  //
3175  // Note also these are just syntatic wrappers to simplify client code.
3176  // You can call into the lower level functions without much effort:
3177  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3178  ryml::csubstr encoded = tree[c.text].val();
3179  CHECK(encoded == c.base64);
3180  len = base64_decode(encoded, ryml::blob{&result[0], result.size()});
3181  if(len > result.size()) // the size was not enough; resize and call again
3182  {
3183  result.resize(len);
3184  len = base64_decode(encoded, ryml::blob{&result[0], result.size()});
3185  }
3186  result.resize(len); // trim to the length of the decoded buffer
3187  CHECK(result == c.text);
3188  }
3189  // to decode the key base64 and write the result to buf:
3190  for(const text_and_base64 c : cases)
3191  {
3192  // write the decoded result into the given buffer
3193  tree[c.base64] >> ryml::key(ryml::fmt::base64(buf1)); // cannot know the needed size
3194  size_t len = tree[c.base64].deserialize_key(ryml::fmt::base64(buf2)); // returns the needed size
3195  CHECK(len <= buf1.len);
3196  CHECK(len <= buf2.len);
3197  CHECK(c.text.len == len);
3198  CHECK(buf1.first(len) == c.text);
3199  CHECK(buf2.first(len) == c.text);
3200  // interop with std::string: using substr
3201  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3202  len = tree[c.base64].deserialize_key(ryml::fmt::base64(ryml::to_substr(result))); // returns the needed size
3203  if(len > result.size()) // the size was not enough; resize and call again
3204  {
3205  result.resize(len);
3206  len = tree[c.base64].deserialize_key(ryml::fmt::base64(ryml::to_substr(result))); // returns the needed size
3207  }
3208  result.resize(len); // trim to the length of the decoded buffer
3209  CHECK(result == c.text);
3210  //
3211  // interop with std::string: using blob
3212  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3213  ryml::blob strblob = {&result[0], result.size()};
3214  CHECK(strblob.buf == result.data());
3215  CHECK(strblob.len == result.size());
3216  len = tree[c.base64].deserialize_key(ryml::fmt::base64(strblob)); // returns the needed size
3217  if(len > result.size()) // the size was not enough; resize and call again
3218  {
3219  result.resize(len);
3220  strblob = {&result[0], result.size()};
3221  CHECK(strblob.buf == result.data());
3222  CHECK(strblob.len == result.size());
3223  len = tree[c.base64].deserialize_key(ryml::fmt::base64(strblob)); // returns the needed size
3224  }
3225  result.resize(len); // trim to the length of the decoded buffer
3226  CHECK(result == c.text);
3227  //
3228  // Note also these are just syntactic wrappers to simplify client code.
3229  // You can call into the lower level functions without much effort:
3230  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3231  ryml::csubstr encoded = tree[c.base64].key();
3232  CHECK(encoded == c.base64);
3233  len = base64_decode(encoded, ryml::blob{&result[0], result.size()});
3234  if(len > result.size()) // the size was not enough; resize and call again
3235  {
3236  result.resize(len);
3237  len = base64_decode(encoded, ryml::blob{&result[0], result.size()});
3238  }
3239  result.resize(len); // trim to the length of the decoded buffer
3240  CHECK(result == c.text);
3241  }
3242  // directly encode variables
3243  {
3244  const uint64_t valin = UINT64_C(0xdeadbeef);
3245  uint64_t valout = 0;
3246  tree["deadbeef"] << c4::fmt::base64(valin); // sometimes cbase64() is needed to avoid ambiguity
3247  size_t len = tree["deadbeef"].deserialize_val(ryml::fmt::base64(valout));
3248  CHECK(len <= sizeof(valout));
3249  CHECK(valout == UINT64_C(0xdeadbeef)); // base64 roundtrip is bit-accurate
3250  }
3251  // directly encode memory ranges
3252  {
3253  const uint32_t data_in[11] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xdeadbeef};
3254  uint32_t data_out[11] = {};
3255  CHECK(memcmp(data_in, data_out, sizeof(data_in)) != 0); // before the roundtrip
3256  tree["int_data"] << c4::fmt::base64(data_in);
3257  size_t len = tree["int_data"].deserialize_val(ryml::fmt::base64(data_out));
3258  CHECK(len <= sizeof(data_out));
3259  CHECK(memcmp(data_in, data_out, sizeof(data_in)) == 0); // after the roundtrip
3260  }
3261 }
const_base64_wrapper base64(Args const &...args)
mark a variable to be written in base64 format
Definition: base64.hpp:96
size_t base64_decode(csubstr encoded, blob data)
decode the base64 encoding in the given buffer

References c4::yml::NodeRef::append_child(), c4::fmt::base64(), c4::base64_decode(), CHECK, c4::yml::Tree::key(), c4::yml::key(), c4::yml::MAP, c4::yml::Tree::rootref(), c4::yml::Tree::size(), c4::to_substr(), and c4::yml::Tree::val().

◆ sample_user_scalar_types()

void sample::sample_user_scalar_types ( )

to add scalar types (ie leaf types converting to/from string), define the functions above for those types.

See Serialize/deserialize scalar types.

Definition at line 3479 of file quickstart.cpp.

3480 {
3481  ryml::Tree t;
3482 
3483  auto r = t.rootref();
3484  r |= ryml::MAP;
3485 
3486  vec2<int> v2in{10, 11};
3487  vec2<int> v2out{1, 2};
3488  r["v2"] << v2in; // serializes to the tree's arena, and then sets the keyval
3489  r["v2"] >> v2out;
3490  CHECK(v2in.x == v2out.x);
3491  CHECK(v2in.y == v2out.y);
3492  vec3<int> v3in{100, 101, 102};
3493  vec3<int> v3out{1, 2, 3};
3494  r["v3"] << v3in; // serializes to the tree's arena, and then sets the keyval
3495  r["v3"] >> v3out;
3496  CHECK(v3in.x == v3out.x);
3497  CHECK(v3in.y == v3out.y);
3498  CHECK(v3in.z == v3out.z);
3499  vec4<int> v4in{1000, 1001, 1002, 1003};
3500  vec4<int> v4out{1, 2, 3, 4};
3501  r["v4"] << v4in; // serializes to the tree's arena, and then sets the keyval
3502  r["v4"] >> v4out;
3503  CHECK(v4in.x == v4out.x);
3504  CHECK(v4in.y == v4out.y);
3505  CHECK(v4in.z == v4out.z);
3506  CHECK(v4in.w == v4out.w);
3507  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(v2: '(10,11)'
3508 v3: '(100,101,102)'
3509 v4: '(1000,1001,1002,1003)'
3510 )");
3511 
3512  // note that only the used functions are needed:
3513  // - if a type is only parsed, then only from_chars() is needed
3514  // - if a type is only emitted, then only to_chars() is needed
3515  emit_only_vec2<int> eov2in{20, 21}; // only has to_chars()
3516  parse_only_vec2<int> pov2out{1, 2}; // only has from_chars()
3517  r["v2"] << eov2in; // serializes to the tree's arena, and then sets the keyval
3518  r["v2"] >> pov2out;
3519  CHECK(eov2in.x == pov2out.x);
3520  CHECK(eov2in.y == pov2out.y);
3521  emit_only_vec3<int> eov3in{30, 31, 32}; // only has to_chars()
3522  parse_only_vec3<int> pov3out{1, 2, 3}; // only has from_chars()
3523  r["v3"] << eov3in; // serializes to the tree's arena, and then sets the keyval
3524  r["v3"] >> pov3out;
3525  CHECK(eov3in.x == pov3out.x);
3526  CHECK(eov3in.y == pov3out.y);
3527  CHECK(eov3in.z == pov3out.z);
3528  emit_only_vec4<int> eov4in{40, 41, 42, 43}; // only has to_chars()
3529  parse_only_vec4<int> pov4out{1, 2, 3, 4}; // only has from_chars()
3530  r["v4"] << eov4in; // serializes to the tree's arena, and then sets the keyval
3531  r["v4"] >> pov4out;
3532  CHECK(eov4in.x == pov4out.x);
3533  CHECK(eov4in.y == pov4out.y);
3534  CHECK(eov4in.z == pov4out.z);
3535  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(v2: '(20,21)'
3536 v3: '(30,31,32)'
3537 v4: '(40,41,42,43)'
3538 )");
3539 }

References CHECK, c4::yml::MAP, and c4::yml::Tree::rootref().

◆ sample_user_container_types()

void sample::sample_user_container_types ( )

shows how to serialize/deserialize container types.

See also
Serialize/deserialize container types
sample_std_types

Definition at line 3675 of file quickstart.cpp.

3676 {
3677  my_type mt_in{
3678  {20, 21},
3679  {30, 31, 32},
3680  {40, 41, 42, 43},
3681  {{101, 102, 103, 104, 105, 106, 107}},
3682  {{{1001, 2001}, {1002, 2002}, {1003, 2003}}},
3683  };
3684  my_type mt_out;
3685 
3686  ryml::Tree t;
3687  t.rootref() << mt_in; // read from this
3688  t.crootref() >> mt_out; // assign here
3689  CHECK(mt_out.v2.x == mt_in.v2.x);
3690  CHECK(mt_out.v2.y == mt_in.v2.y);
3691  CHECK(mt_out.v3.x == mt_in.v3.x);
3692  CHECK(mt_out.v3.y == mt_in.v3.y);
3693  CHECK(mt_out.v3.z == mt_in.v3.z);
3694  CHECK(mt_out.v4.x == mt_in.v4.x);
3695  CHECK(mt_out.v4.y == mt_in.v4.y);
3696  CHECK(mt_out.v4.z == mt_in.v4.z);
3697  CHECK(mt_out.v4.w == mt_in.v4.w);
3698  CHECK(mt_in.seq.seq_member.size() > 0);
3699  CHECK(mt_out.seq.seq_member.size() == mt_in.seq.seq_member.size());
3700  for(size_t i = 0; i < mt_in.seq.seq_member.size(); ++i)
3701  {
3702  CHECK(mt_out.seq.seq_member[i] == mt_in.seq.seq_member[i]);
3703  }
3704  CHECK(mt_in.map.map_member.size() > 0);
3705  CHECK(mt_out.map.map_member.size() == mt_in.map.map_member.size());
3706  for(auto const& kv : mt_in.map.map_member)
3707  {
3708  CHECK(mt_out.map.map_member.find(kv.first) != mt_out.map.map_member.end());
3709  CHECK(mt_out.map.map_member[kv.first] == kv.second);
3710  }
3711  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(v2: '(20,21)'
3712 v3: '(30,31,32)'
3713 v4: '(40,41,42,43)'
3714 seq:
3715  - 101
3716  - 102
3717  - 103
3718  - 104
3719  - 105
3720  - 106
3721  - 107
3722 map:
3723  1001: 2001
3724  1002: 2002
3725  1003: 2003
3726 )");
3727 }

References CHECK, c4::yml::Tree::crootref(), sample::my_type::map, sample::my_map_type< K, V >::map_member, c4::yml::Tree::rootref(), sample::my_type::seq, sample::my_seq_type< T >::seq_member, sample::my_type::v2, sample::my_type::v3, sample::my_type::v4, sample::vec4< T >::w, sample::vec2< T >::x, sample::vec3< T >::x, sample::vec4< T >::x, sample::vec2< T >::y, sample::vec3< T >::y, sample::vec4< T >::y, sample::vec3< T >::z, and sample::vec4< T >::z.

◆ sample_std_types()

void sample::sample_std_types ( )

demonstrates usage with the std implementations provided by ryml in the ryml_std.hpp header

See also
Serialize/deserialize container types
also the STL section in Serialization/deserialization

Definition at line 3736 of file quickstart.cpp.

3737 {
3738  std::string yml_std_string = R"(- v2: '(20,21)'
3739  v3: '(30,31,32)'
3740  v4: '(40,41,42,43)'
3741  seq:
3742  - 101
3743  - 102
3744  - 103
3745  - 104
3746  - 105
3747  - 106
3748  - 107
3749  map:
3750  1001: 2001
3751  1002: 2002
3752  1003: 2003
3753 - v2: '(120,121)'
3754  v3: '(130,131,132)'
3755  v4: '(140,141,142,143)'
3756  seq:
3757  - 1101
3758  - 1102
3759  - 1103
3760  - 1104
3761  - 1105
3762  - 1106
3763  - 1107
3764  map:
3765  11001: 12001
3766  11002: 12002
3767  11003: 12003
3768 - v2: '(220,221)'
3769  v3: '(230,231,232)'
3770  v4: '(240,241,242,243)'
3771  seq:
3772  - 2101
3773  - 2102
3774  - 2103
3775  - 2104
3776  - 2105
3777  - 2106
3778  - 2107
3779  map:
3780  21001: 22001
3781  21002: 22002
3782  21003: 22003
3783 )";
3784  // parse in-place using the std::string above
3785  ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(yml_std_string));
3786  // my_type is a container-of-containers type. see above its
3787  // definition implementation for ryml.
3788  std::vector<my_type> vmt;
3789  tree.rootref() >> vmt;
3790  CHECK(vmt.size() == 3);
3791  ryml::Tree tree_out;
3792  tree_out.rootref() << vmt;
3793  CHECK(ryml::emitrs_yaml<std::string>(tree_out) == yml_std_string);
3794 }

References CHECK, c4::yml::parse_in_place(), c4::yml::Tree::rootref(), and c4::to_substr().

◆ sample_float_precision()

void sample::sample_float_precision ( )

control precision of serialized floats

Definition at line 3800 of file quickstart.cpp.

3801 {
3802  std::vector<double> reference{1.23234412342131234, 2.12323123143434237, 3.67847983572591234};
3803  // A safe precision for comparing doubles. May vary depending on
3804  // compiler flags. Double goes to about 15 digits, so 14 should be
3805  // safe enough for this test to succeed.
3806  const double precision_safe = 1.e-14;
3807  const size_t num_digits_safe = 14;
3808  const size_t num_digits_original = 17;
3809  auto get_num_digits = [](ryml::csubstr number){ return number.sub(2).len; };
3810  //
3811  // no significant precision is lost when reading
3812  // floating point numbers:
3813  {
3814  ryml::Tree tree = ryml::parse_in_arena(R"([1.23234412342131234, 2.12323123143434237, 3.67847983572591234])");
3815  std::vector<double> output;
3816  tree.rootref() >> output;
3817  CHECK(output.size() == reference.size());
3818  for(size_t i = 0; i < reference.size(); ++i)
3819  {
3820  CHECK(get_num_digits(tree[(ryml::id_type)i].val()) == num_digits_original);
3821  CHECK(fabs(output[i] - reference[i]) < precision_safe);
3822  }
3823  }
3824  //
3825  // However, depending on the compilation settings, there may be a
3826  // significant precision loss when serializing with the default
3827  // approach, operator<<(double):
3828  {
3829  ryml::Tree serialized;
3830  serialized.rootref() << reference;
3831  std::cout << serialized;
3832  // Without std::to_chars() there is a loss of precision:
3833  #if (!C4CORE_HAVE_STD_TOCHARS) // This macro is defined when std::to_chars() is available.
3834  CHECK(ryml::emitrs_yaml<std::string>(serialized) == R"(- 1.23234
3835 - 2.12323
3836 - 3.67848
3837 )" || (bool)"this is indicative; the exact results will vary from platform to platform.");
3838  C4_UNUSED(num_digits_safe);
3839  #else // ... but when using C++17 and above, the results are eminently equal:
3840  CHECK((ryml::emitrs_yaml<std::string>(serialized) == R"(- 1.2323441234213124
3841 - 2.1232312314343424
3842 - 3.6784798357259123
3843 )") || (bool)"this is indicative; the exact results will vary from platform to platform.");
3844  size_t pos = 0;
3845  for(ryml::ConstNodeRef child : serialized.rootref().children())
3846  {
3847  CHECK(get_num_digits(child.val()) >= num_digits_safe);
3848  double out = {};
3849  child >> out;
3850  CHECK(fabs(out - reference[pos++]) < precision_safe);
3851  }
3852  #endif
3853  }
3854  //
3855  // The difference is explained by the availability of
3856  // fastfloat::from_chars(), std::from_chars() and std::to_chars().
3857  //
3858  // ryml prefers the fastfloat::from_chars() version. Unfortunately
3859  // fastfloat does not have to_chars() (see
3860  // https://github.com/fastfloat/fast_float/issues/23).
3861  //
3862  // When C++17 is used, ryml uses std::to_chars(), which produces
3863  // good defaults.
3864  //
3865  // However, with earlier standards, or in some library
3866  // implementations, there's only snprintf() available. Every other
3867  // std library function will either disrespect the string limits,
3868  // or more precisely, accept no string size limits. So the
3869  // implementation of c4core (which ryml uses) falls back to
3870  // snprintf("%g"), and that picks by default a (low) number of
3871  // digits.
3872  //
3873  // But all is not lost for C++11/C++14 users!
3874  //
3875  // To force a particular precision when serializing, you can use
3876  // c4::fmt::real() (brought into the ryml:: namespace). Or you can
3877  // serialize the number yourself! The small downside is that you
3878  // have to build the container.
3879  //
3880  // First a function to check the result:
3881  auto check_precision = [&](ryml::Tree serialized){
3882  std::cout << serialized;
3883  // now it works!
3884  CHECK((ryml::emitrs_yaml<std::string>(serialized) == R"(- 1.23234412342131239
3885 - 2.12323123143434245
3886 - 3.67847983572591231
3887 )") || (bool)"this is indicative; the exact results will vary from platform to platform.");
3888  size_t pos = 0;
3889  for(ryml::ConstNodeRef child : serialized.rootref().children())
3890  {
3891  CHECK(get_num_digits(child.val()) == num_digits_original);
3892  double out = {};
3893  child >> out;
3894  CHECK(fabs(out - reference[pos++]) < precision_safe);
3895  }
3896  };
3897  //
3898  // Serialization example using fmt::real()
3899  {
3900  ryml::Tree serialized;
3901  ryml::NodeRef root = serialized.rootref();
3902  root |= ryml::SEQ;
3903  for(const double v : reference)
3904  root.append_child() << ryml::fmt::real(v, num_digits_original, ryml::FTOA_FLOAT);
3905  check_precision(serialized); // OK - now within bounds!
3906  }
3907  //
3908  // Serialization example using snprintf
3909  {
3910  ryml::Tree serialized;
3911  ryml::NodeRef root = serialized.rootref();
3912  root |= ryml::SEQ;
3913  char tmp[64];
3914  for(const double v : reference)
3915  {
3916  // reuse a buffer to serialize.
3917  // add 1 to the significant digits because the %g
3918  // specifier counts the integral digits.
3919  (void)snprintf(tmp, sizeof(tmp), "%.18g", v);
3920  // copy the serialized string to the tree (operator<<
3921  // copies to the arena, operator= just assigns the string
3922  // pointer and would be wrong in this case):
3923  root.append_child() << ryml::to_csubstr((const char*)tmp);
3924  }
3925  check_precision(serialized); // OK - now within bounds!
3926  }
3927 }

References c4::yml::NodeRef::append_child(), CHECK, c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::children(), c4::FTOA_FLOAT, c4::yml::parse_in_arena(), c4::fmt::real(), c4::yml::Tree::rootref(), c4::yml::SEQ, and c4::to_csubstr().

◆ sample_emit_to_container()

void sample::sample_emit_to_container ( )

demonstrates how to emit to a linear container of char

Definition at line 3933 of file quickstart.cpp.

3934 {
3935  // it is possible to emit to any linear container of char.
3936 
3937  ryml::csubstr ymla = "- 1\n- 2\n";
3938  ryml::csubstr ymlb = R"(- a
3939 - b
3940 - x0: 1
3941  x1: 2
3942 - champagne: Dom Perignon
3943  coffee: Arabica
3944  more:
3945  vinho verde: Soalheiro
3946  vinho tinto: Redoma 2017
3947  beer:
3948  - Rochefort 10
3949  - Busch
3950  - Leffe Rituel
3951 - foo
3952 - bar
3953 - baz
3954 - bat
3955 )";
3956  auto treea = ryml::parse_in_arena(ymla);
3957  auto treeb = ryml::parse_in_arena(ymlb);
3958 
3959  // eg, std::vector<char>
3960  {
3961  // do a blank call on an empty buffer to find the required size.
3962  // no overflow will occur, and returns a substr with the size
3963  // required to output
3964  ryml::csubstr output = ryml::emit_yaml(treea, treea.root_id(), ryml::substr{}, /*error_on_excess*/false);
3965  CHECK(output.str == nullptr);
3966  CHECK(output.len > 0);
3967  size_t num_needed_chars = output.len;
3968  std::vector<char> buf(num_needed_chars);
3969  // now try again with the proper buffer
3970  output = ryml::emit_yaml(treea, treea.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
3971  CHECK(output == ymla);
3972 
3973  // it is possible to reuse the buffer and grow it as needed.
3974  // first do a blank run to find the size:
3975  output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::substr{}, /*error_on_excess*/false);
3976  CHECK(output.str == nullptr);
3977  CHECK(output.len > 0);
3978  CHECK(output.len == ymlb.len);
3979  num_needed_chars = output.len;
3980  buf.resize(num_needed_chars);
3981  // now try again with the proper buffer
3982  output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
3983  CHECK(output == ymlb);
3984 
3985  // there is a convenience wrapper performing the same as above:
3986  // provided to_substr() is defined for that container.
3987  output = ryml::emitrs_yaml(treeb, &buf);
3988  CHECK(output == ymlb);
3989 
3990  // or you can just output a new container:
3991  // provided to_substr() is defined for that container.
3992  std::vector<char> another = ryml::emitrs_yaml<std::vector<char>>(treeb);
3993  CHECK(ryml::to_csubstr(another) == ymlb);
3994 
3995  // you can also emit nested nodes:
3996  another = ryml::emitrs_yaml<std::vector<char>>(treeb[3][2]);
3997  CHECK(ryml::to_csubstr(another) == R"(more:
3998  vinho verde: Soalheiro
3999  vinho tinto: Redoma 2017
4000 )");
4001  }
4002 
4003 
4004  // eg, std::string. notice this is the same code as above
4005  {
4006  // do a blank call on an empty buffer to find the required size.
4007  // no overflow will occur, and returns a substr with the size
4008  // required to output
4009  ryml::csubstr output = ryml::emit_yaml(treea, treea.root_id(), ryml::substr{}, /*error_on_excess*/false);
4010  CHECK(output.str == nullptr);
4011  CHECK(output.len > 0);
4012  size_t num_needed_chars = output.len;
4013  std::string buf;
4014  buf.resize(num_needed_chars);
4015  // now try again with the proper buffer
4016  output = ryml::emit_yaml(treea, treea.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4017  CHECK(output == ymla);
4018 
4019  // it is possible to reuse the buffer and grow it as needed.
4020  // first do a blank run to find the size:
4021  output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::substr{}, /*error_on_excess*/false);
4022  CHECK(output.str == nullptr);
4023  CHECK(output.len > 0);
4024  CHECK(output.len == ymlb.len);
4025  num_needed_chars = output.len;
4026  buf.resize(num_needed_chars);
4027  // now try again with the proper buffer
4028  output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4029  CHECK(output == ymlb);
4030 
4031  // there is a convenience wrapper performing the above instructions:
4032  // provided to_substr() is defined for that container
4033  output = ryml::emitrs_yaml(treeb, &buf);
4034  CHECK(output == ymlb);
4035 
4036  // or you can just output a new container:
4037  // provided to_substr() is defined for that container.
4038  std::string another = ryml::emitrs_yaml<std::string>(treeb);
4039  CHECK(ryml::to_csubstr(another) == ymlb);
4040 
4041  // you can also emit nested nodes:
4042  another = ryml::emitrs_yaml<std::string>(treeb[3][2]);
4043  CHECK(ryml::to_csubstr(another) == R"(more:
4044  vinho verde: Soalheiro
4045  vinho tinto: Redoma 2017
4046 )");
4047  }
4048 }
substr emitrs_yaml(Tree const &t, id_type id, EmitOptions const &opts, CharOwningContainer *cont, bool append=false)
(1) emit+resize: emit YAML to the given std::string/std::vector-like container, resizing it as needed...
Definition: emit.hpp:619

References CHECK, c4::yml::emit_yaml(), c4::yml::emitrs_yaml(), c4::yml::parse_in_arena(), c4::to_csubstr(), and c4::to_substr().

◆ sample_emit_to_stream()

void sample::sample_emit_to_stream ( )

demonstrates how to emit to a stream-like structure

Definition at line 4054 of file quickstart.cpp.

4055 {
4056  ryml::csubstr ymlb = R"(- a
4057 - b
4058 - x0: 1
4059  x1: 2
4060 - champagne: Dom Perignon
4061  coffee: Arabica
4062  more:
4063  vinho verde: Soalheiro
4064  vinho tinto: Redoma 2017
4065  beer:
4066  - Rochefort 10
4067  - Busch
4068  - Leffe Rituel
4069 - foo
4070 - bar
4071 - baz
4072 - bat
4073 )";
4074  auto tree = ryml::parse_in_arena(ymlb);
4075 
4076  std::string s;
4077 
4078  // emit a full tree
4079  {
4080  std::stringstream ss;
4081  ss << tree; // works with any stream having .operator<<() and .write()
4082  s = ss.str();
4083  CHECK(ryml::to_csubstr(s) == ymlb);
4084  }
4085 
4086  // emit a full tree as json
4087  {
4088  std::stringstream ss;
4089  ss << ryml::as_json(tree); // works with any stream having .operator<<() and .write()
4090  s = ss.str();
4091  CHECK(ryml::to_csubstr(s) == R"(["a","b",{"x0": 1,"x1": 2},{"champagne": "Dom Perignon","coffee": "Arabica","more": {"vinho verde": "Soalheiro","vinho tinto": "Redoma 2017"},"beer": ["Rochefort 10","Busch","Leffe Rituel"]},"foo","bar","baz","bat"])");
4092  }
4093 
4094  // emit a nested node
4095  {
4096  std::stringstream ss;
4097  ss << tree[3][2]; // works with any stream having .operator<<() and .write()
4098  s = ss.str();
4099  CHECK(ryml::to_csubstr(s) == R"(more:
4100  vinho verde: Soalheiro
4101  vinho tinto: Redoma 2017
4102 )");
4103  }
4104 
4105  // emit a nested node as json
4106  {
4107  std::stringstream ss;
4108  ss << ryml::as_json(tree[3][2]); // works with any stream having .operator<<() and .write()
4109  s = ss.str();
4110  CHECK(ryml::to_csubstr(s) == R"("more": {"vinho verde": "Soalheiro","vinho tinto": "Redoma 2017"})");
4111  }
4112 }
mark a tree or node to be emitted as yaml when using operator<<, with options.
Definition: emit.hpp:406

References CHECK, c4::yml::parse_in_arena(), and c4::to_csubstr().

◆ sample_emit_to_file()

void sample::sample_emit_to_file ( )

demonstrates how to emit to a FILE*

Definition at line 4118 of file quickstart.cpp.

4119 {
4120  ryml::csubstr yml = R"(- a
4121 - b
4122 - x0: 1
4123  x1: 2
4124 - champagne: Dom Perignon
4125  coffee: Arabica
4126  more:
4127  vinho verde: Soalheiro
4128  vinho tinto: Redoma 2017
4129  beer:
4130  - Rochefort 10
4131  - Busch
4132  - Leffe Rituel
4133 - foo
4134 - bar
4135 - baz
4136 - bat
4137 )";
4138  auto tree = ryml::parse_in_arena(yml);
4139  // this is emitting to stdout, but of course you can pass in any
4140  // FILE* obtained from fopen()
4141  size_t len = ryml::emit_yaml(tree, tree.root_id(), stdout);
4142  // the return value is the number of characters that were written
4143  // to the file
4144  CHECK(len == yml.len);
4145 }

References CHECK, c4::yml::emit_yaml(), and c4::yml::parse_in_arena().

◆ sample_emit_nested_node()

void sample::sample_emit_nested_node ( )

just like parsing into a nested node, you can also emit from a nested node.

Definition at line 4151 of file quickstart.cpp.

4152 {
4153  const ryml::Tree tree = ryml::parse_in_arena(R"(- a
4154 - b
4155 - x0: 1
4156  x1: 2
4157 - champagne: Dom Perignon
4158  coffee: Arabica
4159  more:
4160  vinho verde: Soalheiro
4161  vinho tinto: Redoma 2017
4162  beer:
4163  - Rochefort 10
4164  - Busch
4165  - Leffe Rituel
4166  - - and so
4167  - many other
4168  - wonderful beers
4169 - more
4170 - seq
4171 - members
4172 - here
4173 )");
4174  CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"]) == R"(beer:
4175  - Rochefort 10
4176  - Busch
4177  - Leffe Rituel
4178  - - and so
4179  - many other
4180  - wonderful beers
4181 )");
4182  CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][0]) == "Rochefort 10\n");
4183  CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][3]) == R"(- and so
4184 - many other
4185 - wonderful beers
4186 )");
4187 }

References CHECK, and c4::yml::parse_in_arena().

◆ sample_emit_style()

void sample::sample_emit_style ( )

[experimental] pick flow/block style for certain nodes.

Definition at line 4193 of file quickstart.cpp.

4194 {
4195  ryml::Tree tree = ryml::parse_in_arena(R"(
4196 NodeOne:
4197  - key: a
4198  desc: b
4199  class: c
4200  number: d
4201  - key: e
4202  desc: f
4203  class: g
4204  number: h
4205  - key: i
4206  desc: j
4207  class: k
4208  number: l
4209 )");
4210  // ryml uses block style by default:
4211  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(NodeOne:
4212  - key: a
4213  desc: b
4214  class: c
4215  number: d
4216  - key: e
4217  desc: f
4218  class: g
4219  number: h
4220  - key: i
4221  desc: j
4222  class: k
4223  number: l
4224 )");
4225  // you can override the emit style of individual nodes:
4226  for(ryml::NodeRef child : tree["NodeOne"].children())
4227  child |= ryml::FLOW_SL; // flow, single-line
4228  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(NodeOne:
4229  - {key: a,desc: b,class: c,number: d}
4230  - {key: e,desc: f,class: g,number: h}
4231  - {key: i,desc: j,class: k,number: l}
4232 )");
4233  tree["NodeOne"] |= ryml::FLOW_SL;
4234  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(NodeOne: [{key: a,desc: b,class: c,number: d},{key: e,desc: f,class: g,number: h},{key: i,desc: j,class: k,number: l}]
4235 )");
4236  tree.rootref() |= ryml::FLOW_SL;
4237  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"({NodeOne: [{key: a,desc: b,class: c,number: d},{key: e,desc: f,class: g,number: h},{key: i,desc: j,class: k,number: l}]})");
4238 }
@ FLOW_SL
mark container with single-line flow style (seqs as '[val1,val2], maps as '{key: val,...
Definition: node_type.hpp:54

References CHECK, c4::yml::FLOW_SL, c4::yml::parse_in_arena(), and c4::yml::Tree::rootref().

◆ sample_json()

void sample::sample_json ( )

shows how to parse and emit JSON.

Definition at line 4245 of file quickstart.cpp.

4246 {
4247  ryml::csubstr json = R"({
4248  "doe":"a deer, a female deer",
4249  "ray":"a drop of golden sun",
4250  "me":"a name, I call myself",
4251  "far":"a long long way to go"
4252 })";
4253  // Since JSON is a subset of YAML, parsing JSON is just the
4254  // same as YAML:
4255  ryml::Tree tree = ryml::parse_in_arena(json);
4256  // If you are sure the source is valid json, you can use the
4257  // appropriate parse_json overload, which is faster because json
4258  // has a smaller grammar:
4259  ryml::Tree json_tree = ryml::parse_json_in_arena(json);
4260  // to emit JSON, use the proper overload:
4261  CHECK(ryml::emitrs_json<std::string>(tree) == R"({"doe": "a deer, a female deer","ray": "a drop of golden sun","me": "a name, I call myself","far": "a long long way to go"})");
4262  CHECK(ryml::emitrs_json<std::string>(json_tree) == R"({"doe": "a deer, a female deer","ray": "a drop of golden sun","me": "a name, I call myself","far": "a long long way to go"})");
4263  // to emit JSON to a stream:
4264  std::stringstream ss;
4265  ss << ryml::as_json(tree); // <- mark it like this
4266  CHECK(ss.str() == R"({"doe": "a deer, a female deer","ray": "a drop of golden sun","me": "a name, I call myself","far": "a long long way to go"})");
4267  // Note the following limitations:
4268  //
4269  // - YAML streams cannot be emitted as json, and are not
4270  // allowed. But you can work around this by emitting the
4271  // individual documents separately; see the sample_docs()
4272  // below for such an example.
4273  //
4274  // - tags cannot be emitted as json, and are not allowed.
4275  //
4276  // - anchors and references cannot be emitted as json and
4277  // are not allowed.
4278 }
void parse_json_in_arena(Parser *parser, csubstr filename, csubstr json, Tree *t, id_type node_id)
(1) parse JSON into an existing tree node. The filename will be used in any error messages arising du...
Definition: parse.cpp:112

References CHECK, c4::yml::parse_in_arena(), and c4::yml::parse_json_in_arena().

◆ sample_anchors_and_aliases()

void sample::sample_anchors_and_aliases ( )

demonstrates usage with anchors and alias references.

Note that dereferencing is opt-in; after parsing, you have to call Tree::resolve() explicitly if you want resolved references in the tree. This method will resolve all references and substitute the anchored values in place of the reference.

The Tree::resolve() method first does a full traversal of the tree to gather all anchors and references in a separate collection, then it goes through that collection to locate the names, which it does by obeying the YAML standard diktat that

an alias node refers to the most recent node in
the serialization having the specified anchor

So, depending on the number of anchor/alias nodes, this is a potentially expensive operation, with a best-case linear complexity (from the initial traversal) and a worst-case quadratic complexity (if every node has an alias/anchor). This potential cost is the reason for requiring an explicit call to Tree::resolve().

Definition at line 4303 of file quickstart.cpp.

4304 {
4305  std::string unresolved = R"(base: &base
4306  name: Everyone has same name
4307 foo: &foo
4308  <<: *base
4309  age: 10
4310 bar: &bar
4311  <<: *base
4312  age: 20
4313 bill_to: &id001
4314  street: |-
4315  123 Tornado Alley
4316  Suite 16
4317  city: East Centerville
4318  state: KS
4319 ship_to: *id001
4320 &keyref key: &valref val
4321 *valref : *keyref
4322 )";
4323  std::string resolved = R"(base:
4324  name: Everyone has same name
4325 foo:
4326  name: Everyone has same name
4327  age: 10
4328 bar:
4329  name: Everyone has same name
4330  age: 20
4331 bill_to:
4332  street: |-
4333  123 Tornado Alley
4334  Suite 16
4335  city: East Centerville
4336  state: KS
4337 ship_to:
4338  street: |-
4339  123 Tornado Alley
4340  Suite 16
4341  city: East Centerville
4342  state: KS
4343 key: val
4344 val: key
4345 )";
4346 
4347  ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(unresolved));
4348  // by default, references are not resolved when parsing:
4349  CHECK( ! tree["base"].has_key_anchor());
4350  CHECK( tree["base"].has_val_anchor());
4351  CHECK( tree["base"].val_anchor() == "base");
4352  CHECK( tree["key"].key_anchor() == "keyref");
4353  CHECK( tree["key"].val_anchor() == "valref");
4354  CHECK( tree["*valref"].is_key_ref());
4355  CHECK( tree["*valref"].is_val_ref());
4356  CHECK( tree["*valref"].key_ref() == "valref");
4357  CHECK( tree["*valref"].val_ref() == "keyref");
4358 
4359  // to resolve references, simply call tree.resolve(),
4360  // which will perform the reference instantiations:
4361  tree.resolve();
4362 
4363  // all the anchors and references are substistuted and then removed:
4364  CHECK( ! tree["base"].has_key_anchor());
4365  CHECK( ! tree["base"].has_val_anchor());
4366  CHECK( ! tree["base"].has_val_anchor());
4367  CHECK( ! tree["key"].has_key_anchor());
4368  CHECK( ! tree["key"].has_val_anchor());
4369  CHECK( ! tree["val"].is_key_ref()); // notice *valref is now turned to val
4370  CHECK( ! tree["val"].is_val_ref()); // notice *valref is now turned to val
4371 
4372  CHECK(tree["ship_to"]["city"].val() == "East Centerville");
4373  CHECK(tree["ship_to"]["state"].val() == "KS");
4374 }
void resolve(ReferenceResolver *rr)
Resolve references (aliases <- anchors) in the tree.
Definition: tree.cpp:1110

References CHECK, c4::yml::parse_in_arena(), c4::yml::Tree::resolve(), and c4::to_csubstr().

◆ sample_tags()

void sample::sample_tags ( )

Definition at line 4379 of file quickstart.cpp.

4380 {
4381  const std::string yaml = R"(--- !!map
4382 a: 0
4383 b: 1
4384 --- !map
4385 a: b
4386 --- !!seq
4387 - a
4388 - b
4389 --- !!str a b
4390 --- !!str 'a: b'
4391 ---
4392 !!str a: b
4393 --- !!set
4394 ? a
4395 ? b
4396 --- !!set
4397 a:
4398 --- !!seq
4399 - !!int 0
4400 - !!str 1
4401 )";
4402  const ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(yaml));
4403  const ryml::ConstNodeRef root = tree.rootref();
4404  CHECK(root.is_stream());
4405  CHECK(root.num_children() == 9);
4406  for(ryml::ConstNodeRef doc : root.children())
4407  CHECK(doc.is_doc());
4408  // tags are kept verbatim from the source:
4409  CHECK(root[0].has_val_tag());
4410  CHECK(root[0].val_tag() == "!!map"); // valid only if the node has a val tag
4411  CHECK(root[1].val_tag() == "!map");
4412  CHECK(root[2].val_tag() == "!!seq");
4413  CHECK(root[3].val_tag() == "!!str");
4414  CHECK(root[4].val_tag() == "!!str");
4415  CHECK(root[5]["a"].has_key_tag());
4416  CHECK(root[5]["a"].key_tag() == "!!str"); // valid only if the node has a key tag
4417  CHECK(root[6].val_tag() == "!!set");
4418  CHECK(root[7].val_tag() == "!!set");
4419  CHECK(root[8].val_tag() == "!!seq");
4420  CHECK(root[8][0].val_tag() == "!!int");
4421  CHECK(root[8][1].val_tag() == "!!str");
4422  // ryml also provides a complete toolbox to deal with tags.
4423  // there is an enumeration for the standard YAML tags:
4424  CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
4425  CHECK(ryml::to_tag("!!map") == ryml::TAG_MAP);
4426  CHECK(ryml::to_tag("!!seq") == ryml::TAG_SEQ);
4427  CHECK(ryml::to_tag("!!str") == ryml::TAG_STR);
4428  CHECK(ryml::to_tag("!!int") == ryml::TAG_INT);
4429  CHECK(ryml::to_tag("!!set") == ryml::TAG_SET);
4430  // given a tag enum, you can fetch the short tag string:
4432  CHECK(ryml::from_tag(ryml::TAG_MAP) == "!!map");
4433  CHECK(ryml::from_tag(ryml::TAG_SEQ) == "!!seq");
4434  CHECK(ryml::from_tag(ryml::TAG_STR) == "!!str");
4435  CHECK(ryml::from_tag(ryml::TAG_INT) == "!!int");
4436  CHECK(ryml::from_tag(ryml::TAG_SET) == "!!set");
4437  // you can also fetch the long tag string:
4439  CHECK(ryml::from_tag_long(ryml::TAG_MAP) == "<tag:yaml.org,2002:map>");
4440  CHECK(ryml::from_tag_long(ryml::TAG_SEQ) == "<tag:yaml.org,2002:seq>");
4441  CHECK(ryml::from_tag_long(ryml::TAG_STR) == "<tag:yaml.org,2002:str>");
4442  CHECK(ryml::from_tag_long(ryml::TAG_INT) == "<tag:yaml.org,2002:int>");
4443  CHECK(ryml::from_tag_long(ryml::TAG_SET) == "<tag:yaml.org,2002:set>");
4444  // and likewise:
4445  CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
4446  CHECK(ryml::to_tag("<tag:yaml.org,2002:map>") == ryml::TAG_MAP);
4447  CHECK(ryml::to_tag("<tag:yaml.org,2002:seq>") == ryml::TAG_SEQ);
4448  CHECK(ryml::to_tag("<tag:yaml.org,2002:str>") == ryml::TAG_STR);
4449  CHECK(ryml::to_tag("<tag:yaml.org,2002:int>") == ryml::TAG_INT);
4450  CHECK(ryml::to_tag("<tag:yaml.org,2002:set>") == ryml::TAG_SET);
4451  // to normalize a tag as much as possible, use normalize_tag():
4452  CHECK(ryml::normalize_tag("!!map") == "!!map");
4453  CHECK(ryml::normalize_tag("!<tag:yaml.org,2002:map>") == "!!map");
4454  CHECK(ryml::normalize_tag("<tag:yaml.org,2002:map>") == "!!map");
4455  CHECK(ryml::normalize_tag("tag:yaml.org,2002:map") == "!!map");
4456  CHECK(ryml::normalize_tag("!<!!map>") == "<!!map>");
4457  CHECK(ryml::normalize_tag("!map") == "!map");
4458  CHECK(ryml::normalize_tag("!my!foo") == "!my!foo");
4459  // and also for the long form:
4460  CHECK(ryml::normalize_tag_long("!!map") == "<tag:yaml.org,2002:map>");
4461  CHECK(ryml::normalize_tag_long("!<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
4462  CHECK(ryml::normalize_tag_long("<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
4463  CHECK(ryml::normalize_tag_long("tag:yaml.org,2002:map") == "<tag:yaml.org,2002:map>");
4464  CHECK(ryml::normalize_tag_long("!<!!map>") == "<!!map>");
4465  CHECK(ryml::normalize_tag_long("!map") == "!map");
4466  // The tree provides the following methods applying to every node
4467  // with a key and/or val tag:
4468  ryml::Tree normalized_tree = tree;
4469  normalized_tree.normalize_tags(); // normalize all tags in short form
4470  CHECK(ryml::emitrs_yaml<std::string>(normalized_tree) == R"(--- !!map
4471 a: 0
4472 b: 1
4473 --- !map
4474 a: b
4475 --- !!seq
4476 - a
4477 - b
4478 --- !!str a b
4479 --- !!str 'a: b'
4480 ---
4481 !!str a: b
4482 --- !!set
4483 a:
4484 b:
4485 --- !!set
4486 a:
4487 --- !!seq
4488 - !!int 0
4489 - !!str 1
4490 )");
4491  ryml::Tree normalized_tree_long = tree;
4492  normalized_tree_long.normalize_tags_long(); // normalize all tags in short form
4493  CHECK(ryml::emitrs_yaml<std::string>(normalized_tree_long) == R"(--- !<tag:yaml.org,2002:map>
4494 a: 0
4495 b: 1
4496 --- !map
4497 a: b
4498 --- !<tag:yaml.org,2002:seq>
4499 - a
4500 - b
4501 --- !<tag:yaml.org,2002:str> a b
4502 --- !<tag:yaml.org,2002:str> 'a: b'
4503 ---
4504 !<tag:yaml.org,2002:str> a: b
4505 --- !<tag:yaml.org,2002:set>
4506 a:
4507 b:
4508 --- !<tag:yaml.org,2002:set>
4509 a:
4510 --- !<tag:yaml.org,2002:seq>
4511 - !<tag:yaml.org,2002:int> 0
4512 - !<tag:yaml.org,2002:str> 1
4513 )");
4514 }
void normalize_tags()
Definition: tree.cpp:1438
void normalize_tags_long()
Definition: tree.cpp:1445
csubstr from_tag_long(YamlTag_e tag)
Definition: tag.cpp:123
csubstr normalize_tag_long(csubstr tag)
Definition: tag.cpp:31
csubstr normalize_tag(csubstr tag)
Definition: tag.cpp:19
csubstr from_tag(YamlTag_e tag)
Definition: tag.cpp:163
YamlTag_e to_tag(csubstr tag)
Definition: tag.cpp:67
@ TAG_SET
!!set Unordered set of non-equal values.
Definition: tag.hpp:32
@ TAG_INT
!!float Mathematical integers.
Definition: tag.hpp:38
@ TAG_SEQ
!!seq Sequence of arbitrary values.
Definition: tag.hpp:33
@ TAG_NONE
Definition: tag.hpp:27
@ TAG_STR
!!str A sequence of zero or more Unicode characters.
Definition: tag.hpp:41
@ TAG_MAP
!!map Unordered set of key: value pairs without duplicates.
Definition: tag.hpp:29
bool is_stream() const RYML_NOEXCEPT
Forward to Tree::is_stream().
Definition: node.hpp:236

References CHECK, c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::children(), c4::yml::from_tag(), c4::yml::from_tag_long(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::is_stream(), c4::yml::normalize_tag(), c4::yml::normalize_tag_long(), c4::yml::Tree::normalize_tags(), c4::yml::Tree::normalize_tags_long(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::num_children(), c4::yml::parse_in_arena(), c4::yml::Tree::rootref(), c4::yml::TAG_INT, c4::yml::TAG_MAP, c4::yml::TAG_NONE, c4::yml::TAG_SEQ, c4::yml::TAG_SET, c4::yml::TAG_STR, c4::to_csubstr(), and c4::yml::to_tag().

◆ sample_tag_directives()

void sample::sample_tag_directives ( )

Definition at line 4519 of file quickstart.cpp.

4520 {
4521  const std::string yaml = R"(
4522 %TAG !m! !my-
4523 --- # Bulb here
4524 !m!light fluorescent
4525 ...
4526 %TAG !m! !meta-
4527 --- # Color here
4528 !m!light green
4529 )";
4531  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(%TAG !m! !my-
4532 --- !m!light fluorescent
4533 ...
4534 %TAG !m! !meta-
4535 --- !m!light green
4536 )");
4537  // tags are not resolved by default. Use .resolve_tags() to
4538  // accomplish this:
4539  tree.resolve_tags();
4540  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(%TAG !m! !my-
4541 --- !<!my-light> fluorescent
4542 ...
4543 %TAG !m! !meta-
4544 --- !<!meta-light> green
4545 )");
4546  // see also tree.normalize_tags()
4547  // see also tree.normalize_tags_long()
4548 }
void resolve_tags()
Definition: tree.cpp:1428

References CHECK, c4::yml::parse_in_arena(), c4::yml::Tree::resolve_tags(), and c4::to_csubstr().

◆ sample_docs()

void sample::sample_docs ( )

Definition at line 4553 of file quickstart.cpp.

4554 {
4555  std::string yml = R"(---
4556 a: 0
4557 b: 1
4558 ---
4559 c: 2
4560 d: 3
4561 ---
4562 - 4
4563 - 5
4564 - 6
4565 - 7
4566 )";
4568  CHECK(ryml::emitrs_yaml<std::string>(tree) == yml);
4569 
4570  // iteration through docs
4571  {
4572  // using the node API
4573  const ryml::ConstNodeRef stream = tree.rootref();
4574  CHECK(stream.is_root());
4575  CHECK(stream.is_stream());
4576  CHECK(!stream.is_doc());
4577  CHECK(stream.num_children() == 3);
4578  for(const ryml::ConstNodeRef doc : stream.children())
4579  CHECK(doc.is_doc());
4580  CHECK(tree.docref(0).id() == stream.child(0).id());
4581  CHECK(tree.docref(1).id() == stream.child(1).id());
4582  CHECK(tree.docref(2).id() == stream.child(2).id());
4583  // equivalent: using the lower level index API
4584  const ryml::id_type stream_id = tree.root_id();
4585  CHECK(tree.is_root(stream_id));
4586  CHECK(tree.is_stream(stream_id));
4587  CHECK(!tree.is_doc(stream_id));
4588  CHECK(tree.num_children(stream_id) == 3);
4589  for(ryml::id_type doc_id = tree.first_child(stream_id); doc_id != ryml::NONE; doc_id = tree.next_sibling(stream_id))
4590  CHECK(tree.is_doc(doc_id));
4591  CHECK(tree.doc(0) == tree.child(stream_id, 0));
4592  CHECK(tree.doc(1) == tree.child(stream_id, 1));
4593  CHECK(tree.doc(2) == tree.child(stream_id, 2));
4594 
4595  // using the node API
4596  CHECK(stream[0].is_doc());
4597  CHECK(stream[0].is_map());
4598  CHECK(stream[0]["a"].val() == "0");
4599  CHECK(stream[0]["b"].val() == "1");
4600  // equivalent: using the index API
4601  const ryml::id_type doc0_id = tree.first_child(stream_id);
4602  CHECK(tree.is_doc(doc0_id));
4603  CHECK(tree.is_map(doc0_id));
4604  CHECK(tree.val(tree.find_child(doc0_id, "a")) == "0");
4605  CHECK(tree.val(tree.find_child(doc0_id, "b")) == "1");
4606 
4607  // using the node API
4608  CHECK(stream[1].is_doc());
4609  CHECK(stream[1].is_map());
4610  CHECK(stream[1]["c"].val() == "2");
4611  CHECK(stream[1]["d"].val() == "3");
4612  // equivalent: using the index API
4613  const ryml::id_type doc1_id = tree.next_sibling(doc0_id);
4614  CHECK(tree.is_doc(doc1_id));
4615  CHECK(tree.is_map(doc1_id));
4616  CHECK(tree.val(tree.find_child(doc1_id, "c")) == "2");
4617  CHECK(tree.val(tree.find_child(doc1_id, "d")) == "3");
4618 
4619  // using the node API
4620  CHECK(stream[2].is_doc());
4621  CHECK(stream[2].is_seq());
4622  CHECK(stream[2][0].val() == "4");
4623  CHECK(stream[2][1].val() == "5");
4624  CHECK(stream[2][2].val() == "6");
4625  CHECK(stream[2][3].val() == "7");
4626  // equivalent: using the index API
4627  const ryml::id_type doc2_id = tree.next_sibling(doc1_id);
4628  CHECK(tree.is_doc(doc2_id));
4629  CHECK(tree.is_seq(doc2_id));
4630  CHECK(tree.val(tree.child(doc2_id, 0)) == "4");
4631  CHECK(tree.val(tree.child(doc2_id, 1)) == "5");
4632  CHECK(tree.val(tree.child(doc2_id, 2)) == "6");
4633  CHECK(tree.val(tree.child(doc2_id, 3)) == "7");
4634  }
4635 
4636  // Note: since json does not have streams, you cannot emit the above
4637  // tree as json when you start from the root:
4638  //CHECK(ryml::emitrs_json<std::string>(tree) == yml); // RUNTIME ERROR!
4639 
4640  // emitting streams as json is not possible, but
4641  // you can iterate through individual documents and emit
4642  // them separately:
4643  {
4644  const std::string expected_json[] = {
4645  R"({"a": 0,"b": 1})",
4646  R"({"c": 2,"d": 3})",
4647  R"([4,5,6,7])",
4648  };
4649  // using the node API
4650  {
4651  ryml::id_type count = 0;
4652  const ryml::ConstNodeRef stream = tree.rootref();
4653  CHECK(stream.num_children() == (ryml::id_type)C4_COUNTOF(expected_json));
4654  for(ryml::ConstNodeRef doc : stream.children())
4655  CHECK(ryml::emitrs_json<std::string>(doc) == expected_json[count++]);
4656  }
4657  // equivalent: using the index API
4658  {
4659  ryml::id_type count = 0;
4660  const ryml::id_type stream_id = tree.root_id();
4661  CHECK(tree.num_children(stream_id) == (ryml::id_type)C4_COUNTOF(expected_json));
4662  for(ryml::id_type doc_id = tree.first_child(stream_id); doc_id != ryml::NONE; doc_id = tree.next_sibling(doc_id))
4663  CHECK(ryml::emitrs_json<std::string>(tree, doc_id) == expected_json[count++]);
4664  }
4665  }
4666 }
id_type id() const noexcept
Definition: node.hpp:1100
bool is_stream(id_type node) const
Definition: tree.hpp:400
bool is_root(id_type node) const
Definition: tree.hpp:452
NodeRef docref(id_type i)
get the i-th document of the stream
Definition: tree.cpp:68
bool is_doc(id_type node) const
Definition: tree.hpp:401
id_type doc(id_type i) const
gets the i document node index.
Definition: tree.hpp:515
id_type num_children(id_type node) const
O(num_children)
Definition: tree.cpp:1120
id_type child(id_type node, id_type pos) const
Definition: tree.cpp:1128
bool is_root() const RYML_NOEXCEPT
Forward to Tree::is_root().
Definition: node.hpp:305
bool is_doc() const RYML_NOEXCEPT
Forward to Tree::is_doc().
Definition: node.hpp:237
auto child(id_type pos) RYML_NOEXCEPT -> Impl
Forward to Tree::child().
Definition: node.hpp:347

References CHECK, c4::yml::Tree::child(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::child(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::children(), c4::yml::Tree::doc(), c4::yml::Tree::docref(), c4::yml::Tree::find_child(), c4::yml::Tree::first_child(), c4::yml::NodeRef::id(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::is_doc(), c4::yml::Tree::is_doc(), c4::yml::Tree::is_map(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::is_root(), c4::yml::Tree::is_root(), c4::yml::Tree::is_seq(), c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::is_stream(), c4::yml::Tree::is_stream(), c4::yml::Tree::next_sibling(), c4::yml::NONE, c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::num_children(), c4::yml::Tree::num_children(), c4::yml::parse_in_place(), c4::yml::Tree::root_id(), c4::yml::Tree::rootref(), c4::to_substr(), and c4::yml::Tree::val().

◆ sample_error_handler()

void sample::sample_error_handler ( )

demonstrates how to set a custom error handler for ryml

Definition at line 4689 of file quickstart.cpp.

4690 {
4691  ErrorHandlerExample errh;
4692 
4693  // set a global error handler. Note the error callback must never
4694  // return: it must either throw an exception, use setjmp() and
4695  // longjmp(), or abort. Otherwise, the parser will enter into an
4696  // infinite loop, or the program may crash.
4697  ryml::set_callbacks(errh.callbacks());
4698  errh.check_effect(/*committed*/true);
4699  CHECK(errh.check_error_occurs([&]{
4700  ryml::Tree tree = ryml::parse_in_arena("errorhandler.yml", "[a: b\n}");
4701  }));
4702  ryml::set_callbacks(errh.defaults); // restore defaults.
4703  errh.check_effect(/*committed*/false);
4704 }

References sample::ErrorHandlerExample::callbacks(), CHECK, sample::ErrorHandlerExample::check_effect(), sample::ErrorHandlerExample::check_error_occurs(), sample::ErrorHandlerExample::defaults, and c4::yml::set_callbacks().

◆ sample_global_allocator()

void sample::sample_global_allocator ( )

demonstrates how to set the global allocator for ryml

Definition at line 4801 of file quickstart.cpp.

4802 {
4803  GlobalAllocatorExample mem;
4804 
4805  // save the existing callbacks for restoring
4806  ryml::Callbacks defaults = ryml::get_callbacks();
4807 
4808  // set to our callbacks
4809  ryml::set_callbacks(mem.callbacks());
4810 
4811  // verify that the allocator is in effect
4812  ryml::Callbacks const& current = ryml::get_callbacks();
4813  CHECK(current.m_allocate == &mem.s_allocate);
4814  CHECK(current.m_free == &mem.s_free);
4815 
4816  // so far nothing was allocated
4817  CHECK(mem.alloc_size == 0);
4818 
4819  // parse one tree and check
4820  (void)ryml::parse_in_arena(R"({foo: bar})");
4821  mem.check_and_reset();
4822 
4823  // parse another tree and check
4824  (void)ryml::parse_in_arena(R"([a, b, c, d, {foo: bar, money: pennys}])");
4825  mem.check_and_reset();
4826 
4827  // verify that by reserving we save allocations
4828  {
4829  ryml::EventHandlerTree evt_handler;
4830  ryml::Parser parser(&evt_handler); // reuse a parser
4831  ryml::Tree tree; // reuse a tree
4832 
4833  tree.reserve(10); // reserve the number of nodes
4834  tree.reserve_arena(100); // reserve the arena size
4835  parser.reserve_stack(10); // reserve the parser depth.
4836 
4837  // since the parser stack uses Small Storage Optimization,
4838  // allocations will only happen with capacities higher than 16.
4839  CHECK(mem.num_allocs == 2); // tree, tree_arena and NOT the parser
4840 
4841  parser.reserve_stack(20); // reserve the parser depth.
4842  CHECK(mem.num_allocs == 3); // tree, tree_arena and now the parser as well
4843 
4844  // verify that no other allocations occur when parsing
4845  size_t size_before = mem.alloc_size;
4846  parse_in_arena(&parser, "", R"([a, b, c, d, {foo: bar, money: pennys}])", &tree);
4847  CHECK(mem.alloc_size == size_before);
4848  CHECK(mem.num_allocs == 3);
4849  }
4850  mem.check_and_reset();
4851 
4852  // restore defaults.
4853  ryml::set_callbacks(defaults);
4854 }

References sample::GlobalAllocatorExample::alloc_size, sample::GlobalAllocatorExample::callbacks(), CHECK, sample::GlobalAllocatorExample::check_and_reset(), c4::yml::get_callbacks(), c4::yml::Callbacks::m_allocate, c4::yml::Callbacks::m_free, sample::GlobalAllocatorExample::num_allocs, c4::yml::parse_in_arena(), c4::yml::Tree::reserve(), c4::yml::Tree::reserve_arena(), c4::yml::ParseEngine< EventHandler >::reserve_stack(), sample::GlobalAllocatorExample::s_allocate(), sample::GlobalAllocatorExample::s_free(), and c4::yml::set_callbacks().

◆ sample_per_tree_allocator()

void sample::sample_per_tree_allocator ( )

Definition at line 4924 of file quickstart.cpp.

4925 {
4926  PerTreeMemoryExample mrp;
4927  PerTreeMemoryExample mr1;
4928  PerTreeMemoryExample mr2;
4929 
4930  // the trees will use the memory in the resources above,
4931  // with each tree using a separate resource
4932  {
4933  // Watchout: ensure that the lifetime of the callbacks target
4934  // exceeds the lifetime of the tree.
4935  ryml::EventHandlerTree evt_handler(mrp.callbacks());
4936  ryml::Parser parser(&evt_handler);
4937  ryml::Tree tree1(mr1.callbacks());
4938  ryml::Tree tree2(mr2.callbacks());
4939 
4940  ryml::csubstr yml1 = "{a: b}";
4941  ryml::csubstr yml2 = "{c: d, e: f, g: [h, i, 0, 1, 2, 3]}";
4942 
4943  parse_in_arena(&parser, "file1.yml", yml1, &tree1);
4944  parse_in_arena(&parser, "file2.yml", yml2, &tree2);
4945  }
4946 
4947  CHECK(mrp.num_allocs == 0); // YAML depth not large enough to warrant a parser allocation
4948  CHECK(mr1.alloc_size <= mr2.alloc_size); // because yml2 has more nodes
4949 }

References sample::PerTreeMemoryExample::alloc_size, sample::PerTreeMemoryExample::callbacks(), CHECK, sample::PerTreeMemoryExample::num_allocs, and c4::yml::parse_in_arena().

◆ sample_static_trees()

void sample::sample_static_trees ( )

shows how to work around the static initialization order fiasco when using a static-duration ryml tree

See also
https://en.cppreference.com/w/cpp/language/siof

Definition at line 4958 of file quickstart.cpp.

4959 {
4960  // Using static trees incurs may incur a static initialization
4961  // order problem. This happens because a default-constructed tree will
4962  // obtain the callbacks from the current global setting, which may
4963  // not have been initialized due to undefined static initialization
4964  // order:
4965  //
4966  //static ryml::Tree tree; // ERROR! depends on ryml::get_callbacks() which may not have been initialized.
4967  //
4968  // To work around the issue, declare static callbacks
4969  // to explicitly initialize the static tree:
4970  static ryml::Callbacks callbacks = {}; // use default callback members
4971  static ryml::Tree tree(callbacks); // OK
4972  // now you can use the tree as normal:
4973  ryml::parse_in_arena(R"(doe: "a deer, a female deer")", &tree);
4974  CHECK(tree["doe"].val() == "a deer, a female deer");
4975 }

References CHECK, and c4::yml::parse_in_arena().

◆ sample_location_tracking()

void sample::sample_location_tracking ( )

demonstrates how to obtain the (zero-based) location of a node from a recently parsed tree

Definition at line 4983 of file quickstart.cpp.

4984 {
4985  // NOTE: locations are zero-based. If you intend to show the
4986  // location to a human user, you may want to pre-increment the line
4987  // and column by 1.
4988  ryml::csubstr yaml = R"({
4989 aa: contents,
4990 foo: [one, [two, three]]
4991 })";
4992  // A parser is needed to track locations, and it has to be
4993  // explicitly set to do it. Location tracking is disabled by
4994  // default.
4995  ryml::ParserOptions opts = {};
4996  opts.locations(true); // enable locations, default is false
4997  ryml::EventHandlerTree evt_handler = {};
4998  ryml::Parser parser(&evt_handler, opts);
4999  CHECK(parser.options().locations());
5000  // When locations are enabled, the first task while parsing will
5001  // consist of building and caching (in the parser) a
5002  // source-to-node lookup structure to accelerate location lookups.
5003  //
5004  // The cost of building the location accelerator is linear in the
5005  // size of the source buffer. This increased cost is the reason
5006  // for the opt-in requirement. When locations are disabled there
5007  // is no cost.
5008  //
5009  // Building the location accelerator may trigger an allocation,
5010  // but this can and should be avoided by reserving prior to
5011  // parsing:
5012  parser.reserve_locations(50u); // reserve for 50 lines
5013  // Now the structure will be built during parsing:
5014  ryml::Tree tree = parse_in_arena(&parser, "source.yml", yaml);
5015  // After this, we are ready to query the location from the parser:
5016  ryml::Location loc = parser.location(tree.rootref());
5017  // As for the complexity of the query: for large buffers it is
5018  // O(log(numlines)). For short source buffers (30 lines and less),
5019  // it is O(numlines), as a plain linear search is faster in this
5020  // case.
5021  CHECK(parser.location_contents(loc).begins_with("{"));
5022  CHECK(loc.offset == 0u);
5023  CHECK(loc.line == 0u);
5024  CHECK(loc.col == 0u);
5025  // on the next call, we only pay O(log(numlines)) because the
5026  // rebuild is already available:
5027  loc = parser.location(tree["aa"]);
5028  CHECK(parser.location_contents(loc).begins_with("aa"));
5029  CHECK(loc.offset == 2u);
5030  CHECK(loc.line == 1u);
5031  CHECK(loc.col == 0u);
5032  // KEYSEQ in flow style: points at the key
5033  loc = parser.location(tree["foo"]);
5034  CHECK(parser.location_contents(loc).begins_with("foo"));
5035  CHECK(loc.offset == 16u);
5036  CHECK(loc.line == 2u);
5037  CHECK(loc.col == 0u);
5038  loc = parser.location(tree["foo"][0]);
5039  CHECK(parser.location_contents(loc).begins_with("one"));
5040  CHECK(loc.line == 2u);
5041  CHECK(loc.col == 6u);
5042  // SEQ in flow style: location points at the opening '[' (there's no key)
5043  loc = parser.location(tree["foo"][1]);
5044  CHECK(parser.location_contents(loc).begins_with("["));
5045  CHECK(loc.line == 2u);
5046  CHECK(loc.col == 11u);
5047  loc = parser.location(tree["foo"][1][0]);
5048  CHECK(parser.location_contents(loc).begins_with("two"));
5049  CHECK(loc.line == 2u);
5050  CHECK(loc.col == 12u);
5051  loc = parser.location(tree["foo"][1][1]);
5052  CHECK(parser.location_contents(loc).begins_with("three"));
5053  CHECK(loc.line == 2u);
5054  CHECK(loc.col == 17u);
5055  // NOTE. The parser locations always point at the latest buffer to
5056  // be parsed with the parser object, so they must be queried using
5057  // the corresponding latest tree to be parsed. This means that if
5058  // the parser is reused, earlier trees will loose the possibility
5059  // of querying for location. It is undefined behavior to query the
5060  // parser for the location of a node from an earlier tree:
5061  ryml::Tree docval = parse_in_arena(&parser, "docval.yaml", "this is a docval");
5062  // From now on, none of the locations from the previous tree can
5063  // be queried:
5064  //loc = parser.location(tree.rootref()); // ERROR, undefined behavior
5065  loc = parser.location(docval.rootref()); // OK. this is the latest tree from this parser
5066  CHECK(parser.location_contents(loc).begins_with("this is a docval"));
5067  CHECK(loc.line == 0u);
5068  CHECK(loc.col == 0u);
5069 
5070  // NOTES ABOUT CONTAINER LOCATIONS
5071  ryml::Tree tree2 = parse_in_arena(&parser, "containers.yaml", R"(
5072 a new: buffer
5073 to: be parsed
5074 map with key:
5075  first: value
5076  second: value
5077 seq with key:
5078  - first value
5079  - second value
5080  -
5081  - nested first value
5082  - nested second value
5083  -
5084  nested first: value
5085  nested second: value
5086 )");
5087  // (Likewise, the docval tree can no longer be used to query.)
5088  //
5089  // For key-less block-style maps, the location of the container
5090  // points at the first child's key. For example, in this case
5091  // the root does not have a key, so its location is taken
5092  // to be at the first child:
5093  loc = parser.location(tree2.rootref());
5094  CHECK(parser.location_contents(loc).begins_with("a new"));
5095  CHECK(loc.offset == 1u);
5096  CHECK(loc.line == 1u);
5097  CHECK(loc.col == 0u);
5098  // note the first child points exactly at the same place:
5099  loc = parser.location(tree2["a new"]);
5100  CHECK(parser.location_contents(loc).begins_with("a new"));
5101  CHECK(loc.offset == 1u);
5102  CHECK(loc.line == 1u);
5103  CHECK(loc.col == 0u);
5104  loc = parser.location(tree2["to"]);
5105  CHECK(parser.location_contents(loc).begins_with("to"));
5106  CHECK(loc.line == 2u);
5107  CHECK(loc.col == 0u);
5108  // but of course, if the block-style map is a KEYMAP, then the
5109  // location is the map's key, and not the first child's key:
5110  loc = parser.location(tree2["map with key"]);
5111  CHECK(parser.location_contents(loc).begins_with("map with key"));
5112  CHECK(loc.line == 3u);
5113  CHECK(loc.col == 0u);
5114  loc = parser.location(tree2["map with key"]["first"]);
5115  CHECK(parser.location_contents(loc).begins_with("first"));
5116  CHECK(loc.line == 4u);
5117  CHECK(loc.col == 2u);
5118  loc = parser.location(tree2["map with key"]["second"]);
5119  CHECK(parser.location_contents(loc).begins_with("second"));
5120  CHECK(loc.line == 5u);
5121  CHECK(loc.col == 2u);
5122  // same thing for KEYSEQ:
5123  loc = parser.location(tree2["seq with key"]);
5124  CHECK(parser.location_contents(loc).begins_with("seq with key"));
5125  CHECK(loc.line == 6u);
5126  CHECK(loc.col == 0u);
5127  loc = parser.location(tree2["seq with key"][0]);
5128  CHECK(parser.location_contents(loc).begins_with("first value"));
5129  CHECK(loc.line == 7u);
5130  CHECK(loc.col == 4u);
5131  loc = parser.location(tree2["seq with key"][1]);
5132  CHECK(parser.location_contents(loc).begins_with("second value"));
5133  CHECK(loc.line == 8u);
5134  CHECK(loc.col == 4u);
5135  // SEQ nested in SEQ: container location points at the first child's "- " dash
5136  loc = parser.location(tree2["seq with key"][2]);
5137  CHECK(parser.location_contents(loc).begins_with("- nested first value"));
5138  CHECK(loc.line == 10u);
5139  CHECK(loc.col == 4u);
5140  loc = parser.location(tree2["seq with key"][2][0]);
5141  CHECK(parser.location_contents(loc).begins_with("nested first value"));
5142  CHECK(loc.line == 10u);
5143  CHECK(loc.col == 6u);
5144  // MAP nested in SEQ: same as above: point to key
5145  loc = parser.location(tree2["seq with key"][3]);
5146  CHECK(parser.location_contents(loc).begins_with("nested first: "));
5147  CHECK(loc.line == 13u);
5148  CHECK(loc.col == 4u);
5149  loc = parser.location(tree2["seq with key"][3][0]);
5150  CHECK(parser.location_contents(loc).begins_with("nested first: "));
5151  CHECK(loc.line == 13u);
5152  CHECK(loc.col == 4u);
5153 }
size_t offset
number of bytes from the beginning of the source buffer
Definition: common.hpp:298
ParserOptions & locations(bool enabled) noexcept
enable/disable source location tracking

References CHECK, c4::yml::Location::col, c4::yml::Location::line, c4::yml::ParseEngine< EventHandler >::location(), c4::yml::ParseEngine< EventHandler >::location_contents(), c4::yml::ParserOptions::locations(), c4::yml::Location::offset, c4::yml::ParseEngine< EventHandler >::options(), c4::yml::parse_in_arena(), c4::yml::ParseEngine< EventHandler >::reserve_locations(), and c4::yml::Tree::rootref().

Variable Documentation

◆ defaults

ryml::Callbacks sample::ErrorHandlerExample::defaults