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

Example code for every feature. More...

Modules

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

Functions

ryml::Callbacks sample::ErrorHandlerExample::callbacks ()
 a helper to create the Callbacks object with the custom error handler More...
 
static void sample::ErrorHandlerExample::s_error (const char *msg, size_t len, ryml::Location loc, void *this_)
 
template<class Fn >
bool sample::ErrorHandlerExample::check_error_occurs (Fn &&fn) const
 
template<class Fn >
bool sample::ErrorHandlerExample::check_assertion_occurs (Fn &&fn) const
 
void sample::ErrorHandlerExample::check_effect (bool committed) const
 
 sample::ErrorHandlerExample::ErrorHandlerExample ()
 
 sample::ScopedErrorHandlerExample::ScopedErrorHandlerExample ()
 
 sample::ScopedErrorHandlerExample::~ScopedErrorHandlerExample ()
 
void sample::sample_lightning_overview ()
 a lightning tour over most features see sample_quick_overview More...
 
void sample::sample_quick_overview ()
 a brief tour over most features More...
 
void sample::sample_substr ()
 demonstrate usage of ryml::substr and ryml::csubstr More...
 
void sample::sample_parse_file ()
 demonstrate how to load a YAML file from disk to parse with ryml. More...
 
void sample::sample_parse_in_place ()
 demonstrate in-place parsing of a mutable YAML source buffer. More...
 
void sample::sample_parse_in_arena ()
 demonstrate parsing of a read-only YAML source buffer More...
 
void sample::sample_parse_reuse_tree ()
 demonstrate reuse/modification of tree when parsing More...
 
void sample::sample_parse_reuse_parser ()
 Demonstrates reuse of an existing parser. More...
 
void sample::sample_parse_reuse_tree_and_parser ()
 for ultimate speed when parsing multiple times, reuse both the tree and parser More...
 
void sample::sample_iterate_trees ()
 shows how to programatically iterate through trees More...
 
void sample::sample_create_trees ()
 shows how to programatically create trees More...
 
void sample::sample_tree_arena ()
 demonstrates explicit and implicit interaction with the tree's string arena. More...
 
void sample::sample_fundamental_types ()
 ryml provides facilities for serializing and deserializing the C++ fundamental types, including boolean and null values; this is provided by the several overloads in to_chars: generalized chars to value and from_chars: generalized chars to value. More...
 
void sample::sample_empty_null_values ()
 Shows how to deal with empty/null values. More...
 
void sample::sample_formatting ()
 ryml provides facilities for formatting/deformatting (imported from c4core into the ryml namespace). More...
 
void sample::sample_base64 ()
 demonstrates how to read and write base64-encoded blobs. More...
 
void sample::sample_user_scalar_types ()
 to add scalar types (ie leaf types converting to/from string), define the functions above for those types. More...
 
void sample::sample_user_container_types ()
 shows how to serialize/deserialize container types. More...
 
void sample::sample_std_types ()
 demonstrates usage with the std implementations provided by ryml in the ryml_std.hpp header More...
 
void sample::sample_float_precision ()
 control precision of serialized floats More...
 
void sample::sample_emit_to_container ()
 demonstrates how to emit to a linear container of char More...
 
void sample::sample_emit_to_stream ()
 demonstrates how to emit to a stream-like structure More...
 
void sample::sample_emit_to_file ()
 demonstrates how to emit to a FILE* More...
 
void sample::sample_emit_nested_node ()
 just like parsing into a nested node, you can also emit from a nested node. More...
 
void sample::sample_style ()
 [experimental] how to query/set/modify node style. 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_anchors_and_aliases_create ()
 demonstrates how to use the API to programatically create anchors and aliases 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.10.0
GIT_SHALLOW FALSE # ensure submodules are checked out
)
FetchContent_MakeAvailable(ryml)
add_executable(ryml-quickstart ${ryml_SOURCE_DIR}/samples/quickstart.cpp)
target_link_libraries(ryml-quickstart ryml::ryml)
add_custom_target(run ryml-quickstart
COMMAND $<TARGET_FILE:ryml-quickstart>
DEPENDS ryml-quickstart
COMMENT "running: $<TARGET_FILE:ryml-quickstart>")

Now run the following commands in the same folder:

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

Function Documentation

◆ callbacks()

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

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

Definition at line 5448 of file quickstart.cpp.

5449  {
5450  ++num_failed_checks;
5451  msg = predicate ? "ERROR: " : "ERROR";

Referenced by sample::ScopedErrorHandlerExample::ScopedErrorHandlerExample(), 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 5453 of file quickstart.cpp.

5453  :' << line << ": " << msg << (predicate ? predicate : "") << std::endl;
5454  return result;
5455 }
5456 
5457 
5458 int report_checks()
5459 {
5460  std::cout << "Completed " << num_checks << " checks." << std::endl;
5461  if(num_failed_checks)
5462  std::cout << "ERROR: " << num_failed_checks << '/' << num_checks << " checks failed." << std::endl;
5463  else
5464  std::cout << "SUCCESS!" << std::endl;
5465  return num_failed_checks;
5466 }

Referenced by sample::ScopedErrorHandlerExample::ScopedErrorHandlerExample(), and sample::ScopedErrorHandlerExample::~ScopedErrorHandlerExample().

◆ ErrorHandlerExample()

sample::ErrorHandlerExample::ErrorHandlerExample ( )
inline

Definition at line 255 of file quickstart.cpp.

Callbacks const & get_callbacks()
get the global callbacks
Definition: common.cpp:118

◆ ScopedErrorHandlerExample()

sample::ScopedErrorHandlerExample::ScopedErrorHandlerExample ( )
inline

Definition at line 261 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 sample::ErrorHandlerExample::callbacks(), sample::ErrorHandlerExample::check_effect(), and c4::yml::set_callbacks().

◆ ~ScopedErrorHandlerExample()

sample::ScopedErrorHandlerExample::~ScopedErrorHandlerExample ( )
inline

