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

Example code for every feature. More...

Modules

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

Functions

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

Variables

ryml::Callbacks sample::ErrorHandlerExample::defaults
 

Detailed Description

Example code for every feature.

This file does a quick tour of ryml.

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

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

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

Happy ryml'ing!

Some guidance on building

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

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

cmake_minimum_required(VERSION 3.13)
project(ryml-quickstart LANGUAGES CXX)
include(FetchContent)
FetchContent_Declare(ryml
GIT_REPOSITORY https://github.com/biojppm/rapidyaml.git
GIT_TAG v0.7.1
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 5281 of file quickstart.cpp.

5282 {
5283  return ryml::Callbacks(this, nullptr, nullptr, ErrorHandlerExample::s_error);
5284 }
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(), sample::sample_fundamental_types(), 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 5286 of file quickstart.cpp.

5287 {
5288  ryml::Callbacks const& current = ryml::get_callbacks();
5289  if(committed)
5290  {
5291  CHECK((ryml::pfn_error)current.m_error == &s_error);
5292  }
5293  else
5294  {
5295  CHECK((ryml::pfn_error)current.m_error != &s_error);
5296  }
5297  CHECK(current.m_allocate == defaults.m_allocate);
5298  CHECK(current.m_free == defaults.m_free);
5299 }
void(*)(const char *msg, size_t msg_len, Location location, void *user_data) pfn_error
the type of the function used to report errors
Definition: common.hpp:359
Callbacks const & get_callbacks()
get the global callbacks
Definition: common.cpp:118
#define CHECK(predicate)
Definition: quickstart.cpp:229
pfn_allocate m_allocate
Definition: common.hpp:377
pfn_free m_free
Definition: common.hpp:378
pfn_error m_error
Definition: common.hpp:379

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

Referenced by sample::sample_error_handler().

◆ ErrorHandlerExample()

sample::ErrorHandlerExample::ErrorHandlerExample ( )
inline

Definition at line 250 of file quickstart.cpp.

◆ ScopedErrorHandlerExample()

sample::ScopedErrorHandlerExample::ScopedErrorHandlerExample ( )
inline

Definition at line 256 of file quickstart.cpp.

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

References c4::yml::set_callbacks().

◆ ~ScopedErrorHandlerExample()

sample::ScopedErrorHandlerExample::~ScopedErrorHandlerExample ( )
inline

Definition at line 257 of file quickstart.cpp.

References c4::yml::set_callbacks().

◆ sample_lightning_overview()

void sample::sample_lightning_overview ( )

a lightning tour over most features see sample_quick_overview

Definition at line 269 of file quickstart.cpp.

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

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

◆ sample_quick_overview()

void sample::sample_quick_overview ( )

a brief tour over most features

Definition at line 317 of file quickstart.cpp.

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

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

◆ sample_substr()

void sample::sample_substr ( )

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

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

See also
Substring: read/write string views

Definition at line 936 of file quickstart.cpp.

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

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

◆ sample_parse_file()

void sample::sample_parse_file ( )

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

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

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

See also
Parse utilities

Definition at line 1743 of file quickstart.cpp.

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

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

◆ sample_parse_in_place()

void sample::sample_parse_in_place ( )

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

See also
Parse utilities

Definition at line 1784 of file quickstart.cpp.

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

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

◆ sample_parse_in_arena()

void sample::sample_parse_in_arena ( )

demonstrate parsing of a read-only YAML source buffer

See also
Parse utilities

Definition at line 1838 of file quickstart.cpp.

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

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

◆ sample_parse_reuse_tree()

void sample::sample_parse_reuse_tree ( )

demonstrate reuse/modification of tree when parsing

See also
Parse utilities

Definition at line 1884 of file quickstart.cpp.

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

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

◆ sample_parse_reuse_parser()

void sample::sample_parse_reuse_parser ( )

Demonstrates reuse of an existing parser.

Doing this is recommended when multiple files are parsed.

See also
Parse utilities

Definition at line 2036 of file quickstart.cpp.

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

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

◆ sample_parse_reuse_tree_and_parser()

void sample::sample_parse_reuse_tree_and_parser ( )

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

See also
Parse utilities

Definition at line 2063 of file quickstart.cpp.

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

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

◆ sample_iterate_trees()

void sample::sample_iterate_trees ( )

shows how to programatically iterate through trees

See also
Tree utilities
Node classes

Definition at line 2121 of file quickstart.cpp.

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

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

◆ sample_create_trees()

void sample::sample_create_trees ( )

shows how to programatically create trees

See also
Tree utilities
Node classes

Definition at line 2187 of file quickstart.cpp.

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

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

◆ sample_tree_arena()

void sample::sample_tree_arena ( )

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

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

Definition at line 2268 of file quickstart.cpp.

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

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

◆ sample_fundamental_types()

void sample::sample_fundamental_types ( )

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

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

Definition at line 2433 of file quickstart.cpp.

2434 {
2435  ryml::Tree tree;
2436  CHECK(tree.arena().empty());
2437  CHECK(tree.to_arena('a') == "a"); CHECK(tree.arena() == "a");
2438  CHECK(tree.to_arena("bcde") == "bcde"); CHECK(tree.arena() == "abcde");
2439  CHECK(tree.to_arena(unsigned(0)) == "0"); CHECK(tree.arena() == "abcde0");
2440  CHECK(tree.to_arena(int(1)) == "1"); CHECK(tree.arena() == "abcde01");
2441  CHECK(tree.to_arena(uint8_t(0)) == "0"); CHECK(tree.arena() == "abcde010");
2442  CHECK(tree.to_arena(uint16_t(1)) == "1"); CHECK(tree.arena() == "abcde0101");
2443  CHECK(tree.to_arena(uint32_t(2)) == "2"); CHECK(tree.arena() == "abcde01012");
2444  CHECK(tree.to_arena(uint64_t(3)) == "3"); CHECK(tree.arena() == "abcde010123");
2445  CHECK(tree.to_arena(int8_t( 4)) == "4"); CHECK(tree.arena() == "abcde0101234");
2446  CHECK(tree.to_arena(int8_t(-4)) == "-4"); CHECK(tree.arena() == "abcde0101234-4");
2447  CHECK(tree.to_arena(int16_t( 5)) == "5"); CHECK(tree.arena() == "abcde0101234-45");
2448  CHECK(tree.to_arena(int16_t(-5)) == "-5"); CHECK(tree.arena() == "abcde0101234-45-5");
2449  CHECK(tree.to_arena(int32_t( 6)) == "6"); CHECK(tree.arena() == "abcde0101234-45-56");
2450  CHECK(tree.to_arena(int32_t(-6)) == "-6"); CHECK(tree.arena() == "abcde0101234-45-56-6");
2451  CHECK(tree.to_arena(int64_t( 7)) == "7"); CHECK(tree.arena() == "abcde0101234-45-56-67");
2452  CHECK(tree.to_arena(int64_t(-7)) == "-7"); CHECK(tree.arena() == "abcde0101234-45-56-67-7");
2453  CHECK(tree.to_arena((void*)1) == "0x1"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x1");
2454  CHECK(tree.to_arena(float(0.124)) == "0.124"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.124");
2455  CHECK(tree.to_arena(double(0.234)) == "0.234"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.234");
2456 
2457  // write boolean values - see also sample_formatting()
2458  CHECK(tree.to_arena(bool(true)) == "1"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.2341");
2459  CHECK(tree.to_arena(bool(false)) == "0"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410");
2460  CHECK(tree.to_arena(c4::fmt::boolalpha(true)) == "true"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410true");
2461  CHECK(tree.to_arena(c4::fmt::boolalpha(false)) == "false"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse");
2462 
2463  // write special float values
2464  // see also sample_float_precision()
2465  const float fnan = std::numeric_limits<float >::quiet_NaN();
2466  const double dnan = std::numeric_limits<double>::quiet_NaN();
2467  const float finf = std::numeric_limits<float >::infinity();
2468  const double dinf = std::numeric_limits<double>::infinity();
2469  CHECK(tree.to_arena( finf) == ".inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf");
2470  CHECK(tree.to_arena( dinf) == ".inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf");
2471  CHECK(tree.to_arena(-finf) == "-.inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf");
2472  CHECK(tree.to_arena(-dinf) == "-.inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf");
2473  CHECK(tree.to_arena( fnan) == ".nan"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf.nan");
2474  CHECK(tree.to_arena( dnan) == ".nan"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf.nan.nan");
2475 
2476  // read special float values
2477  // see also sample_float_precision()
2478  C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wfloat-equal");
2479  tree = ryml::parse_in_arena(R"({ninf: -.inf, pinf: .inf, nan: .nan})");
2480  float f = 0.f;
2481  double d = 0.;
2482  CHECK(f == 0.f);
2483  CHECK(d == 0.);
2484  tree["ninf"] >> f; CHECK(f == -finf);
2485  tree["ninf"] >> d; CHECK(d == -dinf);
2486  tree["pinf"] >> f; CHECK(f == finf);
2487  tree["pinf"] >> d; CHECK(d == dinf);
2488  tree["nan" ] >> f; CHECK(std::isnan(f));
2489  tree["nan" ] >> d; CHECK(std::isnan(d));
2490  C4_SUPPRESS_WARNING_GCC_CLANG_POP
2491 
2492  // value overflow detection:
2493  // (for integral types only)
2494  {
2495  // we will be detecting errors below, so we use this sample helper
2496  ScopedErrorHandlerExample err = {};
2497  ryml::Tree t(err.callbacks()); // instantiate with the error-detecting callbacks
2498  // create a simple tree with an int value
2499  ryml::parse_in_arena(R"({val: 258})", &t);
2500  // by default, overflow is not detected:
2501  uint8_t valu8 = 0;
2502  int8_t vali8 = 0;
2503  t["val"] >> valu8; CHECK(valu8 == 2); // not 257; it wrapped around
2504  t["val"] >> vali8; CHECK(vali8 == 2); // not 257; it wrapped around
2505  // ...but there are facilities to detect overflow
2506  CHECK(ryml::overflows<uint8_t>(t["val"].val()));
2507  CHECK(ryml::overflows<int8_t>(t["val"].val()));
2508  CHECK( ! ryml::overflows<int16_t>(t["val"].val()));
2509  // and there is a format helper
2510  CHECK(err.check_error_occurs([&]{
2511  auto checku8 = ryml::fmt::overflow_checked(valu8); // need to declare the wrapper type before using it with >>
2512  t["val"] >> checku8; // this will cause an error
2513  }));
2514  CHECK(err.check_error_occurs([&]{
2515  auto checki8 = ryml::fmt::overflow_checked(vali8); // need to declare the wrapper type before using it with >>
2516  t["val"] >> checki8; // this will cause an error
2517  }));
2518  }
2519 }

References c4::yml::Tree::arena(), c4::fmt::boolalpha(), sample::ErrorHandlerExample::callbacks(), CHECK, sample::ErrorHandlerExample::check_error_occurs(), 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 2526 of file quickstart.cpp.

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

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

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

3508 {
3509  ryml::Tree t;
3510 
3511  auto r = t.rootref();
3512  r |= ryml::MAP;
3513 
3514  vec2<int> v2in{10, 11};
3515  vec2<int> v2out{1, 2};
3516  r["v2"] << v2in; // serializes to the tree's arena, and then sets the keyval
3517  r["v2"] >> v2out;
3518  CHECK(v2in.x == v2out.x);
3519  CHECK(v2in.y == v2out.y);
3520  vec3<int> v3in{100, 101, 102};
3521  vec3<int> v3out{1, 2, 3};
3522  r["v3"] << v3in; // serializes to the tree's arena, and then sets the keyval
3523  r["v3"] >> v3out;
3524  CHECK(v3in.x == v3out.x);
3525  CHECK(v3in.y == v3out.y);
3526  CHECK(v3in.z == v3out.z);
3527  vec4<int> v4in{1000, 1001, 1002, 1003};
3528  vec4<int> v4out{1, 2, 3, 4};
3529  r["v4"] << v4in; // serializes to the tree's arena, and then sets the keyval
3530  r["v4"] >> v4out;
3531  CHECK(v4in.x == v4out.x);
3532  CHECK(v4in.y == v4out.y);
3533  CHECK(v4in.z == v4out.z);
3534  CHECK(v4in.w == v4out.w);
3535  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(v2: '(10,11)'
3536 v3: '(100,101,102)'
3537 v4: '(1000,1001,1002,1003)'
3538 )");
3539 
3540  // note that only the used functions are needed:
3541  // - if a type is only parsed, then only from_chars() is needed
3542  // - if a type is only emitted, then only to_chars() is needed
3543  emit_only_vec2<int> eov2in{20, 21}; // only has to_chars()
3544  parse_only_vec2<int> pov2out{1, 2}; // only has from_chars()
3545  r["v2"] << eov2in; // serializes to the tree's arena, and then sets the keyval
3546  r["v2"] >> pov2out;
3547  CHECK(eov2in.x == pov2out.x);
3548  CHECK(eov2in.y == pov2out.y);
3549  emit_only_vec3<int> eov3in{30, 31, 32}; // only has to_chars()
3550  parse_only_vec3<int> pov3out{1, 2, 3}; // only has from_chars()
3551  r["v3"] << eov3in; // serializes to the tree's arena, and then sets the keyval
3552  r["v3"] >> pov3out;
3553  CHECK(eov3in.x == pov3out.x);
3554  CHECK(eov3in.y == pov3out.y);
3555  CHECK(eov3in.z == pov3out.z);
3556  emit_only_vec4<int> eov4in{40, 41, 42, 43}; // only has to_chars()
3557  parse_only_vec4<int> pov4out{1, 2, 3, 4}; // only has from_chars()
3558  r["v4"] << eov4in; // serializes to the tree's arena, and then sets the keyval
3559  r["v4"] >> pov4out;
3560  CHECK(eov4in.x == pov4out.x);
3561  CHECK(eov4in.y == pov4out.y);
3562  CHECK(eov4in.z == pov4out.z);
3563  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(v2: '(20,21)'
3564 v3: '(30,31,32)'
3565 v4: '(40,41,42,43)'
3566 )");
3567 }

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

3704 {
3705  my_type mt_in{
3706  {20, 21},
3707  {30, 31, 32},
3708  {40, 41, 42, 43},
3709  {{101, 102, 103, 104, 105, 106, 107}},
3710  {{{1001, 2001}, {1002, 2002}, {1003, 2003}}},
3711  };
3712  my_type mt_out;
3713 
3714  ryml::Tree t;
3715  t.rootref() << mt_in; // read from this
3716  t.crootref() >> mt_out; // assign here
3717  CHECK(mt_out.v2.x == mt_in.v2.x);
3718  CHECK(mt_out.v2.y == mt_in.v2.y);
3719  CHECK(mt_out.v3.x == mt_in.v3.x);
3720  CHECK(mt_out.v3.y == mt_in.v3.y);
3721  CHECK(mt_out.v3.z == mt_in.v3.z);
3722  CHECK(mt_out.v4.x == mt_in.v4.x);
3723  CHECK(mt_out.v4.y == mt_in.v4.y);
3724  CHECK(mt_out.v4.z == mt_in.v4.z);
3725  CHECK(mt_out.v4.w == mt_in.v4.w);
3726  CHECK(mt_in.seq.seq_member.size() > 0);
3727  CHECK(mt_out.seq.seq_member.size() == mt_in.seq.seq_member.size());
3728  for(size_t i = 0; i < mt_in.seq.seq_member.size(); ++i)
3729  {
3730  CHECK(mt_out.seq.seq_member[i] == mt_in.seq.seq_member[i]);
3731  }
3732  CHECK(mt_in.map.map_member.size() > 0);
3733  CHECK(mt_out.map.map_member.size() == mt_in.map.map_member.size());
3734  for(auto const& kv : mt_in.map.map_member)
3735  {
3736  CHECK(mt_out.map.map_member.find(kv.first) != mt_out.map.map_member.end());
3737  CHECK(mt_out.map.map_member[kv.first] == kv.second);
3738  }
3739  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(v2: '(20,21)'
3740 v3: '(30,31,32)'
3741 v4: '(40,41,42,43)'
3742 seq:
3743  - 101
3744  - 102
3745  - 103
3746  - 104
3747  - 105
3748  - 106
3749  - 107
3750 map:
3751  1001: 2001
3752  1002: 2002
3753  1003: 2003
3754 )");
3755 }

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

3765 {
3766  std::string yml_std_string = R"(- v2: '(20,21)'
3767  v3: '(30,31,32)'
3768  v4: '(40,41,42,43)'
3769  seq:
3770  - 101
3771  - 102
3772  - 103
3773  - 104
3774  - 105
3775  - 106
3776  - 107
3777  map:
3778  1001: 2001
3779  1002: 2002
3780  1003: 2003
3781 - v2: '(120,121)'
3782  v3: '(130,131,132)'
3783  v4: '(140,141,142,143)'
3784  seq:
3785  - 1101
3786  - 1102
3787  - 1103
3788  - 1104
3789  - 1105
3790  - 1106
3791  - 1107
3792  map:
3793  11001: 12001
3794  11002: 12002
3795  11003: 12003
3796 - v2: '(220,221)'
3797  v3: '(230,231,232)'
3798  v4: '(240,241,242,243)'
3799  seq:
3800  - 2101
3801  - 2102
3802  - 2103
3803  - 2104
3804  - 2105
3805  - 2106
3806  - 2107
3807  map:
3808  21001: 22001
3809  21002: 22002
3810  21003: 22003
3811 )";
3812  // parse in-place using the std::string above
3813  ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(yml_std_string));
3814  // my_type is a container-of-containers type. see above its
3815  // definition implementation for ryml.
3816  std::vector<my_type> vmt;
3817  tree.rootref() >> vmt;
3818  CHECK(vmt.size() == 3);
3819  ryml::Tree tree_out;
3820  tree_out.rootref() << vmt;
3821  CHECK(ryml::emitrs_yaml<std::string>(tree_out) == yml_std_string);
3822 }

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

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

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

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

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

◆ sample_emit_to_stream()

void sample::sample_emit_to_stream ( )

demonstrates how to emit to a stream-like structure

Definition at line 4082 of file quickstart.cpp.

4083 {
4084  ryml::csubstr ymlb = R"(- a
4085 - b
4086 - x0: 1
4087  x1: 2
4088 - champagne: Dom Perignon
4089  coffee: Arabica
4090  more:
4091  vinho verde: Soalheiro
4092  vinho tinto: Redoma 2017
4093  beer:
4094  - Rochefort 10
4095  - Busch
4096  - Leffe Rituel
4097 - foo
4098 - bar
4099 - baz
4100 - bat
4101 )";
4102  auto tree = ryml::parse_in_arena(ymlb);
4103 
4104  std::string s;
4105 
4106  // emit a full tree
4107  {
4108  std::stringstream ss;
4109  ss << tree; // works with any stream having .operator<<() and .write()
4110  s = ss.str();
4111  CHECK(ryml::to_csubstr(s) == ymlb);
4112  }
4113 
4114  // emit a full tree as json
4115  {
4116  std::stringstream ss;
4117  ss << ryml::as_json(tree); // works with any stream having .operator<<() and .write()
4118  s = ss.str();
4119  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"])");
4120  }
4121 
4122  // emit a nested node
4123  {
4124  std::stringstream ss;
4125  ss << tree[3][2]; // works with any stream having .operator<<() and .write()
4126  s = ss.str();
4127  CHECK(ryml::to_csubstr(s) == R"(more:
4128  vinho verde: Soalheiro
4129  vinho tinto: Redoma 2017
4130 )");
4131  }
4132 
4133  // emit a nested node as json
4134  {
4135  std::stringstream ss;
4136  ss << ryml::as_json(tree[3][2]); // works with any stream having .operator<<() and .write()
4137  s = ss.str();
4138  CHECK(ryml::to_csubstr(s) == R"("more": {"vinho verde": "Soalheiro","vinho tinto": "Redoma 2017"})");
4139  }
4140 }
mark a tree or node to be emitted as yaml when using operator<<, with options.
Definition: emit.hpp:406

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

◆ sample_emit_to_file()

void sample::sample_emit_to_file ( )

demonstrates how to emit to a FILE*

Definition at line 4146 of file quickstart.cpp.

4147 {
4148  ryml::csubstr yml = R"(- a
4149 - b
4150 - x0: 1
4151  x1: 2
4152 - champagne: Dom Perignon
4153  coffee: Arabica
4154  more:
4155  vinho verde: Soalheiro
4156  vinho tinto: Redoma 2017
4157  beer:
4158  - Rochefort 10
4159  - Busch
4160  - Leffe Rituel
4161 - foo
4162 - bar
4163 - baz
4164 - bat
4165 )";
4166  auto tree = ryml::parse_in_arena(yml);
4167  // this is emitting to stdout, but of course you can pass in any
4168  // FILE* obtained from fopen()
4169  size_t len = ryml::emit_yaml(tree, tree.root_id(), stdout);
4170  // the return value is the number of characters that were written
4171  // to the file
4172  CHECK(len == yml.len);
4173 }

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

4180 {
4181  const ryml::Tree tree = ryml::parse_in_arena(R"(- a
4182 - b
4183 - x0: 1
4184  x1: 2
4185 - champagne: Dom Perignon
4186  coffee: Arabica
4187  more:
4188  vinho verde: Soalheiro
4189  vinho tinto: Redoma 2017
4190  beer:
4191  - Rochefort 10
4192  - Busch
4193  - Leffe Rituel
4194  - - and so
4195  - many other
4196  - wonderful beers
4197 - more
4198 - seq
4199 - members
4200 - here
4201 )");
4202  CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"]) == R"(beer:
4203  - Rochefort 10
4204  - Busch
4205  - Leffe Rituel
4206  - - and so
4207  - many other
4208  - wonderful beers
4209 )");
4210  CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][0]) == "Rochefort 10\n");
4211  CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][3]) == R"(- and so
4212 - many other
4213 - wonderful beers
4214 )");
4215 }

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

