rapidyaml  0.6.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.6.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 5249 of file quickstart.cpp.

5250 {
5251  return ryml::Callbacks(this, nullptr, nullptr, ErrorHandlerExample::s_error);
5252 }
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 5254 of file quickstart.cpp.

5255 {
5256  ryml::Callbacks const& current = ryml::get_callbacks();
5257  if(committed)
5258  {
5259  CHECK((ryml::pfn_error)current.m_error == &s_error);
5260  }
5261  else
5262  {
5263  CHECK((ryml::pfn_error)current.m_error != &s_error);
5264  }
5265  CHECK(current.m_allocate == defaults.m_allocate);
5266  CHECK(current.m_free == defaults.m_free);
5267 }
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:230
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 251 of file quickstart.cpp.

◆ ScopedErrorHandlerExample()

sample::ScopedErrorHandlerExample::ScopedErrorHandlerExample ( )
inline

Definition at line 257 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 258 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 270 of file quickstart.cpp.

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

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

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::NodeRef::invalid(), c4::yml::ConstNodeRef::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::NodeRef::readable(), c4::yml::ConstNodeRef::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 932 of file quickstart.cpp.

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

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

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

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

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 1880 of file quickstart.cpp.

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

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

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 2059 of file quickstart.cpp.

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

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 2117 of file quickstart.cpp.

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

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 2183 of file quickstart.cpp.

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

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

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 2429 of file quickstart.cpp.

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

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 2494 of file quickstart.cpp.

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

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

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

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

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 3671 of file quickstart.cpp.

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

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 3732 of file quickstart.cpp.

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

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 3796 of file quickstart.cpp.

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

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 3929 of file quickstart.cpp.

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

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 4050 of file quickstart.cpp.

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

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 4114 of file quickstart.cpp.

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

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 4147 of file quickstart.cpp.

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

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 4189 of file quickstart.cpp.

4190 {
4191  ryml::Tree tree = ryml::parse_in_arena(R"(
4192 NodeOne:
4193  - key: a
4194  desc: b
4195  class: c
4196  number: d
4197  - key: e
4198  desc: f
4199  class: g
4200  number: h
4201  - key: i
4202  desc: j
4203  class: k
4204  number: l
4205 )");
4206  // ryml uses block style by default:
4207  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(NodeOne:
4208  - key: a
4209  desc: b
4210  class: c
4211  number: d
4212  - key: e
4213  desc: f
4214  class: g
4215  number: h
4216  - key: i
4217  desc: j
4218  class: k
4219  number: l
4220 )");
4221  // you can override the emit style of individual nodes:
4222  for(ryml::NodeRef child : tree["NodeOne"].children())
4223  child |= ryml::FLOW_SL; // flow, single-line
4224  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(NodeOne:
4225  - {key: a,desc: b,class: c,number: d}
4226  - {key: e,desc: f,class: g,number: h}
4227  - {key: i,desc: j,class: k,number: l}
4228 )");
4229  tree["NodeOne"] |= ryml::FLOW_SL;
4230  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}]
4231 )");
4232  tree.rootref() |= ryml::FLOW_SL;
4233  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}]})");
4234 }
@ 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 4241 of file quickstart.cpp.

4242 {
4243  ryml::csubstr json = R"({
4244  "doe":"a deer, a female deer",
4245  "ray":"a drop of golden sun",
4246  "me":"a name, I call myself",
4247  "far":"a long long way to go"
4248 })";
4249  // Since JSON is a subset of YAML, parsing JSON is just the
4250  // same as YAML:
4251  ryml::Tree tree = ryml::parse_in_arena(json);
4252  // If you are sure the source is valid json, you can use the
4253  // appropriate parse_json overload, which is faster because json
4254  // has a smaller grammar:
4255  ryml::Tree json_tree = ryml::parse_json_in_arena(json);
4256  // to emit JSON, use the proper overload:
4257  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"})");
4258  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"})");
4259  // to emit JSON to a stream:
4260  std::stringstream ss;
4261  ss << ryml::as_json(tree); // <- mark it like this
4262  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"})");
4263  // Note the following limitations:
4264  //
4265  // - YAML streams cannot be emitted as json, and are not
4266  // allowed. But you can work around this by emitting the
4267  // individual documents separately; see the sample_docs()
4268  // below for such an example.
4269  //
4270  // - tags cannot be emitted as json, and are not allowed.
4271  //
4272  // - anchors and references cannot be emitted as json and
4273  // are not allowed.
4274 }
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 4299 of file quickstart.cpp.

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

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

◆ sample_tags()

void sample::sample_tags ( )

Definition at line 4375 of file quickstart.cpp.

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

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 4515 of file quickstart.cpp.

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

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 4549 of file quickstart.cpp.

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

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 4685 of file quickstart.cpp.

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

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 4797 of file quickstart.cpp.

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

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 4920 of file quickstart.cpp.

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

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 4954 of file quickstart.cpp.

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

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 4979 of file quickstart.cpp.

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