◆ sample_lightning_overview()

void sample::sample_lightning_overview ( )

a lightning tour over most features see sample_quick_overview

Definition at line 274 of file quickstart.cpp.

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

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

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

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::detail::RoNodeMethods< Impl, ConstImpl >::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 941 of file quickstart.cpp.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

◆ sample_style()

void sample::sample_style ( )

[experimental] how to query/set/modify node style.

Definition at line 4226 of file quickstart.cpp.

4227 {
4228  ryml::Tree tree = ryml::parse_in_arena(R"(
4229 block map:
4230  block key: block val
4231 block seq:
4232  - block val 1
4233  - block val 2
4234  - 'quoted'
4235 flow map: {flow key: flow val}
4236 flow seq: [flow val, flow val]
4237 )");
4238  // while parsing, ryml marks parsed nodes with their original style:
4239  CHECK(tree.rootref().is_block());
4240  CHECK(tree["block map"].key_style() & ryml::KEY_PLAIN);
4241  CHECK(tree["block seq"].key_style() & ryml::KEY_PLAIN);
4242  CHECK(tree["flow map"].key_style() & ryml::KEY_PLAIN);
4243  CHECK(tree["flow seq"].key_style() & ryml::KEY_PLAIN);
4244  CHECK(tree["block map"].is_block());
4245  CHECK(tree["block seq"].is_block());
4246  CHECK(tree["flow map"].is_flow());
4247  CHECK(tree["flow seq"].is_flow());
4248  // which means that if you emit a tree, its style will be
4249  // preserved:
4250  CHECK(ryml::emitrs_yaml<std::string>(tree) ==
4251 R"(block map:
4252  block key: block val
4253 block seq:
4254  - block val 1
4255  - block val 2
4256  - 'quoted'
4257 flow map: {flow key: flow val}
4258 flow seq: [flow val,flow val]
4259 )");
4260  // you can set the style programatically:
4261  tree["block map"].set_container_style(ryml::FLOW_SL); // container style: to flow
4262  tree["block seq"].set_container_style(ryml::FLOW_SL);
4263  tree["flow map"].set_container_style(ryml::BLOCK); // container style: to block
4264  tree["flow seq"].set_container_style(ryml::BLOCK);
4265  tree["block map"].set_key_style(ryml::KEY_SQUO); // scalar style: to single-quoted scalar
4266  tree["block seq"].set_key_style(ryml::KEY_DQUO); // scalar style: to double-quoted scalar
4267  tree["flow map"].set_key_style(ryml::KEY_LITERAL); // scalar style: to literal scalar
4268  tree["flow seq"].set_key_style(ryml::KEY_FOLDED); // scalar style: to folded scalar
4269  tree["block seq"][2].set_val_style(ryml::VAL_PLAIN); // scalar style: to plain
4270  tree["flow seq"][0].set_val_style(ryml::VAL_SQUO);
4271  tree["flow seq"][1].set_val_style(ryml::VAL_DQUO);
4272  // note the difference now:
4273  CHECK(ryml::emitrs_yaml<std::string>(tree) ==
4274 R"('block map': {block key: block val}
4275 "block seq": [block val 1,block val 2,quoted]
4276 ? |-
4277  flow map
4278 :
4279  flow key: flow val
4280 ? >-
4281  flow seq
4282 :
4283  - 'flow val'
4284  - "flow val"
4285 )");
4286  // you can clear the style of single nodes:
4287  tree["block map"].clear_style();
4288 std::cout << tree;
4289  CHECK(ryml::emitrs_yaml<std::string>(tree) ==
4290 R"(block map:
4291  block key: block val
4292 "block seq": [block val 1,block val 2,quoted]
4293 ? |-
4294  flow map
4295 :
4296  flow key: flow val
4297 ? >-
4298  flow seq
4299 :
4300  - 'flow val'
4301  - "flow val"
4302 )");
4303  // you can clear the style recursively:
4304  tree.rootref().clear_style(/*recurse*/true);
4305  CHECK(ryml::emitrs_yaml<std::string>(tree) ==
4306 R"(block map:
4307  block key: block val
4308 block seq:
4309  - block val 1
4310  - block val 2
4311  - quoted
4312 flow map:
4313  flow key: flow val
4314 flow seq:
4315  - flow val
4316  - flow val
4317 )");
4318  // you can also set the style based on type conditions:
4319  {
4320  // set a single key to single-quoted
4321  tree["block map"].set_style_conditionally(ryml::KEY,
void set_key_style(id_type node, NodeType_e style)
Definition: tree.hpp:519
void clear_style(id_type node, bool recurse=false)
Definition: tree.cpp:1346
void set_container_style(id_type node, NodeType_e style)
Definition: tree.hpp:518
void set_val_style(id_type node, NodeType_e style)
Definition: tree.hpp:520
@ KEY_DQUO
mark key scalar as double quoted "
Definition: node_type.hpp:68
@ FLOW_SL
mark container with single-line flow style (seqs as '[val1,val2], maps as '{key: val,...
Definition: node_type.hpp:59
@ VAL_SQUO
mark val scalar as single quoted '
Definition: node_type.hpp:67
@ VAL_PLAIN
mark val scalar as plain scalar (unquoted, even when multiline)
Definition: node_type.hpp:71
@ BLOCK
mark container with block style (seqs as '- val ', maps as 'key: val')
Definition: node_type.hpp:61
@ VAL_DQUO
mark val scalar as double quoted "
Definition: node_type.hpp:69
@ KEY_SQUO
mark key scalar as single quoted '
Definition: node_type.hpp:66
@ KEY_LITERAL
mark key scalar as multiline, block literal |
Definition: node_type.hpp:62
@ KEY_PLAIN
mark key scalar as plain scalar (unquoted, even when multiline)
Definition: node_type.hpp:70
@ KEY_FOLDED
mark key scalar as multiline, block folded >
Definition: node_type.hpp:64
bool is_block() const RYML_NOEXCEPT
Forward to Tree::is_block().
Definition: node.hpp:276

References c4::yml::BLOCK, CHECK, c4::yml::NodeRef::clear_style(), c4::yml::Tree::clear_style(), c4::yml::FLOW_SL, c4::yml::detail::RoNodeMethods< Impl, ConstImpl >::is_block(), c4::yml::KEY, c4::yml::KEY_DQUO, c4::yml::KEY_FOLDED, c4::yml::KEY_LITERAL, c4::yml::KEY_PLAIN, c4::yml::KEY_SQUO, c4::yml::KEY_STYLE, c4::yml::parse_in_arena(), c4::yml::Tree::rootref(), c4::yml::Tree::set_container_style(), c4::yml::Tree::set_key_style(), c4::yml::Tree::set_style_conditionally(), c4::yml::Tree::set_val_style(), c4::yml::VAL_DQUO, c4::yml::VAL_PLAIN, and c4::yml::VAL_SQUO.

◆ sample_json()

void sample::sample_json ( )

shows how to parse and emit JSON.

To emit YAML parsed from JSON, see also sample_style() for info on clearing the style flags (example below).

Definition at line 4330 of file quickstart.cpp.

4332  :
4333  flow key: flow val
4334 flow seq:
4335  - flow val
4336  - flow val
4337 )");
4338  // set all keys to single-quoted:
4339  tree.rootref().set_style_conditionally(/*type_mask*/ryml::KEY,
4340  /*remflags*/ryml::KEY_STYLE,
4341  /*addflags*/ryml::KEY_SQUO,
4342  /*recurse*/true);
4343  // set all vals to double-quoted
4344  tree.rootref().set_style_conditionally(/*type_mask*/ryml::VAL,
4345  /*remflags*/ryml::VAL_STYLE,
4346  /*addflags*/ryml::VAL_DQUO,
4347  /*recurse*/true);
4348  // set all seqs to flow
4349  tree.rootref().set_style_conditionally(/*type_mask*/ryml::SEQ,
4350  /*remflags*/ryml::CONTAINER_STYLE,
4351  /*addflags*/ryml::FLOW_SL,
4352  /*recurse*/true);
4353  // set all maps to flow
4354  tree.rootref().set_style_conditionally(/*type_mask*/ryml::MAP,
4355  /*remflags*/ryml::CONTAINER_STYLE,
4356  /*addflags*/ryml::BLOCK,
4357  /*recurse*/true);
4358  // done!
4359  CHECK(ryml::emitrs_yaml<std::string>(tree) ==
4360 R"('block map':
4361  'block key': "block val"
4362 'block seq': ["block val 1","block val 2","quoted"]
4363 'flow map':
4364  'flow key': "flow val"
4365 'flow seq': ["flow val","flow val"]
4366 )");
4367  // you can also set a conditional style in a single node:
4368  tree["flow seq"].set_style_conditionally(/*type_mask*/ryml::SEQ,
4369  /*remflags*/ryml::CONTAINER_STYLE,
4370  /*addflags*/ryml::BLOCK,
4371  /*recurse*/false);
4372  CHECK(ryml::emitrs_yaml<std::string>(tree) ==
4373 R"('block map':
4374  'block key': "block val"
4375 'block seq': ["block val 1","block val 2","quoted"]
4376 'flow map':
4377  'flow key': "flow val"
4378 'flow seq':
4379  - "flow val"
4380  - "flow val"
4381 )");
4382  }
4383  // see also:
4384  // - ryml::scalar_style_choose()
4385  // - ryml::scalar_style_json_choose()
4386  // - ryml::scalar_style_query_squo()
4387  // - ryml::scalar_style_query_plain()
4388 }
4389 
4390 
4391 //-----------------------------------------------------------------------------

◆ 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 c4::yml::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 c4::yml::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 c4::yml::Tree::resolve()

Definition at line 4416 of file quickstart.cpp.

4418  {"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"})");
4419  // Note the following limitations:
4420  //
4421  // - YAML streams cannot be emitted as json, and are not
4422  // allowed. But you can work around this by emitting the
4423  // individual documents separately; see the sample_docs()
4424  // below for such an example.
4425  //
4426  // - tags cannot be emitted as json, and are not allowed.
4427  //
4428  // - anchors and references cannot be emitted as json and
4429  // are not allowed.
4430  //
4431 
4432  // Note that when parsing JSON, ryml will the style of each node
4433  // in the JSON. This means that if you emit as YAML it will look
4434  // mostly the same as the JSON:
4435  CHECK(ryml::emitrs_yaml<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"})");
4436  // If you want to avoid this, you will need to clear the style.
4437  json_tree.rootref().clear_style(); // clear the style of the map, but do not recurse
4438  // note that this is now block mode. That is because when no
4439  // style is set, the emit function will default to block mode.
4440  CHECK(ryml::emitrs_yaml<std::string>(json_tree) ==
4441  R"("doe": "a deer, a female deer"
4442 "ray": "a drop of golden sun"
4443 "me": "a name, I call myself"
4444 "far": "a long long way to go"
4445 )");
4446  // if you don't want the double quotes in the scalar, you can
4447  // recurse:
4448  json_tree.rootref().clear_style(/*recurse*/true);
4449  // so now you when emittingwill get this:
4450  // (the scalars with a comma are single-qu)
4451  CHECK(ryml::emitrs_yaml<std::string>(json_tree) ==
4452  R"(doe: 'a deer, a female deer'
4453 ray: a drop of golden sun
4454 me: 'a name, I call myself'
4455 far: a long long way to go
4456 )");
4457  // you can do custom style changes based on a type mask. this
4458  // will change set the style of all scalar values to single-quoted
4459  json_tree.rootref().set_style_conditionally(ryml::VAL,
4460  /*remflags*/ryml::VAL_STYLE,
4461  /*addflags*/ryml::VAL_SQUO,
4462  /*recurse*/true);
4463  CHECK(ryml::emitrs_yaml<std::string>(json_tree) ==
4464  R"(doe: 'a deer, a female deer'
4465 ray: 'a drop of golden sun'
4466 me: 'a name, I call myself'
4467 far: 'a long long way to go'
4468 )");
4469  // see in particular sample_style() for more examples
4470 }
4471 
4472 
4473 //-----------------------------------------------------------------------------
4474 
4475 /** demonstrates usage with anchors and alias references.
4476 
4477 Note that dereferencing is opt-in; after parsing, you have to call
4478 @ref c4::yml::Tree::resolve() explicitly if you want resolved
4479 references in the tree. This method will resolve all references and
4480 substitute the anchored values in place of the reference.
4481 
4482 The @ref c4::yml::Tree::resolve() method first does a full traversal
4483 of the tree to gather all anchors and references in a separate
4484 collection, then it goes through that collection to locate the names,
4485 which it does by obeying the YAML standard diktat that
4486 
4487  > an alias node refers to the most recent node in

◆ sample_anchors_and_aliases_create()

void sample::sample_anchors_and_aliases_create ( )

demonstrates how to use the API to programatically create anchors and aliases

Definition at line 4491 of file quickstart.cpp.

4496 {
4497  std::string unresolved = R"(base: &base
4498  name: Everyone has same name
4499 foo: &foo
4500  <<: *base
4501  age: 10
4502 bar: &bar
4503  <<: *base
4504  age: 20
4505 bill_to: &id001
4506  street: |-
4507  123 Tornado Alley
4508  Suite 16
4509  city: East Centerville
4510  state: KS
4511 ship_to: *id001
4512 &keyref key: &valref val
4513 *valref : *keyref
4514 )";
4515  std::string resolved = R"(base:
4516  name: Everyone has same name
4517 foo:
4518  name: Everyone has same name
4519  age: 10
4520 bar:
4521  name: Everyone has same name
4522  age: 20
4523 bill_to:
4524  street: |-
4525  123 Tornado Alley
4526  Suite 16
4527  city: East Centerville
4528  state: KS
4529 ship_to:
4530  street: |-
4531  123 Tornado Alley
4532  Suite 16
4533  city: East Centerville
4534  state: KS
4535 key: val
4536 val: key
4537 )";
4538 
4539  ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(unresolved));
4540  // by default, references are not resolved when parsing:
4541  CHECK( ! tree["base"].has_key_anchor());
4542  CHECK( tree["base"].has_val_anchor());
4543  CHECK( tree["base"].val_anchor() == "base");
4544  CHECK( tree["key"].key_anchor() == "keyref");
4545  CHECK( tree["key"].val_anchor() == "valref");
4546  CHECK( tree["*valref"].is_key_ref());
4547  CHECK( tree["*valref"].is_val_ref());
4548  CHECK( tree["*valref"].key_ref() == "valref");
4549  CHECK( tree["*valref"].val_ref() == "keyref");
4550 
4551  // to resolve references, simply call tree.resolve(),
4552  // which will perform the reference instantiations:
4553  tree.resolve();
4554 
4555  // all the anchors and references are substistuted and then removed:
4556  CHECK( ! tree["base"].has_key_anchor());
4557  CHECK( ! tree["base"].has_val_anchor());
4558  CHECK( ! tree["base"].has_val_anchor());
4559  CHECK( ! tree["key"].has_key_anchor());
4560  CHECK( ! tree["key"].has_val_anchor());
4561  CHECK( ! tree["val"].is_key_ref()); // notice *valref is now turned to val
4562  CHECK( ! tree["val"].is_val_ref()); // notice *valref is now turned to val
4563 
4564  CHECK(tree["ship_to"]["city"].val() == "East Centerville");
4565  CHECK(tree["ship_to"]["state"].val() == "KS");
4566 }
4567 
4568 /** demonstrates how to use the API to programatically create anchors
4569  * and aliases */
void resolve(ReferenceResolver *rr, bool clear_anchors=true)
Resolve references (aliases <- anchors), by forwarding to ReferenceResolver::resolve(); refer to Refe...
Definition: tree.cpp:1142

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