4222 {
4223  ryml::Tree tree = ryml::parse_in_arena(R"(
4224 NodeOne:
4225  - key: a
4226  desc: b
4227  class: c
4228  number: d
4229  - key: e
4230  desc: f
4231  class: g
4232  number: h
4233  - key: i
4234  desc: j
4235  class: k
4236  number: l
4237 )");
4238  // ryml uses block style by default:
4239  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(NodeOne:
4240  - key: a
4241  desc: b
4242  class: c
4243  number: d
4244  - key: e
4245  desc: f
4246  class: g
4247  number: h
4248  - key: i
4249  desc: j
4250  class: k
4251  number: l
4252 )");
4253  // you can override the emit style of individual nodes:
4254  for(ryml::NodeRef child : tree["NodeOne"].children())
4255  child |= ryml::FLOW_SL; // flow, single-line
4256  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(NodeOne:
4257  - {key: a,desc: b,class: c,number: d}
4258  - {key: e,desc: f,class: g,number: h}
4259  - {key: i,desc: j,class: k,number: l}
4260 )");
4261  tree["NodeOne"] |= ryml::FLOW_SL;
4262  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}]
4263 )");
4264  tree.rootref() |= ryml::FLOW_SL;
4265  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}]})");
4266 }
@ 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 4273 of file quickstart.cpp.

4274 {
4275  ryml::csubstr json = R"({
4276  "doe":"a deer, a female deer",
4277  "ray":"a drop of golden sun",
4278  "me":"a name, I call myself",
4279  "far":"a long long way to go"
4280 })";
4281  // Since JSON is a subset of YAML, parsing JSON is just the
4282  // same as YAML:
4283  ryml::Tree tree = ryml::parse_in_arena(json);
4284  // If you are sure the source is valid json, you can use the
4285  // appropriate parse_json overload, which is faster because json
4286  // has a smaller grammar:
4287  ryml::Tree json_tree = ryml::parse_json_in_arena(json);
4288  // to emit JSON, use the proper overload:
4289  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"})");
4290  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"})");
4291  // to emit JSON to a stream:
4292  std::stringstream ss;
4293  ss << ryml::as_json(tree); // <- mark it like this
4294  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"})");
4295  // Note the following limitations:
4296  //
4297  // - YAML streams cannot be emitted as json, and are not
4298  // allowed. But you can work around this by emitting the
4299  // individual documents separately; see the sample_docs()
4300  // below for such an example.
4301  //
4302  // - tags cannot be emitted as json, and are not allowed.
4303  //
4304  // - anchors and references cannot be emitted as json and
4305  // are not allowed.
4306 }
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 4331 of file quickstart.cpp.