◆ sample_tags()

void sample::sample_tags ( )

Definition at line 4574 of file quickstart.cpp.

4584  : 2
4585 vanchor: &vanchor 3
4586 kref: *kanchor
4587 vref: *vanchor
4588 nref: '*vanchor'
4589 )"); // note that ryml emits nref with quotes to disambiguate (because no style was set)
4590  t.resolve();
4591  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(kanchor: 2
4592 vanchor: 3
4593 kref: kanchor
4594 vref: 3
4595 nref: '*vanchor'
4596 )"); // note that nref was not resolved
4597  }
4598 
4599  // part 2: simple inheritance (ie, adding `<<: *anchor` nodes)
4600  {
4601  ryml::Tree t = ryml::parse_in_arena(R"(
4602 orig: &orig {foo: bar, baz: bat}
4603 copy: {}
4604 notcopy: {}
4605 notref: {}
4606 )");
4607  t["copy"]["<<"].set_val_ref("orig");
4608  t["notcopy"]["test"].set_val_ref("orig");
4609  t["notcopy"]["<<"].set_val_ref("orig");
4610  t["notref"]["<<"] = "*orig"; // not a reference! .set_val_ref() was not called
4611  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(orig: &orig {foo: bar,baz: bat}
4612 copy: {<<: *orig}
4613 notcopy: {test: *orig,<<: *orig}
4614 notref: {<<: '*orig'}
4615 )");
4616  t.resolve();
4617  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(orig: {foo: bar,baz: bat}
4618 copy: {foo: bar,baz: bat}
4619 notcopy: {test: {foo: bar,baz: bat},foo: bar,baz: bat}
4620 notref: {<<: '*orig'}
4621 )");
4622  }
4623 
4624  // part 3: multiple inheritance (ie, `<<: [*ref1,*ref2,*etc]`)
4625  {
4626  ryml::Tree t = ryml::parse_in_arena(R"(orig1: &orig1 {foo: bar}
4627 orig2: &orig2 {baz: bat}
4628 orig3: &orig3 {and: more}
4629 copy: {}
4630 )");
4631  ryml::NodeRef seq = t["copy"]["<<"];
4632  seq |= ryml::SEQ;
4633  seq.append_child().set_val_ref("orig1");
4634  seq.append_child().set_val_ref("orig2");
4635  seq.append_child().set_val_ref("orig3");
4636  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(orig1: &orig1 {foo: bar}
4637 orig2: &orig2 {baz: bat}
4638 orig3: &orig3 {and: more}
4639 copy: {<<: [*orig1,*orig2,*orig3]}
4640 )");
4641  t.resolve();
4642  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(orig1: {foo: bar}
4643 orig2: {baz: bat}
4644 orig3: {and: more}
4645 copy: {foo: bar,baz: bat,and: more}
4646 )");
4647  }
4648 }
4649 
4650 
4651 //-----------------------------------------------------------------------------
4652 
4653 void sample_tags()
4654 {
4655  const std::string yaml = R"(--- !!map
4656 a: 0
4657 b: 1
4658 --- !map
4659 a: b
4660 --- !!seq
4661 - a
4662 - b
4663 --- !!str a b
4664 --- !!str 'a: b'
4665 ---
4666 !!str a: b
4667 --- !!set
4668 ? a
4669 ? b
4670 --- !!set
4671 a:
4672 --- !!seq
4673 - !!int 0
4674 - !!str 1
4675 )";
4676  const ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(yaml));
4677  const ryml::ConstNodeRef root = tree.rootref();
4678  CHECK(root.is_stream());
4679  CHECK(root.num_children() == 9);
4680  for(ryml::ConstNodeRef doc : root.children())
4681  CHECK(doc.is_doc());
4682  // tags are kept verbatim from the source:
4683  CHECK(root[0].has_val_tag());
4684  CHECK(root[0].val_tag() == "!!map"); // valid only if the node has a val tag
4685  CHECK(root[1].val_tag() == "!map");
4686  CHECK(root[2].val_tag() == "!!seq");
4687  CHECK(root[3].val_tag() == "!!str");
4688  CHECK(root[4].val_tag() == "!!str");
4689  CHECK(root[5]["a"].has_key_tag());
4690  CHECK(root[5]["a"].key_tag() == "!!str"); // valid only if the node has a key tag
4691  CHECK(root[6].val_tag() == "!!set");
4692  CHECK(root[7].val_tag() == "!!set");
4693  CHECK(root[8].val_tag() == "!!seq");
4694  CHECK(root[8][0].val_tag() == "!!int");
4695  CHECK(root[8][1].val_tag() == "!!str");
4696  // ryml also provides a complete toolbox to deal with tags.
4697  // there is an enumeration for the standard YAML tags:
4698  CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
4699  CHECK(ryml::to_tag("!!map") == ryml::TAG_MAP);
4700  CHECK(ryml::to_tag("!!seq") == ryml::TAG_SEQ);
4701  CHECK(ryml::to_tag("!!str") == ryml::TAG_STR);
4702  CHECK(ryml::to_tag("!!int") == ryml::TAG_INT);
4703  CHECK(ryml::to_tag("!!set") == ryml::TAG_SET);
4704  // given a tag enum, you can fetch the short tag string:
4705  CHECK(ryml::from_tag(ryml::TAG_NONE) == "");
4706  CHECK(ryml::from_tag(ryml::TAG_MAP) == "!!map");
4707  CHECK(ryml::from_tag(ryml::TAG_SEQ) == "!!seq");
4708  CHECK(ryml::from_tag(ryml::TAG_STR) == "!!str");
4709  CHECK(ryml::from_tag(ryml::TAG_INT) == "!!int");