4332 {
4333  std::string unresolved = R"(base: &base
4334  name: Everyone has same name
4335 foo: &foo
4336  <<: *base
4337  age: 10
4338 bar: &bar
4339  <<: *base
4340  age: 20
4341 bill_to: &id001
4342  street: |-
4343  123 Tornado Alley
4344  Suite 16
4345  city: East Centerville
4346  state: KS
4347 ship_to: *id001
4348 &keyref key: &valref val
4349 *valref : *keyref
4350 )";
4351  std::string resolved = R"(base:
4352  name: Everyone has same name
4353 foo:
4354  name: Everyone has same name
4355  age: 10
4356 bar:
4357  name: Everyone has same name
4358  age: 20
4359 bill_to:
4360  street: |-
4361  123 Tornado Alley
4362  Suite 16
4363  city: East Centerville
4364  state: KS
4365 ship_to:
4366  street: |-
4367  123 Tornado Alley
4368  Suite 16
4369  city: East Centerville
4370  state: KS
4371 key: val
4372 val: key
4373 )";
4374 
4375  ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(unresolved));
4376  // by default, references are not resolved when parsing:
4377  CHECK( ! tree["base"].has_key_anchor());
4378  CHECK( tree["base"].has_val_anchor());
4379  CHECK( tree["base"].val_anchor() == "base");
4380  CHECK( tree["key"].key_anchor() == "keyref");
4381  CHECK( tree["key"].val_anchor() == "valref");
4382  CHECK( tree["*valref"].is_key_ref());
4383  CHECK( tree["*valref"].is_val_ref());
4384  CHECK( tree["*valref"].key_ref() == "valref");
4385  CHECK( tree["*valref"].val_ref() == "keyref");
4386 
4387  // to resolve references, simply call tree.resolve(),
4388  // which will perform the reference instantiations:
4389  tree.resolve();
4390 
4391  // all the anchors and references are substistuted and then removed:
4392  CHECK( ! tree["base"].has_key_anchor());
4393  CHECK( ! tree["base"].has_val_anchor());
4394  CHECK( ! tree["base"].has_val_anchor());
4395  CHECK( ! tree["key"].has_key_anchor());
4396  CHECK( ! tree["key"].has_val_anchor());
4397  CHECK( ! tree["val"].is_key_ref()); // notice *valref is now turned to val
4398  CHECK( ! tree["val"].is_val_ref()); // notice *valref is now turned to val
4399 
4400  CHECK(tree["ship_to"]["city"].val() == "East Centerville");
4401  CHECK(tree["ship_to"]["state"].val() == "KS");
4402 }
void resolve(ReferenceResolver *rr)
Resolve references (aliases <- anchors) in the tree.
Definition: tree.cpp:1111

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

◆ sample_tags()

void sample::sample_tags ( )

Definition at line 4407 of file quickstart.cpp.

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

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

◆ sample_tag_directives()

void sample::sample_tag_directives ( )

Definition at line 4547 of file quickstart.cpp.

4548 {
4549  const std::string yaml = R"(
4550 %TAG !m! !my-
4551 --- # Bulb here
4552 !m!light fluorescent
4553 ...
4554 %TAG !m! !meta-
4555 --- # Color here
4556 !m!light green
4557 )";
4559  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(%TAG !m! !my-
4560 --- !m!light fluorescent
4561 ...
4562 %TAG !m! !meta-
4563 --- !m!light green
4564 )");
4565  // tags are not resolved by default. Use .resolve_tags() to
4566  // accomplish this:
4567  tree.resolve_tags();
4568  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(%TAG !m! !my-
4569 --- !<!my-light> fluorescent
4570 ...
4571 %TAG !m! !meta-
4572 --- !<!meta-light> green
4573 )");
4574  // see also tree.normalize_tags()
4575  // see also tree.normalize_tags_long()
4576 }
void resolve_tags()
Definition: tree.cpp:1429

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

4582 {
4583  std::string yml = R"(---
4584 a: 0
4585 b: 1
4586 ---
4587 c: 2
4588 d: 3
4589 ---
4590 - 4
4591 - 5
4592 - 6
4593 - 7
4594 )";
4596  CHECK(ryml::emitrs_yaml<std::string>(tree) == yml);
4597 
4598  // iteration through docs
4599  {
4600  // using the node API
4601  const ryml::ConstNodeRef stream = tree.rootref();
4602  CHECK(stream.is_root());
4603  CHECK(stream.is_stream());
4604  CHECK(!stream.is_doc());
4605  CHECK(stream.num_children() == 3);
4606  for(const ryml::ConstNodeRef doc : stream.children())
4607  CHECK(doc.is_doc());
4608  CHECK(tree.docref(0).id() == stream.child(0).id());
4609  CHECK(tree.docref(1).id() == stream.child(1).id());
4610  CHECK(tree.docref(2).id() == stream.child(2).id());
4611  // equivalent: using the lower level index API
4612  const ryml::id_type stream_id = tree.root_id();
4613  CHECK(tree.is_root(stream_id));
4614  CHECK(tree.is_stream(stream_id));
4615  CHECK(!tree.is_doc(stream_id));
4616  CHECK(tree.num_children(stream_id) == 3);
4617  for(ryml::id_type doc_id = tree.first_child(stream_id); doc_id != ryml::NONE; doc_id = tree.next_sibling(stream_id))
4618  CHECK(tree.is_doc(doc_id));
4619  CHECK(tree.doc(0) == tree.child(stream_id, 0));
4620  CHECK(tree.doc(1) == tree.child(stream_id, 1));
4621  CHECK(tree.doc(2) == tree.child(stream_id, 2));
4622 
4623  // using the node API
4624  CHECK(stream[0].is_doc());
4625  CHECK(stream[0].is_map());
4626  CHECK(stream[0]["a"].val() == "0");
4627  CHECK(stream[0]["b"].val() == "1");
4628  // equivalent: using the index API
4629  const ryml::id_type doc0_id = tree.first_child(stream_id);
4630  CHECK(tree.is_doc(doc0_id));
4631  CHECK(tree.is_map(doc0_id));
4632  CHECK(tree.val(tree.find_child(doc0_id, "a")) == "0");
4633  CHECK(tree.val(tree.find_child(doc0_id, "b")) == "1");
4634 
4635  // using the node API
4636  CHECK(stream[1].is_doc());
4637  CHECK(stream[1].is_map());
4638  CHECK(stream[1]["c"].val() == "2");
4639  CHECK(stream[1]["d"].val() == "3");
4640  // equivalent: using the index API
4641  const ryml::id_type doc1_id = tree.next_sibling(doc0_id);
4642  CHECK(tree.is_doc(doc1_id));
4643  CHECK(tree.is_map(doc1_id));
4644  CHECK(tree.val(tree.find_child(doc1_id, "c")) == "2");
4645  CHECK(tree.val(tree.find_child(doc1_id, "d")) == "3");
4646 
4647  // using the node API
4648  CHECK(stream[2].is_doc());
4649  CHECK(stream[2].is_seq());
4650  CHECK(stream[2][0].val() == "4");
4651  CHECK(stream[2][1].val() == "5");
4652  CHECK(stream[2][2].val() == "6");
4653  CHECK(stream[2][3].val() == "7");
4654  // equivalent: using the index API
4655  const ryml::id_type doc2_id = tree.next_sibling(doc1_id);
4656  CHECK(tree.is_doc(doc2_id));
4657  CHECK(tree.is_seq(doc2_id));
4658  CHECK(tree.val(tree.child(doc2_id, 0)) == "4");
4659  CHECK(tree.val(tree.child(doc2_id, 1)) == "5");
4660  CHECK(tree.val(tree.child(doc2_id, 2)) == "6");
4661  CHECK(tree.val(tree.child(doc2_id, 3)) == "7");
4662  }
4663 
4664  // Note: since json does not have streams, you cannot emit the above
4665  // tree as json when you start from the root:
4666  //CHECK(ryml::emitrs_json<std::string>(tree) == yml); // RUNTIME ERROR!
4667 
4668  // emitting streams as json is not possible, but
4669  // you can iterate through individual documents and emit
4670  // them separately:
4671  {
4672  const std::string expected_json[] = {
4673  R"({"a": 0,"b": 1})",
4674  R"({"c": 2,"d": 3})",
4675  R"([4,5,6,7])",
4676  };
4677  // using the node API
4678  {
4679  ryml::id_type count = 0;
4680  const ryml::ConstNodeRef stream = tree.rootref();
4681  CHECK(stream.num_children() == (ryml::id_type)C4_COUNTOF(expected_json));
4682  for(ryml::ConstNodeRef doc : stream.children())
4683  CHECK(ryml::emitrs_json<std::string>(doc) == expected_json[count++]);
4684  }
4685  // equivalent: using the index API
4686  {
4687  ryml::id_type count = 0;
4688  const ryml::id_type stream_id = tree.root_id();
4689  CHECK(tree.num_children(stream_id) == (ryml::id_type)C4_COUNTOF(expected_json));
4690  for(ryml::id_type doc_id = tree.first_child(stream_id); doc_id != ryml::NONE; doc_id = tree.next_sibling(doc_id))
4691  CHECK(ryml::emitrs_json<std::string>(tree, doc_id) == expected_json[count++]);
4692  }
4693  }
4694 }
id_type id() const noexcept
Definition: node.hpp:1100
bool is_stream(id_type node) const
Definition: tree.hpp:404
bool is_root(id_type node) const
Definition: tree.hpp:456
NodeRef docref(id_type i)
get the i-th document of the stream
Definition: tree.cpp:69
bool is_doc(id_type node) const
Definition: tree.hpp:405
id_type doc(id_type i) const
gets the i document node index.
Definition: tree.hpp:519
id_type num_children(id_type node) const
O(num_children)
Definition: tree.cpp:1121
id_type child(id_type node, id_type pos) const
Definition: tree.cpp:1129
bool is_root() const RYML_NOEXCEPT
Forward to Tree::is_root().
Definition: node.hpp:305
bool is_doc() const RYML_NOEXCEPT
Forward to Tree::is_doc().
Definition: node.hpp:237
auto child(id_type pos) RYML_NOEXCEPT -> Impl
Forward to Tree::child().
Definition: node.hpp:347

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