◆ sample_tag_directives()

void sample::sample_tag_directives ( )

Definition at line 4714 of file quickstart.cpp.

4714  :yaml.org,2002:seq>");
4715  CHECK(ryml::from_tag_long(ryml::TAG_STR) == "<tag:yaml.org,2002:str>");
4716  CHECK(ryml::from_tag_long(ryml::TAG_INT) == "<tag:yaml.org,2002:int>");
4717  CHECK(ryml::from_tag_long(ryml::TAG_SET) == "<tag:yaml.org,2002:set>");
4718  // and likewise:
4719  CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
4720  CHECK(ryml::to_tag("<tag:yaml.org,2002:map>") == ryml::TAG_MAP);
4721  CHECK(ryml::to_tag("<tag:yaml.org,2002:seq>") == ryml::TAG_SEQ);
4722  CHECK(ryml::to_tag("<tag:yaml.org,2002:str>") == ryml::TAG_STR);
4723  CHECK(ryml::to_tag("<tag:yaml.org,2002:int>") == ryml::TAG_INT);
4724  CHECK(ryml::to_tag("<tag:yaml.org,2002:set>") == ryml::TAG_SET);
4725  // to normalize a tag as much as possible, use normalize_tag():
4726  CHECK(ryml::normalize_tag("!!map") == "!!map");
4727  CHECK(ryml::normalize_tag("!<tag:yaml.org,2002:map>") == "!!map");
4728  CHECK(ryml::normalize_tag("<tag:yaml.org,2002:map>") == "!!map");
4729  CHECK(ryml::normalize_tag("tag:yaml.org,2002:map") == "!!map");
4730  CHECK(ryml::normalize_tag("!<!!map>") == "<!!map>");
4731  CHECK(ryml::normalize_tag("!map") == "!map");
4732  CHECK(ryml::normalize_tag("!my!foo") == "!my!foo");
4733  // and also for the long form:
4734  CHECK(ryml::normalize_tag_long("!!map") == "<tag:yaml.org,2002:map>");
4735  CHECK(ryml::normalize_tag_long("!<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
4736  CHECK(ryml::normalize_tag_long("<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
4737  CHECK(ryml::normalize_tag_long("tag:yaml.org,2002:map") == "<tag:yaml.org,2002:map>");
4738  CHECK(ryml::normalize_tag_long("!<!!map>") == "<!!map>");
4739  CHECK(ryml::normalize_tag_long("!map") == "!map");
4740  // The tree provides the following methods applying to every node
4741  // with a key and/or val tag:
4742  ryml::Tree normalized_tree = tree;
4743  normalized_tree.normalize_tags(); // normalize all tags in short form

◆ sample_docs()

void sample::sample_docs ( )

Definition at line 4748 of file quickstart.cpp.

4748  : b
4749 --- !!seq
4750 - a
4751 - b
4752 --- !!str a b
4753 --- !!str 'a: b'
4754 ---
4755 !!str a: b
4756 --- !!set
4757 a:
4758 b:
4759 --- !!set
4760 a:
4761 --- !!seq
4762 - !!int 0
4763 - !!str 1
4764 )");
4765  ryml::Tree normalized_tree_long = tree;
4766  normalized_tree_long.normalize_tags_long(); // normalize all tags in short form
4767  CHECK(ryml::emitrs_yaml<std::string>(normalized_tree_long) == R"(--- !<tag:yaml.org,2002:map>
4768 a: 0
4769 b: 1
4770 --- !map
4771 a: b
4772 --- !<tag:yaml.org,2002:seq>
4773 - a
4774 - b
4775 --- !<tag:yaml.org,2002:str> a b
4776 --- !<tag:yaml.org,2002:str> 'a: b'
4777 ---
4778 !<tag:yaml.org,2002:str> a: b
4779 --- !<tag:yaml.org,2002:set>
4780 a:
4781 b:
4782 --- !<tag:yaml.org,2002:set>
4783 a:
4784 --- !<tag:yaml.org,2002:seq>
4785 - !<tag:yaml.org,2002:int> 0
4786 - !<tag:yaml.org,2002:str> 1
4787 )");
4788 }
4789 
4790 
4791 //-----------------------------------------------------------------------------
4792 
4793 void sample_tag_directives()
4794 {
4795  const std::string yaml = R"(
4796 %TAG !m! !my-
4797 --- # Bulb here
4798 !m!light fluorescent
4799 ...
4800 %TAG !m! !meta-
4801 --- # Color here
4802 !m!light green
4803 )";
4804  ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(yaml));
4805  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(%TAG !m! !my-
4806 --- !m!light fluorescent
4807 ...
4808 %TAG !m! !meta-
4809 --- !m!light green
4810 )");
4811  // tags are not resolved by default. Use .resolve_tags() to
4812  // accomplish this:
4813  tree.resolve_tags();
4814  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(%TAG !m! !my-
4815 --- !<!my-light> fluorescent
4816 ...
4817 %TAG !m! !meta-
4818 --- !<!meta-light> green
4819 )");
4820  // see also tree.normalize_tags()
4821  // see also tree.normalize_tags_long()
4822 }
4823 
4824 
4825 //-----------------------------------------------------------------------------
4826 
4827 void sample_docs()
4828 {
4829  std::string yml = R"(---
4830 a: 0
4831 b: 1
4832 ---
4833 c: 2
4834 d: 3
4835 ---
4836 - 4
4837 - 5
4838 - 6
4839 - 7
4840 )";
4841  ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(yml));
4842  CHECK(ryml::emitrs_yaml<std::string>(tree) == yml);
4843 
4844  // iteration through docs
4845  {
4846  // using the node API
4847  const ryml::ConstNodeRef stream = tree.rootref();
4848  CHECK(stream.is_root());
4849  CHECK(stream.is_stream());
4850  CHECK(!stream.is_doc());
4851  CHECK(stream.num_children() == 3);
4852  for(const ryml::ConstNodeRef doc : stream.children())
4853  CHECK(doc.is_doc());
4854  CHECK(tree.docref(0).id() == stream.child(0).id());
4855  CHECK(tree.docref(1).id() == stream.child(1).id());
4856  CHECK(tree.docref(2).id() == stream.child(2).id());
4857  // equivalent: using the lower level index API
4858  const ryml::id_type stream_id = tree.root_id();
4859  CHECK(tree.is_root(stream_id));
4860  CHECK(tree.is_stream(stream_id));
4861  CHECK(!tree.is_doc(stream_id));

◆ sample_error_handler()

void sample::sample_error_handler ( )

demonstrates how to set a custom error handler for ryml

Definition at line 4884 of file quickstart.cpp.

◆ sample_global_allocator()

void sample::sample_global_allocator ( )

demonstrates how to set the global allocator for ryml

Definition at line 4996 of file quickstart.cpp.

5003 {
5004  std::vector<char> memory_pool = std::vector<char>(10u * 1024u); // 10KB
5005  size_t num_allocs = 0, alloc_size = 0, corr_size = 0;
5006  size_t num_deallocs = 0, dealloc_size = 0;
5007 
5008  void *allocate(size_t len)
5009  {
5010  void *ptr = &memory_pool[alloc_size];
5011  alloc_size += len;
5012  ++num_allocs;
5013  // ensure the ptr is aligned
5014  uintptr_t uptr = (uintptr_t)ptr;
5015  const uintptr_t align = alignof(max_align_t);
5016  if (uptr % align)
5017  {
5018  uintptr_t prev = uptr - (uptr % align);
5019  uintptr_t next = prev + align;
5020  uintptr_t corr = next - uptr;
5021  ptr = (void*)(((char*)ptr) + corr);
5022  corr_size += corr;
5023  }
5024  C4_CHECK_MSG(alloc_size + corr_size <= memory_pool.size(),
5025  "out of memory! requested=%zu+%zu available=%zu\n",
5026  alloc_size, corr_size, memory_pool.size());
5027  return ptr;
5028  }
5029 
5030  void free(void *mem, size_t len)
5031  {
5032  CHECK((char*)mem >= &memory_pool.front() && (char*)mem < &memory_pool.back());
5033  CHECK((char*)mem+len >= &memory_pool.front() && (char*)mem+len <= &memory_pool.back());
5034  dealloc_size += len;
5035  ++num_deallocs;
5036  // no need to free here
5037  }
5038 
5039  // bridge
5040  ryml::Callbacks callbacks()
5041  {
5042  return ryml::Callbacks(this, &GlobalAllocatorExample::s_allocate, &GlobalAllocatorExample::s_free, nullptr);
5043  }
5044  static void* s_allocate(size_t len, void* /*hint*/, void *this_)
5045  {
5046  return ((GlobalAllocatorExample*)this_)->allocate(len);
5047  }
5048  static void s_free(void *mem, size_t len, void *this_)
5049  {
a c-style callbacks class.
Definition: common.hpp:377

References sample::GlobalAllocatorExample::~GlobalAllocatorExample(), sample::GlobalAllocatorExample::alloc_size, sample::GlobalAllocatorExample::allocate(), sample::GlobalAllocatorExample::callbacks(), CHECK, sample::GlobalAllocatorExample::check_and_reset(), sample::GlobalAllocatorExample::corr_size, sample::GlobalAllocatorExample::dealloc_size, sample::GlobalAllocatorExample::free(), sample::GlobalAllocatorExample::memory_pool, sample::GlobalAllocatorExample::num_allocs, sample::GlobalAllocatorExample::num_deallocs, sample::GlobalAllocatorExample::s_allocate(), and sample::GlobalAllocatorExample::s_free().

◆ sample_per_tree_allocator()

void sample::sample_per_tree_allocator ( )

Definition at line 5119 of file quickstart.cpp.

5120  {foo: bar, money: pennys}])", &tree);
5121  CHECK(mem.alloc_size == size_before);
5122  CHECK(mem.num_allocs == 3);
5123  }
5124  mem.check_and_reset();
5125 
5126  // restore defaults.
5127  ryml::set_callbacks(defaults);
5128 }
5129 
5130 
5131 //-----------------------------------------------------------------------------
5132 
5133 /** @addtogroup doc_sample_helpers
5134  * @{ */
5135 
5136 /** an example for a per-tree memory allocator */
5137 struct PerTreeMemoryExample
5138 {
5139  std::vector<char> memory_pool = std::vector<char>(10u * 1024u); // 10KB
5140  size_t num_allocs = 0, alloc_size = 0;
5141  size_t num_deallocs = 0, dealloc_size = 0;
5142 
5143  ryml::Callbacks callbacks() const
5144  {

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

5157  {
5158  void *ptr = &memory_pool[alloc_size];
5159  alloc_size += len;
5160  ++num_allocs;
5161  if(C4_UNLIKELY(alloc_size > memory_pool.size()))
5162  {
5163  std::cerr << "out of memory! requested=" << alloc_size << " vs " << memory_pool.size() << " available" << std::endl;
5164  std::abort();
5165  }
5166  return ptr;
5167  }
5168 
5169  void free(void *mem, size_t len)
5170  {

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

5180  {
5181  check_and_reset();
5182  }
5183  void check_and_reset()
5184  {
5185  std::cout << "size: alloc=" << alloc_size << " dealloc=" << dealloc_size << std::endl;
5186  std::cout << "count: #allocs=" << num_allocs << " #deallocs=" << num_deallocs << std::endl;
5187  CHECK(num_allocs == num_deallocs);
5188  CHECK(alloc_size >= dealloc_size); // failure here means a double free
5189  CHECK(alloc_size == dealloc_size); // failure here means a leak
5190  num_allocs = 0;
5191  num_deallocs = 0;
5192  alloc_size = 0;
5193  dealloc_size = 0;
5194  }
5195 };
5196 /** @} */
5197 
5199 {
5200  PerTreeMemoryExample mrp;
5201  PerTreeMemoryExample mr1;
5202  PerTreeMemoryExample mr2;
5203 
5204  // the trees will use the memory in the resources above,
5205  // with each tree using a separate resource
5206  {
5207  // Watchout: ensure that the lifetime of the callbacks target
5208  // exceeds the lifetime of the tree.
5209  ryml::EventHandlerTree evt_handler(mrp.callbacks());
5210  ryml::Parser parser(&evt_handler);
5211  ryml::Tree tree1(mr1.callbacks());
5212  ryml::Tree tree2(mr2.callbacks());
5213 
5214  ryml::csubstr yml1 = "{a: b}";
5215  ryml::csubstr yml2 = "{c: d, e: f, g: [h, i, 0, 1, 2, 3]}";
5216 
5217  parse_in_arena(&parser, "file1.yml", yml1, &tree1);
5218  parse_in_arena(&parser, "file2.yml", yml2, &tree2);
5219  }
5220 
5221  CHECK(mrp.num_allocs == 0); // YAML depth not large enough to warrant a parser allocation
5222  CHECK(mr1.alloc_size <= mr2.alloc_size); // because yml2 has more nodes
5223 }
5224 
5225 
5226 //-----------------------------------------------------------------------------
5227 
5228 
5229 /** shows how to work around the static initialization order fiasco
5230  * when using a static-duration ryml tree
5231  * @see https://en.cppreference.com/w/cpp/language/siof */
5232 void sample_static_trees()
5233 {
5234  // Using static trees incurs may incur a static initialization
5235  // order problem. This happens because a default-constructed tree will
5236  // obtain the callbacks from the current global setting, which may
5237  // not have been initialized due to undefined static initialization
5238  // order:
5239  //
5240  //static ryml::Tree tree; // ERROR! depends on ryml::get_callbacks() which may not have been initialized.
5241  //
5242  // To work around the issue, declare static callbacks
5243  // to explicitly initialize the static tree:
5244  static ryml::Callbacks callbacks = {}; // use default callback members
5245  static ryml::Tree tree(callbacks); // OK
5246  // now you can use the tree as normal:
5247  ryml::parse_in_arena(R"(doe: "a deer, a female deer")", &tree);
5248  CHECK(tree["doe"].val() == "a deer, a female deer");
5249 }
5250 
5251 
5252 //-----------------------------------------------------------------------------
5253 //-----------------------------------------------------------------------------
5254 //-----------------------------------------------------------------------------
5255 /** demonstrates how to obtain the (zero-based) location of a node
5256  * from a recently parsed tree */
5258 {
5259  // NOTE: locations are zero-based. If you intend to show the
5260  // location to a human user, you may want to pre-increment the line
5261  // and column by 1.
5262  ryml::csubstr yaml = R"({
5263 aa: contents,
5264 foo: [one, [two, three]]
5265 })";
5266  // A parser is needed to track locations, and it has to be
5267  // explicitly set to do it. Location tracking is disabled by
5268  // default.
5269  ryml::ParserOptions opts = {};
5270  opts.locations(true); // enable locations, default is false
5271  ryml::EventHandlerTree evt_handler = {};
5272  ryml::Parser parser(&evt_handler, opts);
5273  CHECK(parser.options().locations());
5274  // When locations are enabled, the first task while parsing will
5275  // consist of building and caching (in the parser) a
5276  // source-to-node lookup structure to accelerate location lookups.
5277  //
5278  // The cost of building the location accelerator is linear in the
5279  // size of the source buffer. This increased cost is the reason
5280  // for the opt-in requirement. When locations are disabled there
5281  // is no cost.
5282  //
5283  // Building the location accelerator may trigger an allocation,
5284  // but this can and should be avoided by reserving prior to
5285  // parsing:
5286  parser.reserve_locations(50u); // reserve for 50 lines
5287  // Now the structure will be built during parsing:
5288  ryml::Tree tree = parse_in_arena(&parser, "source.yml", yaml);
5289  // After this, we are ready to query the location from the parser:
5290  ryml::Location loc = tree.rootref().location(parser);
5291  // As for the complexity of the query: for large buffers it is
5292  // O(log(numlines)). For short source buffers (30 lines and less),
5293  // it is O(numlines), as a plain linear search is faster in this
5294  // case.
5295  CHECK(parser.location_contents(loc).begins_with("{"));
5296  CHECK(loc.offset == 0u);
5297  CHECK(loc.line == 0u);
5298  CHECK(loc.col == 0u);
5299  // on the next call, we only pay O(log(numlines)) because the
5300  // rebuild is already available:
5301  loc = tree["aa"].location(parser);
5302  CHECK(parser.location_contents(loc).begins_with("aa"));
5303  CHECK(loc.offset == 2u);
5304  CHECK(loc.line == 1u);
5305  CHECK(loc.col == 0u);
5306  // KEYSEQ in flow style: points at the key
5307  loc = tree["foo"].location(parser);
5308  CHECK(parser.location_contents(loc).begins_with("foo"));
5309  CHECK(loc.offset == 16u);
5310  CHECK(loc.line == 2u);
5311  CHECK(loc.col == 0u);
5312  loc = tree["foo"][0].location(parser);
5313  CHECK(parser.location_contents(loc).begins_with("one"));
5314  CHECK(loc.line == 2u);
5315  CHECK(loc.col == 6u);
5316  // SEQ in flow style: location points at the opening '[' (there's no key)
5317  loc = tree["foo"][1].location(parser);
5318  CHECK(parser.location_contents(loc).begins_with("["));
5319  CHECK(loc.line == 2u);
5320  CHECK(loc.col == 11u);
5321  loc = tree["foo"][1][0].location(parser);
5322  CHECK(parser.location_contents(loc).begins_with("two"));
5323  CHECK(loc.line == 2u);
5324  CHECK(loc.col == 12u);
5325  loc = tree["foo"][1][1].location(parser);
5326  CHECK(parser.location_contents(loc).begins_with("three"));
5327  CHECK(loc.line == 2u);
5328  CHECK(loc.col == 17u);
5329  // NOTE. The parser locations always point at the latest buffer to
5330  // be parsed with the parser object, so they must be queried using
5331  // the corresponding latest tree to be parsed. This means that if
5332  // the parser is reused, earlier trees will loose the possibility
5333  // of querying for location. It is undefined behavior to query the
5334  // parser for the location of a node from an earlier tree:
5335  ryml::Tree docval = parse_in_arena(&parser, "docval.yaml", "this is a docval");
5336  // From now on, none of the locations from the previous tree can
5337  // be queried:
5338  //loc = tree.rootref().location(parser); // ERROR, undefined behavior
5339  loc = docval.rootref().location(parser); // OK. this is the latest tree from this parser
5340  CHECK(parser.location_contents(loc).begins_with("this is a docval"));
5341  CHECK(loc.line == 0u);
5342  CHECK(loc.col == 0u);
5343 
5344  // NOTES ABOUT CONTAINER LOCATIONS
5345  ryml::Tree tree2 = parse_in_arena(&parser, "containers.yaml", R"(
5346 a new: buffer
5347 to: be parsed
5348 map with key:
Location location(Parser const &p, id_type node) const
Get the location of a node from the parse used to parse this tree.
Definition: tree.cpp:1872
void sample_per_tree_allocator()
void sample_location_tracking()
demonstrates how to obtain the (zero-based) location of a node from a recently parsed tree
void sample_static_trees()
shows how to work around the static initialization order fiasco when using a static-duration ryml tre...
size_t offset
number of bytes from the beginning of the source buffer
Definition: common.hpp:300
ParserOptions & locations(bool enabled) noexcept
enable/disable source location tracking

References sample::PerTreeMemoryExample::check_and_reset().

Variable Documentation

◆ defaults

ryml::Callbacks sample::ErrorHandlerExample::defaults