◆ sample_error_handler()

void sample::sample_error_handler ( )

demonstrates how to set a custom error handler for ryml

Definition at line 4717 of file quickstart.cpp.

4718 {
4719  ErrorHandlerExample errh;
4720 
4721  // set a global error handler. Note the error callback must never
4722  // return: it must either throw an exception, use setjmp() and
4723  // longjmp(), or abort. Otherwise, the parser will enter into an
4724  // infinite loop, or the program may crash.
4725  ryml::set_callbacks(errh.callbacks());
4726  errh.check_effect(/*committed*/true);
4727  CHECK(errh.check_error_occurs([&]{
4728  ryml::Tree tree = ryml::parse_in_arena("errorhandler.yml", "[a: b\n}");
4729  }));
4730  ryml::set_callbacks(errh.defaults); // restore defaults.
4731  errh.check_effect(/*committed*/false);
4732 }

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

4830 {
4831  GlobalAllocatorExample mem;
4832 
4833  // save the existing callbacks for restoring
4834  ryml::Callbacks defaults = ryml::get_callbacks();
4835 
4836  // set to our callbacks
4837  ryml::set_callbacks(mem.callbacks());
4838 
4839  // verify that the allocator is in effect
4840  ryml::Callbacks const& current = ryml::get_callbacks();
4841  CHECK(current.m_allocate == &mem.s_allocate);
4842  CHECK(current.m_free == &mem.s_free);
4843 
4844  // so far nothing was allocated
4845  CHECK(mem.alloc_size == 0);
4846 
4847  // parse one tree and check
4848  (void)ryml::parse_in_arena(R"({foo: bar})");
4849  mem.check_and_reset();
4850 
4851  // parse another tree and check
4852  (void)ryml::parse_in_arena(R"([a, b, c, d, {foo: bar, money: pennys}])");
4853  mem.check_and_reset();
4854 
4855  // verify that by reserving we save allocations
4856  {
4857  ryml::EventHandlerTree evt_handler;
4858  ryml::Parser parser(&evt_handler); // reuse a parser
4859  ryml::Tree tree; // reuse a tree
4860 
4861  tree.reserve(10); // reserve the number of nodes
4862  tree.reserve_arena(100); // reserve the arena size
4863  parser.reserve_stack(10); // reserve the parser depth.
4864 
4865  // since the parser stack uses Small Storage Optimization,
4866  // allocations will only happen with capacities higher than 16.
4867  CHECK(mem.num_allocs == 2); // tree, tree_arena and NOT the parser
4868 
4869  parser.reserve_stack(20); // reserve the parser depth.
4870  CHECK(mem.num_allocs == 3); // tree, tree_arena and now the parser as well
4871 
4872  // verify that no other allocations occur when parsing
4873  size_t size_before = mem.alloc_size;
4874  parse_in_arena(&parser, "", R"([a, b, c, d, {foo: bar, money: pennys}])", &tree);
4875  CHECK(mem.alloc_size == size_before);
4876  CHECK(mem.num_allocs == 3);
4877  }
4878  mem.check_and_reset();
4879 
4880  // restore defaults.
4881  ryml::set_callbacks(defaults);
4882 }

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

4953 {
4954  PerTreeMemoryExample mrp;
4955  PerTreeMemoryExample mr1;
4956  PerTreeMemoryExample mr2;
4957 
4958  // the trees will use the memory in the resources above,
4959  // with each tree using a separate resource
4960  {
4961  // Watchout: ensure that the lifetime of the callbacks target
4962  // exceeds the lifetime of the tree.
4963  ryml::EventHandlerTree evt_handler(mrp.callbacks());
4964  ryml::Parser parser(&evt_handler);
4965  ryml::Tree tree1(mr1.callbacks());
4966  ryml::Tree tree2(mr2.callbacks());
4967 
4968  ryml::csubstr yml1 = "{a: b}";
4969  ryml::csubstr yml2 = "{c: d, e: f, g: [h, i, 0, 1, 2, 3]}";
4970 
4971  parse_in_arena(&parser, "file1.yml", yml1, &tree1);
4972  parse_in_arena(&parser, "file2.yml", yml2, &tree2);
4973  }
4974 
4975  CHECK(mrp.num_allocs == 0); // YAML depth not large enough to warrant a parser allocation
4976  CHECK(mr1.alloc_size <= mr2.alloc_size); // because yml2 has more nodes
4977 }

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

4987 {
4988  // Using static trees incurs may incur a static initialization
4989  // order problem. This happens because a default-constructed tree will
4990  // obtain the callbacks from the current global setting, which may
4991  // not have been initialized due to undefined static initialization
4992  // order:
4993  //
4994  //static ryml::Tree tree; // ERROR! depends on ryml::get_callbacks() which may not have been initialized.
4995  //
4996  // To work around the issue, declare static callbacks
4997  // to explicitly initialize the static tree:
4998  static ryml::Callbacks callbacks = {}; // use default callback members
4999  static ryml::Tree tree(callbacks); // OK
5000  // now you can use the tree as normal:
5001  ryml::parse_in_arena(R"(doe: "a deer, a female deer")", &tree);
5002  CHECK(tree["doe"].val() == "a deer, a female deer");
5003 }

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

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