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

Example code for every feature. More...

Modules

 Sample helpers
 Helper utilities used in the sample.
 

Functions

void sample_lightning_overview ()
 a lightning tour over most features see sample_quick_overview More...
 
void sample_quick_overview ()
 a brief tour over most features More...
 
void sample_substr ()
 demonstrate usage of ryml::substr and ryml::csubstr More...
 
void sample_parse_file ()
 demonstrate how to load a YAML file from disk to parse with ryml. More...
 
void sample_parse_in_place ()
 demonstrate in-place parsing of a mutable YAML source buffer. More...
 
void sample_parse_in_arena ()
 demonstrate parsing of a read-only YAML source buffer More...
 
void sample_parse_reuse_tree ()
 demonstrate reuse/modification of tree when parsing More...
 
void sample_parse_reuse_parser ()
 Demonstrates reuse of an existing parser. More...
 
void sample_parse_reuse_tree_and_parser ()
 for ultimate speed when parsing multiple times, reuse both the tree and parser More...
 
void sample_iterate_trees ()
 shows how to programatically iterate through trees More...
 
void sample_create_trees ()
 shows how to programatically create trees More...
 
void sample_tree_arena ()
 demonstrates explicit and implicit interaction with the tree's string arena. More...
 
void 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_empty_null_values ()
 Shows how to deal with empty/null values. More...
 
void sample_formatting ()
 ryml provides facilities for formatting/deformatting (imported from c4core into the ryml namespace). More...
 
void sample_base64 ()
 demonstrates how to read and write base64-encoded blobs. More...
 
void 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_user_container_types ()
 shows how to serialize/deserialize container types. More...
 
void sample_std_types ()
 demonstrates usage with the std implementations provided by ryml in the ryml_std.hpp header More...
 
void sample_float_precision ()
 control precision of serialized floats More...
 
void sample_emit_to_container ()
 demonstrates how to emit to a linear container of char More...
 
void sample_emit_to_stream ()
 demonstrates how to emit to a stream-like structure More...
 
void sample_emit_to_file ()
 demonstrates how to emit to a FILE* More...
 
void sample_emit_nested_node ()
 just like parsing into a nested node, you can also emit from a nested node. More...
 
void sample_style ()
 [experimental] query/set/modify node style to control formatting of emitted YAML code. More...
 
void sample_style_flow_ml_indent ()
 [experimental] control the indentation of emitted FLOW_ML containers More...
 
void sample_style_flow_ml_filter ()
 [experimental] set the parser to pick FLOW_SL even if the container being parsed is FLOW_ML More...
 
void sample_json ()
 shows how to parse and emit JSON. More...
 
void sample_anchors_and_aliases ()
 demonstrates usage with anchors and alias references. More...
 
void sample_anchors_and_aliases_create ()
 demonstrates how to use the API to programatically create anchors and aliases More...
 
void sample_tags ()
 
void sample_tag_directives ()
 
void sample_docs ()
 
void sample_error_handler ()
 demonstrates how to set a custom error handler for ryml More...
 
void sample_error_basic ()
 
void sample_error_parse ()
 
void sample_error_visit ()
 Visit errors happen when an error is triggered while reading from a node. More...
 
void sample_error_visit_location ()
 It is possible to obtain the YAML location from a visit error: when the tree is obtained from parsing YAML, the messages may be enriched by using a parser set to track the locations. More...
 
void sample_global_allocator ()
 demonstrates how to set the global allocator for ryml More...
 
void sample_per_tree_allocator ()
 
void sample_static_trees ()
 shows how to work around the static initialization order fiasco when using a static-duration ryml tree More...
 
void sample_location_tracking ()
 demonstrates how to obtain the (zero-based) location of a node from a recently parsed tree More...
 

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.11.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

◆ sample_lightning_overview()

void sample_lightning_overview ( )

a lightning tour over most features see sample_quick_overview

Definition at line 310 of file quickstart.cpp.

311 {
312  // Parse YAML code in place, potentially mutating the buffer:
313  char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}";
314  ryml::Tree tree = ryml::parse_in_place(yml_buf);
315 
316  // read from the tree:
317  ryml::NodeRef bar = tree["bar"];
318  CHECK(bar[0].val() == "2");
319  CHECK(bar[1].val() == "3");
320  CHECK(bar[0].val().str == yml_buf + 15); // points at the source buffer
321  CHECK(bar[1].val().str == yml_buf + 18);
322 
323  // deserializing:
324  int bar0 = 0, bar1 = 0;
325  bar[0] >> bar0;
326  bar[1] >> bar1;
327  CHECK(bar0 == 2);
328  CHECK(bar1 == 3);
329 
330  // serializing:
331  bar[0] << 10; // creates a string in the tree's arena
332  bar[1] << 11;
333  CHECK(bar[0].val() == "10");
334  CHECK(bar[1].val() == "11");
335 
336  // add nodes
337  bar.append_child() << 12; // see also operator= (explanation below)
338  CHECK(bar[2].val() == "12");
339 
340  // emit tree
341  // to std::string
342  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"({foo: 1,bar: [10,11,12],john: doe})");
343  std::cout << tree; // emit to ostream
344  ryml::emit_yaml(tree, stdout); // emit to FILE*
345 
346  // emit node
347  ryml::ConstNodeRef foo = tree["foo"];
348  // to std::string
349  CHECK(ryml::emitrs_yaml<std::string>(foo) == "foo: 1\n");
350  std::cout << foo; // emit node to ostream
351  ryml::emit_yaml(foo, stdout); // emit node to FILE*
352 }
Holds a pointer to an existing tree, and a node id.
Definition: node.hpp:849
A reference to a node in an existing yaml tree, offering a more convenient API than the index-based A...
Definition: node.hpp:989
NodeRef append_child()
Definition: node.hpp:1407
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:415
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)
a quick'n'dirty assertion to verify a predicate
Definition: quickstart.cpp:278

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

◆ sample_quick_overview()

void sample_quick_overview ( )

a brief tour over most features

Definition at line 358 of file quickstart.cpp.

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

References c4::yml::NodeRef::append_child(), c4::yml::Tree::arena(), ErrorHandlerExample::callbacks(), c4::yml::Tree::callbacks(), CHECK, ErrorHandlerExample::check_assertion_occurs(), 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_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 982 of file quickstart.cpp.

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

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

◆ sample_parse_file()

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

1790 {
1791  const char filename[] = "ryml_example.yml";
1792 
1793  // because this is a minimal sample, it assumes nothing on the
1794  // environment/OS (other than that it can read/write files). So we
1795  // create the file on the fly:
1796  file_put_contents(filename, ryml::csubstr(R"(
1797 foo: 1
1798 bar:
1799 - 2
1800 - 3
1801 )"));
1802 
1803  // now we can load it into a std::string (for example):
1804  {
1805  std::string contents = file_get_contents<std::string>(filename);
1806  ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(contents)); // immutable (csubstr) overload
1807  CHECK(tree["foo"].val() == "1");
1808  CHECK(tree["bar"][0].val() == "2");
1809  CHECK(tree["bar"][1].val() == "3");
1810  }
1811 
1812  // or we can use a vector<char> instead:
1813  {
1814  std::vector<char> contents = file_get_contents<std::vector<char>>(filename);
1815  ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(contents)); // mutable (csubstr) overload
1816  CHECK(tree["foo"].val() == "1");
1817  CHECK(tree["bar"][0].val() == "2");
1818  CHECK(tree["bar"][1].val() == "3");
1819  }
1820 
1821  // generally, any contiguous char container can be used with ryml,
1822  // provided that the ryml::substr/ryml::csubstr view can be
1823  // created out of it.
1824  //
1825  // ryml provides the overloads above for these two containers, but
1826  // if you are using another container it should be very easy (only
1827  // requires pointer and length).
1828 }
void file_put_contents(const char *filename, CharContainer const &v, const char *access="wb")
save a buffer into a file

References CHECK, 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_parse_in_place ( )

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

See also
Parse utilities

Definition at line 1835 of file quickstart.cpp.

1836 {
1837  // Like the name suggests, parse_in_place() directly mutates the
1838  // source buffer in place
1839  char src[] = "{foo: 1, bar: [2, 3]}"; // ryml can parse in situ
1840  ryml::substr srcview = src; // a mutable view to the source buffer
1841  ryml::Tree tree = ryml::parse_in_place(srcview); // you can also reuse the tree and/or parser
1842  ryml::ConstNodeRef root = tree.crootref(); // get a constant reference to the root
1843 
1844  CHECK(root.is_map());
1845  CHECK(root["foo"].is_keyval());
1846  CHECK(root["foo"].key() == "foo");
1847  CHECK(root["foo"].val() == "1");
1848  CHECK(root["bar"].is_seq());
1849  CHECK(root["bar"].has_key());
1850  CHECK(root["bar"].key() == "bar");
1851  CHECK(root["bar"][0].val() == "2");
1852  CHECK(root["bar"][1].val() == "3");
1853 
1854  // deserializing:
1855  int foo = 0, bar0 = 0, bar1 = 0;
1856  root["foo"] >> foo;
1857  root["bar"][0] >> bar0;
1858  root["bar"][1] >> bar1;
1859  CHECK(foo == 1);
1860  CHECK(bar0 == 2);
1861  CHECK(bar1 == 3);
1862 
1863  // after parsing, the tree holds views to the source buffer:
1864  CHECK(root["foo"].val().data() == src + strlen("{foo: "));
1865  CHECK(root["foo"].val().begin() == src + strlen("{foo: "));
1866  CHECK(root["foo"].val().end() == src + strlen("{foo: 1"));
1867  CHECK(root["foo"].val().is_sub(srcview)); // equivalent to the previous three assertions
1868  CHECK(root["bar"][0].val().data() == src + strlen("{foo: 1, bar: ["));
1869  CHECK(root["bar"][0].val().begin() == src + strlen("{foo: 1, bar: ["));
1870  CHECK(root["bar"][0].val().end() == src + strlen("{foo: 1, bar: [2"));
1871  CHECK(root["bar"][0].val().is_sub(srcview)); // equivalent to the previous three assertions
1872  CHECK(root["bar"][1].val().data() == src + strlen("{foo: 1, bar: [2, "));
1873  CHECK(root["bar"][1].val().begin() == src + strlen("{foo: 1, bar: [2, "));
1874  CHECK(root["bar"][1].val().end() == src + strlen("{foo: 1, bar: [2, 3"));
1875  CHECK(root["bar"][1].val().is_sub(srcview)); // equivalent to the previous three assertions
1876 
1877  // NOTE. parse_in_place() cannot accept ryml::csubstr
1878  // so this will cause a /compile/ error:
1879  ryml::csubstr csrcview = srcview; // ok, can assign from mutable to immutable
1880  //tree = ryml::parse_in_place(csrcview); // compile error, cannot mutate an immutable view
1881  (void)csrcview;
1882 }
ConstNodeRef crootref() const
Get the root as a ConstNodeRef.
Definition: tree.cpp:64

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_parse_in_arena ( )

demonstrate parsing of a read-only YAML source buffer

See also
Parse utilities

Definition at line 1889 of file quickstart.cpp.

1890 {
1891  // to parse read-only memory, ryml will copy first to the tree's
1892  // arena, and then parse the copied buffer:
1893  ryml::Tree tree = ryml::parse_in_arena("{foo: 1, bar: [2, 3]}");
1894  ryml::ConstNodeRef root = tree.crootref(); // get a const reference to the root
1895 
1896  CHECK(root.is_map());
1897  CHECK(root["foo"].is_keyval());
1898  CHECK(root["foo"].key() == "foo");
1899  CHECK(root["foo"].val() == "1");
1900  CHECK(root["bar"].is_seq());
1901  CHECK(root["bar"].has_key());
1902  CHECK(root["bar"].key() == "bar");
1903  CHECK(root["bar"][0].val() == "2");
1904  CHECK(root["bar"][1].val() == "3");
1905 
1906  // deserializing:
1907  int foo = 0, bar0 = 0, bar1 = 0;
1908  root["foo"] >> foo;
1909  root["bar"][0] >> bar0;
1910  root["bar"][1] >> bar1;
1911  CHECK(foo == 1);
1912  CHECK(bar0 == 2);
1913  CHECK(bar1 == 3);
1914 
1915  // NOTE. parse_in_arena() cannot accept ryml::substr. Overloads
1916  // receiving substr buffers are declared, but intentionally left
1917  // undefined, so this will cause a /linker/ error
1918  char src[] = "{foo: is it really true}";
1919  ryml::substr srcview = src;
1920  //tree = ryml::parse_in_place(srcview); // linker error, overload intentionally undefined
1921 
1922  // If you really intend to parse a mutable buffer in the arena,
1923  // then simply convert it to immutable prior to calling
1924  // parse_in_arena():
1925  ryml::csubstr csrcview = srcview; // assigning from src also works
1926  tree = ryml::parse_in_arena(csrcview); // OK! csrcview is immutable
1927  CHECK(tree["foo"].val() == "is it really true");
1928 }

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_parse_reuse_tree ( )

demonstrate reuse/modification of tree when parsing

See also
Parse utilities

Definition at line 1935 of file quickstart.cpp.

1936 {
1937  ryml::Tree tree;
1938 
1939  // it will always be faster if the tree's size is conveniently reserved:
1940  tree.reserve(30); // reserve 30 nodes (good enough for this sample)
1941  // if you are using the tree's arena to serialize data,
1942  // then reserve also the arena's size:
1943  tree.reserve_arena(256); // reserve 256 characters (good enough for this sample)
1944 
1945  // now parse into the tree:
1946  ryml::csubstr yaml = R"(foo: 1
1947 bar: [2, 3]
1948 )";
1949  ryml::parse_in_arena(yaml, &tree);
1950 
1951  ryml::ConstNodeRef root = tree.crootref();
1952  CHECK(root.num_children() == 2);
1953  CHECK(root.is_map());
1954  CHECK(root["foo"].is_keyval());
1955  CHECK(root["foo"].key() == "foo");
1956  CHECK(root["foo"].val() == "1");
1957  CHECK(root["bar"].is_seq());
1958  CHECK(root["bar"].has_key());
1959  CHECK(root["bar"].key() == "bar");
1960  CHECK(root["bar"][0].val() == "2");
1961  CHECK(root["bar"][1].val() == "3");
1962  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(foo: 1
1963 bar: [2,3]
1964 )");
1965 
1966  // WATCHOUT: parsing into an existing tree will APPEND to it:
1967  ryml::parse_in_arena("{foo2: 12, bar2: [22, 32]}", &tree);
1968  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(foo: 1
1969 bar: [2,3]
1970 foo2: 12
1971 bar2: [22,32]
1972 )");
1973  CHECK(root.num_children() == 4);
1974  CHECK(root["foo2"].is_keyval());
1975  CHECK(root["foo2"].key() == "foo2");
1976  CHECK(root["foo2"].val() == "12");
1977  CHECK(root["bar2"].is_seq());
1978  CHECK(root["bar2"].has_key());
1979  CHECK(root["bar2"].key() == "bar2");
1980  CHECK(root["bar2"][0].val() == "22");
1981  CHECK(root["bar2"][1].val() == "32");
1982 
1983  // clear first before parsing into an existing tree.
1984  tree.clear();
1985  tree.clear_arena(); // you may or may not want to clear the arena
1986  ryml::parse_in_arena("- a\n- b\n- {x0: 1, x1: 2}", &tree);
1987  CHECK(ryml::emitrs_yaml<std::string>(tree) == "- a\n- b\n- {x0: 1,x1: 2}\n");
1988  CHECK(root.is_seq());
1989  CHECK(root[0].val() == "a");
1990  CHECK(root[1].val() == "b");
1991  CHECK(root[2].is_map());
1992  CHECK(root[2]["x0"].val() == "1");
1993  CHECK(root[2]["x1"].val() == "2");
1994 
1995  // we can parse directly into a node nested deep in an existing tree:
1996  ryml::NodeRef mroot = tree.rootref(); // modifiable root
1997  ryml::parse_in_arena("champagne: Dom Perignon\ncoffee: Arabica", mroot.append_child());
1998  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(- a
1999 - b
2000 - {x0: 1,x1: 2}
2001 - champagne: Dom Perignon
2002  coffee: Arabica
2003 )");
2004  CHECK(root.is_seq());
2005  CHECK(root[0].val() == "a");
2006  CHECK(root[1].val() == "b");
2007  CHECK(root[2].is_map());
2008  CHECK(root[2]["x0"].val() == "1");
2009  CHECK(root[2]["x1"].val() == "2");
2010  CHECK(root[3].is_map());
2011  CHECK(root[3]["champagne"].val() == "Dom Perignon");
2012  CHECK(root[3]["coffee"].val() == "Arabica");
2013 
2014  // watchout: to add to an existing node within a map, the node's
2015  // key must be separately set first:
2016  ryml::NodeRef more = mroot[3].append_child({ryml::KEYMAP, "more"});
2017  ryml::NodeRef beer = mroot[3].append_child({ryml::KEYSEQ, "beer"});
2018  ryml::NodeRef always = mroot[3].append_child({ryml::KEY, "always"});
2019  ryml::parse_in_arena("{vinho verde: Soalheiro, vinho tinto: Redoma 2017}", more);
2020  ryml::parse_in_arena("- Rochefort 10\n- Busch\n- Leffe Rituel", beer);
2021  ryml::parse_in_arena("lots\nof\nwater", always);
2022  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(- a
2023 - b
2024 - {x0: 1,x1: 2}
2025 - champagne: Dom Perignon
2026  coffee: Arabica
2027  more:
2028  vinho verde: Soalheiro
2029  vinho tinto: Redoma 2017
2030  beer:
2031  - Rochefort 10
2032  - Busch
2033  - Leffe Rituel
2034  always: lots of water
2035 )");
2036 
2037  // can append at the top:
2038  ryml::parse_in_arena("- foo\n- bar\n- baz\n- bat", mroot);
2039  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(- a
2040 - b
2041 - {x0: 1,x1: 2}
2042 - champagne: Dom Perignon
2043  coffee: Arabica
2044  more:
2045  vinho verde: Soalheiro
2046  vinho tinto: Redoma 2017
2047  beer:
2048  - Rochefort 10
2049  - Busch
2050  - Leffe Rituel
2051  always: lots of water
2052 - foo
2053 - bar
2054 - baz
2055 - bat
2056 )");
2057 
2058  // or nested:
2059  ryml::parse_in_arena("[Kasteel Donker]", beer);
2060  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(- a
2061 - b
2062 - {x0: 1,x1: 2}
2063 - champagne: Dom Perignon
2064  coffee: Arabica
2065  more:
2066  vinho verde: Soalheiro
2067  vinho tinto: Redoma 2017
2068  beer:
2069  - Rochefort 10
2070  - Busch
2071  - Leffe Rituel
2072  - Kasteel Donker
2073  always: lots of water
2074 - foo
2075 - bar
2076 - baz
2077 - bat
2078 )");
2079 }
void clear()
clear the tree and zero every node
Definition: tree.cpp:332
void reserve_arena(size_t arena_cap=RYML_DEFAULT_TREE_ARENA_CAPACITY)
ensure the tree's internal string arena is at least the given capacity
Definition: tree.hpp:940
void reserve(id_type node_capacity=RYML_DEFAULT_TREE_CAPACITY)
Definition: tree.cpp:294
void clear_arena()
Definition: tree.hpp:277
@ 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_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 2087 of file quickstart.cpp.

2088 {
2089  ryml::EventHandlerTree evt_handler = {};
2090  ryml::Parser parser(&evt_handler);
2091 
2092  // it is also advised to reserve the parser depth
2093  // to the expected depth of the data tree:
2094  parser.reserve_stack(10); // uses small storage optimization
2095  // defaulting to 16 depth, so this
2096  // instruction is a no-op, and the stack
2097  // will located in the parser object.
2098  parser.reserve_stack(20); // But this will cause an allocation
2099  // because it is above 16.
2100 
2101  ryml::Tree champagnes = parse_in_arena(&parser, "champagnes.yml", "[Dom Perignon, Gosset Grande Reserve, Jacquesson 742]");
2102  CHECK(ryml::emitrs_yaml<std::string>(champagnes) == "[Dom Perignon,Gosset Grande Reserve,Jacquesson 742]");
2103 
2104  ryml::Tree beers = parse_in_arena(&parser, "beers.yml", "[Rochefort 10, Busch, Leffe Rituel, Kasteel Donker]");
2105  CHECK(ryml::emitrs_yaml<std::string>(beers) == "[Rochefort 10,Busch,Leffe Rituel,Kasteel Donker]");
2106 }

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

◆ sample_parse_reuse_tree_and_parser()

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

2115 {
2116  ryml::Tree tree;
2117  // it will always be faster if the tree's size is conveniently reserved:
2118  tree.reserve(30); // reserve 30 nodes (good enough for this sample)
2119  // if you are using the tree's arena to serialize data,
2120  // then reserve also the arena's size:
2121  tree.reserve(256); // reserve 256 characters (good enough for this sample)
2122 
2123  ryml::EventHandlerTree evt_handler;
2124  ryml::Parser parser(&evt_handler);
2125  // it is also advised to reserve the parser depth
2126  // to the expected depth of the data tree:
2127  parser.reserve_stack(10); // the parser uses small storage
2128  // optimization defaulting to 16 depth,
2129  // so this instruction is a no-op, and
2130  // the stack will be located in the
2131  // parser object.
2132  parser.reserve_stack(20); // But this will cause an allocation
2133  // because it is above 16.
2134 
2135  ryml::csubstr champagnes = "- Dom Perignon\n- Gosset Grande Reserve\n- Jacquesson 742";
2136  ryml::csubstr beers = "- Rochefort 10\n- Busch\n- Leffe Rituel\n- Kasteel Donker";
2137  ryml::csubstr wines = "- Soalheiro\n- Niepoort Redoma 2017\n- Vina Esmeralda";
2138 
2139  parse_in_arena(&parser, "champagnes.yml", champagnes, &tree);
2140  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(- Dom Perignon
2141 - Gosset Grande Reserve
2142 - Jacquesson 742
2143 )");
2144 
2145  // watchout: this will APPEND to the given tree:
2146  parse_in_arena(&parser, "beers.yml", beers, &tree);
2147  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(- Dom Perignon
2148 - Gosset Grande Reserve
2149 - Jacquesson 742
2150 - Rochefort 10
2151 - Busch
2152 - Leffe Rituel
2153 - Kasteel Donker
2154 )");
2155 
2156  // if you don't wish to append, clear the tree first:
2157  tree.clear();
2158  parse_in_arena(&parser, "wines.yml", wines, &tree);
2159  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(- Soalheiro
2160 - Niepoort Redoma 2017
2161 - Vina Esmeralda
2162 )");
2163 }

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_iterate_trees ( )

shows how to programatically iterate through trees

See also
Tree utilities
Node classes

Definition at line 2172 of file quickstart.cpp.

2173 {
2174  const ryml::Tree tree = ryml::parse_in_arena(R"(doe: "a deer, a female deer"
2175 ray: "a drop of golden sun"
2176 pi: 3.14159
2177 xmas: true
2178 french-hens: 3
2179 calling-birds:
2180  - huey
2181  - dewey
2182  - louie
2183  - fred
2184 xmas-fifth-day:
2185  calling-birds: four
2186  french-hens: 3
2187  golden-rings: 5
2188  partridges:
2189  count: 1
2190  location: a pear tree
2191  turtle-doves: two
2192 cars: GTO
2193 )");
2194  ryml::ConstNodeRef root = tree.crootref();
2195 
2196  // iterate children
2197  {
2198  std::vector<ryml::csubstr> keys, vals; // to store all the root-level keys, vals
2199  for(ryml::ConstNodeRef n : root.children())
2200  {
2201  keys.emplace_back(n.key());
2202  vals.emplace_back(n.has_val() ? n.val() : ryml::csubstr{});
2203  }
2204  CHECK(keys[0] == "doe");
2205  CHECK(vals[0] == "a deer, a female deer");
2206  CHECK(keys[1] == "ray");
2207  CHECK(vals[1] == "a drop of golden sun");
2208  CHECK(keys[2] == "pi");
2209  CHECK(vals[2] == "3.14159");
2210  CHECK(keys[3] == "xmas");
2211  CHECK(vals[3] == "true");
2212  CHECK(root[5].has_key());
2213  CHECK(root[5].is_seq());
2214  CHECK(root[5].key() == "calling-birds");
2215  CHECK(!root[5].has_val()); // it is a map, so not a val
2216  //CHECK(root[5].val() == ""); // ERROR! node does not have a val.
2217  CHECK(keys[5] == "calling-birds");
2218  CHECK(vals[5] == "");
2219  }
2220 
2221  // iterate siblings
2222  {
2223  size_t count = 0;
2224  ryml::csubstr calling_birds[] = {"huey", "dewey", "louie", "fred"};
2225  for(ryml::ConstNodeRef n : root["calling-birds"][2].siblings())
2226  CHECK(n.val() == calling_birds[count++]);
2227  CHECK(count == 4u);
2228  }
2229 }

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_create_trees ( )

shows how to programatically create trees

See also
Tree utilities
Node classes

Definition at line 2238 of file quickstart.cpp.

2239 {
2240  ryml::NodeRef doe;
2241  CHECK(doe.invalid()); // it's pointing at nowhere
2242 
2243  ryml::Tree tree;
2244  ryml::NodeRef root = tree.rootref();
2245  root |= ryml::MAP; // mark root as a map
2246  doe = root["doe"];
2247  CHECK(!doe.invalid()); // it's now pointing at the tree
2248  CHECK(doe.is_seed()); // but the tree has nothing there, so this is only a seed
2249 
2250  // set the value of the node
2251  const char a_deer[] = "a deer, a female deer";
2252  doe = a_deer;
2253  // now the node really exists in the tree, and this ref is no
2254  // longer a seed:
2255  CHECK(!doe.is_seed());
2256  // WATCHOUT for lifetimes:
2257  CHECK(doe.val().str == a_deer); // it is pointing at the initial string
2258  // If you need to avoid lifetime dependency, serialize the data:
2259  {
2260  std::string a_drop = "a drop of golden sun";
2261  // this will copy the string to the tree's arena:
2262  // (see the serialization samples below)
2263  root["ray"] << a_drop;
2264  // and now you can modify the original string without changing
2265  // the tree:
2266  a_drop[0] = 'Z';
2267  a_drop[1] = 'Z';
2268  }
2269  CHECK(root["ray"].val() == "a drop of golden sun");
2270 
2271  // etc.
2272  root["pi"] << ryml::fmt::real(3.141592654, 5);
2273  root["xmas"] << ryml::fmt::boolalpha(true);
2274  root["french-hens"] << 3;
2275  ryml::NodeRef calling_birds = root["calling-birds"];
2276  calling_birds |= ryml::SEQ;
2277  calling_birds.append_child() = "huey";
2278  calling_birds.append_child() = "dewey";
2279  calling_birds.append_child() = "louie";
2280  calling_birds.append_child() = "fred";
2281  ryml::NodeRef xmas5 = root["xmas-fifth-day"];
2282  xmas5 |= ryml::MAP;
2283  xmas5["calling-birds"] = "four";
2284  xmas5["french-hens"] << 3;
2285  xmas5["golden-rings"] << 5;
2286  xmas5["partridges"] |= ryml::MAP;
2287  xmas5["partridges"]["count"] << 1;
2288  xmas5["partridges"]["location"] = "a pear tree";
2289  xmas5["turtle-doves"] = "two";
2290  root["cars"] = "GTO";
2291 
2292  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(doe: 'a deer, a female deer'
2293 ray: a drop of golden sun
2294 pi: 3.14159
2295 xmas: true
2296 french-hens: 3
2297 calling-birds:
2298  - huey
2299  - dewey
2300  - louie
2301  - fred
2302 xmas-fifth-day:
2303  calling-birds: four
2304  french-hens: 3
2305  golden-rings: 5
2306  partridges:
2307  count: 1
2308  location: a pear tree
2309  turtle-doves: two
2310 cars: GTO
2311 )");
2312 }
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_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 2319 of file quickstart.cpp.

2320 {
2321  // mutable buffers are parsed in situ:
2322  {
2323  char buf[] = "[a, b, c, d]";
2324  ryml::substr yml = buf;
2325  ryml::Tree tree = ryml::parse_in_place(yml);
2326  // notice the arena is empty:
2327  CHECK(tree.arena().empty());
2328  // and the tree is pointing at the original buffer:
2329  ryml::NodeRef root = tree.rootref();
2330  CHECK(root[0].val().is_sub(yml));
2331  CHECK(root[1].val().is_sub(yml));
2332  CHECK(root[2].val().is_sub(yml));
2333  CHECK(root[3].val().is_sub(yml));
2334  CHECK(yml.is_super(root[0].val()));
2335  CHECK(yml.is_super(root[1].val()));
2336  CHECK(yml.is_super(root[2].val()));
2337  CHECK(yml.is_super(root[3].val()));
2338  }
2339 
2340  // when parsing immutable buffers, the buffer is first copied to the
2341  // tree's arena; the copy in the arena is then the buffer which is
2342  // actually parsed
2343  {
2344  ryml::csubstr yml = "[a, b, c, d]";
2345  ryml::Tree tree = ryml::parse_in_arena(yml);
2346  // notice the buffer was copied to the arena:
2347  CHECK(tree.arena().data() != yml.data());
2348  CHECK(tree.arena() == yml);
2349  // and the tree is pointing at the arena instead of to the
2350  // original buffer:
2351  ryml::NodeRef root = tree.rootref();
2352  ryml::csubstr arena = tree.arena();
2353  CHECK(root[0].val().is_sub(arena));
2354  CHECK(root[1].val().is_sub(arena));
2355  CHECK(root[2].val().is_sub(arena));
2356  CHECK(root[3].val().is_sub(arena));
2357  CHECK(arena.is_super(root[0].val()));
2358  CHECK(arena.is_super(root[1].val()));
2359  CHECK(arena.is_super(root[2].val()));
2360  CHECK(arena.is_super(root[3].val()));
2361  }
2362 
2363  // the arena is also used when the data is serialized to string
2364  // with NodeRef::operator<<(): mutable buffer
2365  {
2366  char buf[] = "[a, b, c, d]"; // mutable
2367  ryml::substr yml = buf;
2368  ryml::Tree tree = ryml::parse_in_place(yml);
2369  // notice the arena is empty:
2370  CHECK(tree.arena().empty());
2371  ryml::NodeRef root = tree.rootref();
2372 
2373  // serialize an integer, and mutate the tree
2374  CHECK(root[2].val() == "c");
2375  CHECK(root[2].val().is_sub(yml)); // val is first pointing at the buffer
2376  root[2] << 12345;
2377  CHECK(root[2].val() == "12345");
2378  CHECK(root[2].val().is_sub(tree.arena())); // now val is pointing at the arena
2379  // notice the serialized string was appended to the tree's arena:
2380  CHECK(tree.arena() == "12345");
2381 
2382  // serialize an integer, and mutate the tree
2383  CHECK(root[3].val() == "d");
2384  CHECK(root[3].val().is_sub(yml)); // val is first pointing at the buffer
2385  root[3] << 67890;
2386  CHECK(root[3].val() == "67890");
2387  CHECK(root[3].val().is_sub(tree.arena())); // now val is pointing at the arena
2388  // notice the serialized string was appended to the tree's arena:
2389  CHECK(tree.arena() == "1234567890");
2390  }
2391  // the arena is also used when the data is serialized to string
2392  // with NodeRef::operator<<(): immutable buffer
2393  {
2394  ryml::csubstr yml = "[a, b, c, d]"; // immutable
2395  ryml::Tree tree = ryml::parse_in_arena(yml);
2396  // notice the buffer was copied to the arena:
2397  CHECK(tree.arena().data() != yml.data());
2398  CHECK(tree.arena() == yml);
2399  ryml::NodeRef root = tree.rootref();
2400 
2401  // serialize an integer, and mutate the tree
2402  CHECK(root[2].val() == "c");
2403  root[2] << 12345; // serialize an integer
2404  CHECK(root[2].val() == "12345");
2405  // notice the serialized string was appended to the tree's arena:
2406  // notice also the previous values remain there.
2407  // RYML DOES NOT KEEP TRACK OF REFERENCES TO THE ARENA.
2408  CHECK(tree.arena() == "[a, b, c, d]12345");
2409  // old values: --------------^
2410 
2411  // serialize an integer, and mutate the tree
2412  root[3] << 67890;
2413  CHECK(root[3].val() == "67890");
2414  // notice the serialized string was appended to the tree's arena:
2415  // notice also the previous values remain there.
2416  // RYML DOES NOT KEEP TRACK OF REFERENCES TO THE ARENA.
2417  CHECK(tree.arena() == "[a, b, c, d]1234567890");
2418  // old values: --------------^ ---^^^^^
2419  }
2420 
2421  // to_arena(): directly serialize values to the arena:
2422  {
2423  ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2424  ryml::csubstr c10 = tree.to_arena(10101010);
2425  CHECK(c10 == "10101010");
2426  CHECK(c10.is_sub(tree.arena()));
2427  CHECK(tree.arena() == "{a: b}10101010");
2428  CHECK(tree.key(1) == "a");
2429  CHECK(tree.val(1) == "b");
2430  tree.set_val(1, c10);
2431  CHECK(tree.val(1) == c10);
2432  // and you can also do it through a node:
2433  ryml::NodeRef root = tree.rootref();
2434  root["a"].set_val_serialized(2222);
2435  CHECK(root["a"].val() == "2222");
2436  CHECK(tree.arena() == "{a: b}101010102222");
2437  }
2438 
2439  // copy_to_arena(): manually copy a string to the arena:
2440  {
2441  ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2442  ryml::csubstr mystr = "Gosset Grande Reserve";
2443  ryml::csubstr copied = tree.copy_to_arena(mystr);
2444  CHECK(!copied.overlaps(mystr));
2445  CHECK(copied == mystr);
2446  CHECK(tree.arena() == "{a: b}Gosset Grande Reserve");
2447  }
2448 
2449  // alloc_arena(): allocate a buffer from the arena:
2450  {
2451  ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2452  ryml::csubstr mystr = "Gosset Grande Reserve";
2453  ryml::substr copied = tree.alloc_arena(mystr.size());
2454  CHECK(!copied.overlaps(mystr));
2455  memcpy(copied.str, mystr.str, mystr.len);
2456  CHECK(copied == mystr);
2457  CHECK(tree.arena() == "{a: b}Gosset Grande Reserve");
2458  }
2459 
2460  // reserve_arena(): ensure the arena has a certain size to avoid reallocations
2461  {
2462  ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2463  CHECK(tree.arena().size() == strlen("{a: b}"));
2464  tree.reserve_arena(100);
2465  CHECK(tree.arena_capacity() >= 100);
2466  CHECK(tree.arena().size() == strlen("{a: b}"));
2467  tree.to_arena(123456);
2468  CHECK(tree.arena().first(12) == "{a: b}123456");
2469  }
2470 }
size_t set_val_serialized(T const &v)
Definition: node.hpp:1256
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:928
csubstr to_arena(T const &a)
serialize the given variable to the tree's arena, growing it as needed to accomodate the serializatio...
Definition: tree.hpp:878
size_t arena_capacity() const
get the current capacity of the tree's internal arena
Definition: tree.hpp:845
void set_val(id_type node, csubstr val)
Definition: tree.hpp:587
csubstr const & val(id_type node) const
Definition: tree.hpp:388
substr copy_to_arena(csubstr s)
copy the given string to the tree's arena, growing the arena by the required size.
Definition: tree.hpp:899

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

2485 {
2486  ryml::Tree tree;
2487  CHECK(tree.arena().empty());
2488  CHECK(tree.to_arena('a') == "a"); CHECK(tree.arena() == "a");
2489  CHECK(tree.to_arena("bcde") == "bcde"); CHECK(tree.arena() == "abcde");
2490  CHECK(tree.to_arena(unsigned(0)) == "0"); CHECK(tree.arena() == "abcde0");
2491  CHECK(tree.to_arena(int(1)) == "1"); CHECK(tree.arena() == "abcde01");
2492  CHECK(tree.to_arena(uint8_t(0)) == "0"); CHECK(tree.arena() == "abcde010");
2493  CHECK(tree.to_arena(uint16_t(1)) == "1"); CHECK(tree.arena() == "abcde0101");
2494  CHECK(tree.to_arena(uint32_t(2)) == "2"); CHECK(tree.arena() == "abcde01012");
2495  CHECK(tree.to_arena(uint64_t(3)) == "3"); CHECK(tree.arena() == "abcde010123");
2496  CHECK(tree.to_arena(int8_t( 4)) == "4"); CHECK(tree.arena() == "abcde0101234");
2497  CHECK(tree.to_arena(int8_t(-4)) == "-4"); CHECK(tree.arena() == "abcde0101234-4");
2498  CHECK(tree.to_arena(int16_t( 5)) == "5"); CHECK(tree.arena() == "abcde0101234-45");
2499  CHECK(tree.to_arena(int16_t(-5)) == "-5"); CHECK(tree.arena() == "abcde0101234-45-5");
2500  CHECK(tree.to_arena(int32_t( 6)) == "6"); CHECK(tree.arena() == "abcde0101234-45-56");
2501  CHECK(tree.to_arena(int32_t(-6)) == "-6"); CHECK(tree.arena() == "abcde0101234-45-56-6");
2502  CHECK(tree.to_arena(int64_t( 7)) == "7"); CHECK(tree.arena() == "abcde0101234-45-56-67");
2503  CHECK(tree.to_arena(int64_t(-7)) == "-7"); CHECK(tree.arena() == "abcde0101234-45-56-67-7");
2504  CHECK(tree.to_arena((void*)1) == "0x1"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x1");
2505  CHECK(tree.to_arena(float(0.124)) == "0.124"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.124");
2506  CHECK(tree.to_arena(double(0.234)) == "0.234"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.234");
2507 
2508  // write boolean values - see also sample_formatting()
2509  CHECK(tree.to_arena(bool(true)) == "1"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.2341");
2510  CHECK(tree.to_arena(bool(false)) == "0"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410");
2511  CHECK(tree.to_arena(c4::fmt::boolalpha(true)) == "true"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410true");
2512  CHECK(tree.to_arena(c4::fmt::boolalpha(false)) == "false"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse");
2513 
2514  // write special float values
2515  // see also sample_float_precision()
2516  const float fnan = std::numeric_limits<float >::quiet_NaN();
2517  const double dnan = std::numeric_limits<double>::quiet_NaN();
2518  const float finf = std::numeric_limits<float >::infinity();
2519  const double dinf = std::numeric_limits<double>::infinity();
2520  CHECK(tree.to_arena( finf) == ".inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf");
2521  CHECK(tree.to_arena( dinf) == ".inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf");
2522  CHECK(tree.to_arena(-finf) == "-.inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf");
2523  CHECK(tree.to_arena(-dinf) == "-.inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf");
2524  CHECK(tree.to_arena( fnan) == ".nan"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf.nan");
2525  CHECK(tree.to_arena( dnan) == ".nan"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf.nan.nan");
2526 
2527  // read special float values
2528  // see also sample_float_precision()
2529  C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wfloat-equal");
2530  tree = ryml::parse_in_arena(R"({ninf: -.inf, pinf: .inf, nan: .nan})");
2531  float f = 0.f;
2532  double d = 0.;
2533  CHECK(f == 0.f);
2534  CHECK(d == 0.);
2535  tree["ninf"] >> f; CHECK(f == -finf);
2536  tree["ninf"] >> d; CHECK(d == -dinf);
2537  tree["pinf"] >> f; CHECK(f == finf);
2538  tree["pinf"] >> d; CHECK(d == dinf);
2539  tree["nan" ] >> f; CHECK(std::isnan(f));
2540  tree["nan" ] >> d; CHECK(std::isnan(d));
2541  C4_SUPPRESS_WARNING_GCC_CLANG_POP
2542 
2543  // value overflow detection:
2544  // (for integral types only)
2545  {
2546  // we will be detecting errors below, so we use this sample helper
2547  ScopedErrorHandlerExample err = {};
2548  ryml::Tree t(err.callbacks()); // instantiate with the error-detecting callbacks
2549  // create a simple tree with an int value
2550  ryml::parse_in_arena(R"({val: 258})", &t);
2551  // by default, overflow is not detected:
2552  uint8_t valu8 = 0;
2553  int8_t vali8 = 0;
2554  t["val"] >> valu8; CHECK(valu8 == 2); // not 257; it wrapped around
2555  t["val"] >> vali8; CHECK(vali8 == 2); // not 257; it wrapped around
2556  // ...but there are facilities to detect overflow
2557  CHECK(ryml::overflows<uint8_t>(t["val"].val()));
2558  CHECK(ryml::overflows<int8_t>(t["val"].val()));
2559  CHECK( ! ryml::overflows<int16_t>(t["val"].val()));
2560  // and there is a format helper
2561  CHECK(err.check_error_occurs([&]{
2562  auto checku8 = ryml::fmt::overflow_checked(valu8); // need to declare the wrapper type before using it with >>
2563  t["val"] >> checku8; // this will cause an error
2564  }));
2565  CHECK(err.check_error_occurs([&]{
2566  auto checki8 = ryml::fmt::overflow_checked(vali8); // need to declare the wrapper type before using it with >>
2567  t["val"] >> checki8; // this will cause an error
2568  }));
2569  }
2570 }

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

◆ sample_empty_null_values()

void sample_empty_null_values ( )

Shows how to deal with empty/null values.

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

Definition at line 2577 of file quickstart.cpp.

2578 {
2579  // reading empty/null values - see also sample_formatting()
2580  ryml::Tree tree = ryml::parse_in_arena(R"(
2581 plain:
2582 squoted: ''
2583 dquoted: ""
2584 literal: |
2585 folded: >
2586 all_null: [~, null, Null, NULL]
2587 non_null: [nULL, non_null, non null, null it is not]
2588 )");
2589  // first, remember that .has_val() is a structural predicate
2590  // indicating the node is a leaf, and not a container.
2591  CHECK(tree["plain"].has_val()); // has a val, even if it's empty!
2592  CHECK(tree["squoted"].has_val());
2593  CHECK(tree["dquoted"].has_val());
2594  CHECK(tree["literal"].has_val());
2595  CHECK(tree["folded"].has_val());
2596  CHECK( ! tree["all_null"].has_val());
2597  CHECK( ! tree["non_null"].has_val());
2598  // In essence, has_val() is the logical opposite of is_container()
2599  CHECK( ! tree["plain"].is_container());
2600  CHECK( ! tree["squoted"].is_container());
2601  CHECK( ! tree["dquoted"].is_container());
2602  CHECK( ! tree["literal"].is_container());
2603  CHECK( ! tree["folded"].is_container());
2604  CHECK(tree["all_null"].is_container());
2605  CHECK(tree["non_null"].is_container());
2606  //
2607  // Right. How about the contents of each val?
2608  //
2609  // all of these scalars have zero-length:
2610  CHECK(tree["plain"].val().len == 0);
2611  CHECK(tree["squoted"].val().len == 0);
2612  CHECK(tree["dquoted"].val().len == 0);
2613  CHECK(tree["literal"].val().len == 0);
2614  CHECK(tree["folded"].val().len == 0);
2615  // but only the empty scalar has null string:
2616  CHECK(tree["plain"].val().str == nullptr);
2617  CHECK(tree["squoted"].val().str != nullptr);
2618  CHECK(tree["dquoted"].val().str != nullptr);
2619  CHECK(tree["literal"].val().str != nullptr);
2620  CHECK(tree["folded"].val().str != nullptr);
2621  // likewise, scalar comparison to nullptr has the same results:
2622  // (remember that .val() gives you the scalar value, node must
2623  // have a val, ie must be a leaf node, not a container)
2624  CHECK(tree["plain"].val() == nullptr);
2625  CHECK(tree["squoted"].val() != nullptr);
2626  CHECK(tree["dquoted"].val() != nullptr);
2627  CHECK(tree["literal"].val() != nullptr);
2628  CHECK(tree["folded"].val() != nullptr);
2629  // the tree and node classes provide the corresponding predicate
2630  // functions .key_is_null() and .val_is_null().
2631  // (note that these functions have the same preconditions as .val(),
2632  // because they need get the val to look into its contents)
2633  CHECK(tree["plain"].val_is_null());
2634  CHECK( ! tree["squoted"].val_is_null());
2635  CHECK( ! tree["dquoted"].val_is_null());
2636  CHECK( ! tree["literal"].val_is_null());
2637  CHECK( ! tree["folded"].val_is_null());
2638  // matching to null is case-sensitive. only the cases shown here
2639  // match to null:
2640  for(ryml::ConstNodeRef child : tree["all_null"].children())
2641  {
2642  CHECK(child.val() != nullptr); // it is pointing at a string, so it is not nullptr!
2643  CHECK(child.val_is_null());
2644  }
2645  for(ryml::ConstNodeRef child : tree["non_null"].children())
2646  {
2647  CHECK(child.val() != nullptr);
2648  CHECK( ! child.val_is_null());
2649  }
2650  //
2651  //
2652  // Because the meaning of null/~/empty will vary from application
2653  // to application, ryml makes no assumption on what should be
2654  // serialized as null. It leaves this decision to the user. But
2655  // it also provides the proper toolbox for the user to implement
2656  // its intended solution.
2657  //
2658  // writing/disambiguating null values:
2659  ryml::csubstr null = {};
2660  ryml::csubstr nonnull = "";
2661  ryml::csubstr strnull = "null";
2662  ryml::csubstr tilde = "~";
2663  CHECK(null .len == 0); CHECK(null .str == nullptr); CHECK(null == nullptr);
2664  CHECK(nonnull.len == 0); CHECK(nonnull.str != nullptr); CHECK(nonnull != nullptr);
2665  CHECK(strnull.len != 0); CHECK(strnull.str != nullptr); CHECK(strnull != nullptr);
2666  CHECK(tilde .len != 0); CHECK(tilde .str != nullptr); CHECK(tilde != nullptr);
2667  tree.clear();
2668  tree.clear_arena();
2669  tree.rootref() |= ryml::MAP;
2670  // serializes as an empty plain scalar:
2671  tree["empty_null"] << null; CHECK(tree.arena() == "");
2672  // serializes as an empty quoted scalar:
2673  tree["empty_nonnull"] << nonnull; CHECK(tree.arena() == "");
2674  // serializes as the normal 'null' string:
2675  tree["str_null"] << strnull; CHECK(tree.arena() == "null");
2676  // serializes as the normal '~' string:
2677  tree["str_tilde"] << tilde; CHECK(tree.arena() == "null~");
2678  // this is the resulting yaml:
2679  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(empty_null:
2680 empty_nonnull: ''
2681 str_null: null
2682 str_tilde: ~
2683 )");
2684  // To enforce a particular concept of what is a null string, you
2685  // can use the appropriate condition based on pointer nulity or
2686  // other appropriate criteria.
2687  //
2688  // As an example, proper comparison to nullptr:
2689  auto null_if_nullptr = [](ryml::csubstr s) {
2690  return s.str == nullptr ? "null" : s;
2691  };
2692  tree["empty_null"] << null_if_nullptr(null);
2693  tree["empty_nonnull"] << null_if_nullptr(nonnull);
2694  tree["str_null"] << null_if_nullptr(strnull);
2695  tree["str_tilde"] << null_if_nullptr(tilde);
2696  // this is the resulting yaml:
2697  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(empty_null: null
2698 empty_nonnull: ''
2699 str_null: null
2700 str_tilde: ~
2701 )");
2702  //
2703  // As another example, nulity check based on the YAML nulity
2704  // predicate:
2705  auto null_if_predicate = [](ryml::csubstr s) {
2706  return ryml::scalar_is_null(s) ? "null" : s;
2707  };
2708  tree["empty_null"] << null_if_predicate(null);
2709  tree["empty_nonnull"] << null_if_predicate(nonnull);
2710  tree["str_null"] << null_if_predicate(strnull);
2711  tree["str_tilde"] << null_if_predicate(tilde);
2712  // this is the resulting yaml:
2713  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(empty_null: null
2714 empty_nonnull: ''
2715 str_null: null
2716 str_tilde: null
2717 )");
2718  //
2719  // As another example, nulity check based on the YAML nulity
2720  // predicate, but returning "~" to simbolize nulity:
2721  auto tilde_if_predicate = [](ryml::csubstr s) {
2722  return ryml::scalar_is_null(s) ? "~" : s;
2723  };
2724  tree["empty_null"] << tilde_if_predicate(null);
2725  tree["empty_nonnull"] << tilde_if_predicate(nonnull);
2726  tree["str_null"] << tilde_if_predicate(strnull);
2727  tree["str_tilde"] << tilde_if_predicate(tilde);
2728  // this is the resulting yaml:
2729  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"(empty_null: ~
2730 empty_nonnull: ''
2731 str_null: ~
2732 str_tilde: ~
2733 )");
2734 }
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_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 2744 of file quickstart.cpp.

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

demonstrates how to read and write base64-encoded blobs.

See also
Base64 encoding/decoding

Definition at line 3178 of file quickstart.cpp.

3179 {
3180  ryml::Tree tree;
3181  tree.rootref() |= ryml::MAP;
3182  struct text_and_base64 { ryml::csubstr text, base64; };
3183  text_and_base64 cases[] = {
3184  {{"Love all, trust a few, do wrong to none."}, {"TG92ZSBhbGwsIHRydXN0IGEgZmV3LCBkbyB3cm9uZyB0byBub25lLg=="}},
3185  {{"The fool doth think he is wise, but the wise man knows himself to be a fool."}, {"VGhlIGZvb2wgZG90aCB0aGluayBoZSBpcyB3aXNlLCBidXQgdGhlIHdpc2UgbWFuIGtub3dzIGhpbXNlbGYgdG8gYmUgYSBmb29sLg=="}},
3186  {{"Brevity is the soul of wit."}, {"QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu"}},
3187  {{"All that glitters is not gold."}, {"QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu"}},
3188  };
3189  // to encode base64 and write the result to val:
3190  for(text_and_base64 c : cases)
3191  {
3192  tree[c.text] << ryml::fmt::base64(c.text);
3193  CHECK(tree[c.text].val() == c.base64);
3194  }
3195  // to encode base64 and write the result to key:
3196  for(text_and_base64 c : cases)
3197  {
3198  tree.rootref().append_child() << ryml::key(ryml::fmt::base64(c.text)) << c.text;
3199  CHECK(tree[c.base64].val() == c.text);
3200  }
3201  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"('Love all, trust a few, do wrong to none.': TG92ZSBhbGwsIHRydXN0IGEgZmV3LCBkbyB3cm9uZyB0byBub25lLg==
3202 'The fool doth think he is wise, but the wise man knows himself to be a fool.': VGhlIGZvb2wgZG90aCB0aGluayBoZSBpcyB3aXNlLCBidXQgdGhlIHdpc2UgbWFuIGtub3dzIGhpbXNlbGYgdG8gYmUgYSBmb29sLg==
3203 Brevity is the soul of wit.: QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu
3204 All that glitters is not gold.: QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu
3205 TG92ZSBhbGwsIHRydXN0IGEgZmV3LCBkbyB3cm9uZyB0byBub25lLg==: 'Love all, trust a few, do wrong to none.'
3206 VGhlIGZvb2wgZG90aCB0aGluayBoZSBpcyB3aXNlLCBidXQgdGhlIHdpc2UgbWFuIGtub3dzIGhpbXNlbGYgdG8gYmUgYSBmb29sLg==: 'The fool doth think he is wise, but the wise man knows himself to be a fool.'
3207 QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu: Brevity is the soul of wit.
3208 QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu: All that glitters is not gold.
3209 )");
3210  char buf1_[128], buf2_[128];
3211  ryml::substr buf1 = buf1_; // this is where we will write the result (using >>)
3212  ryml::substr buf2 = buf2_; // this is where we will write the result (using deserialize_val()/deserialize_key())
3213  std::string result = {}; // show also how to decode to a std::string
3214  // to decode the val base64 and write the result to buf:
3215  for(const text_and_base64 c : cases)
3216  {
3217  // write the decoded result into the given buffer
3218  tree[c.text] >> ryml::fmt::base64(buf1); // cannot know the needed size
3219  size_t len = tree[c.text].deserialize_val(ryml::fmt::base64(buf2)); // returns the needed size
3220  CHECK(len <= buf1.len);
3221  CHECK(len <= buf2.len);
3222  CHECK(c.text.len == len);
3223  CHECK(buf1.first(len) == c.text);
3224  CHECK(buf2.first(len) == c.text);
3225  //
3226  // interop with std::string: using substr
3227  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3228  len = tree[c.text].deserialize_val(ryml::fmt::base64(ryml::to_substr(result))); // returns the needed size
3229  if(len > result.size()) // the size was not enough; resize and call again
3230  {
3231  result.resize(len);
3232  len = tree[c.text].deserialize_val(ryml::fmt::base64(ryml::to_substr(result))); // returns the needed size
3233  }
3234  result.resize(len); // trim to the length of the decoded buffer
3235  CHECK(result == c.text);
3236  //
3237  // interop with std::string: using blob
3238  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3239  ryml::blob strblob(&result[0], result.size());
3240  CHECK(strblob.buf == result.data());
3241  CHECK(strblob.len == result.size());
3242  len = tree[c.text].deserialize_val(ryml::fmt::base64(strblob)); // returns the needed size
3243  if(len > result.size()) // the size was not enough; resize and call again
3244  {
3245  result.resize(len);
3246  strblob = {&result[0], result.size()};
3247  CHECK(strblob.buf == result.data());
3248  CHECK(strblob.len == result.size());
3249  len = tree[c.text].deserialize_val(ryml::fmt::base64(strblob)); // returns the needed size
3250  }
3251  result.resize(len); // trim to the length of the decoded buffer
3252  CHECK(result == c.text);
3253  //
3254  // Note also these are just syntatic wrappers to simplify client code.
3255  // You can call into the lower level functions without much effort:
3256  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3257  ryml::csubstr encoded = tree[c.text].val();
3258  CHECK(encoded == c.base64);
3259  len = base64_decode(encoded, ryml::blob{&result[0], result.size()});
3260  if(len > result.size()) // the size was not enough; resize and call again
3261  {
3262  result.resize(len);
3263  len = base64_decode(encoded, ryml::blob{&result[0], result.size()});
3264  }
3265  result.resize(len); // trim to the length of the decoded buffer
3266  CHECK(result == c.text);
3267  }
3268  // to decode the key base64 and write the result to buf:
3269  for(const text_and_base64 c : cases)
3270  {
3271  // write the decoded result into the given buffer
3272  tree[c.base64] >> ryml::key(ryml::fmt::base64(buf1)); // cannot know the needed size
3273  size_t len = tree[c.base64].deserialize_key(ryml::fmt::base64(buf2)); // returns the needed size
3274  CHECK(len <= buf1.len);
3275  CHECK(len <= buf2.len);
3276  CHECK(c.text.len == len);
3277  CHECK(buf1.first(len) == c.text);
3278  CHECK(buf2.first(len) == c.text);
3279  // interop with std::string: using substr
3280  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3281  len = tree[c.base64].deserialize_key(ryml::fmt::base64(ryml::to_substr(result))); // returns the needed size
3282  if(len > result.size()) // the size was not enough; resize and call again
3283  {
3284  result.resize(len);
3285  len = tree[c.base64].deserialize_key(ryml::fmt::base64(ryml::to_substr(result))); // returns the needed size
3286  }
3287  result.resize(len); // trim to the length of the decoded buffer
3288  CHECK(result == c.text);
3289  //
3290  // interop with std::string: using blob
3291  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3292  ryml::blob strblob = {&result[0], result.size()};
3293  CHECK(strblob.buf == result.data());
3294  CHECK(strblob.len == result.size());
3295  len = tree[c.base64].deserialize_key(ryml::fmt::base64(strblob)); // returns the needed size
3296  if(len > result.size()) // the size was not enough; resize and call again
3297  {
3298  result.resize(len);
3299  strblob = {&result[0], result.size()};
3300  CHECK(strblob.buf == result.data());
3301  CHECK(strblob.len == result.size());
3302  len = tree[c.base64].deserialize_key(ryml::fmt::base64(strblob)); // returns the needed size
3303  }
3304  result.resize(len); // trim to the length of the decoded buffer
3305  CHECK(result == c.text);
3306  //
3307  // Note also these are just syntactic wrappers to simplify client code.
3308  // You can call into the lower level functions without much effort:
3309  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3310  ryml::csubstr encoded = tree[c.base64].key();
3311  CHECK(encoded == c.base64);
3312  len = base64_decode(encoded, ryml::blob{&result[0], result.size()});
3313  if(len > result.size()) // the size was not enough; resize and call again
3314  {
3315  result.resize(len);
3316  len = base64_decode(encoded, ryml::blob{&result[0], result.size()});
3317  }
3318  result.resize(len); // trim to the length of the decoded buffer
3319  CHECK(result == c.text);
3320  }
3321  // directly encode variables
3322  {
3323  const uint64_t valin = UINT64_C(0xdeadbeef);
3324  uint64_t valout = 0;
3325  tree["deadbeef"] << c4::fmt::base64(valin); // sometimes cbase64() is needed to avoid ambiguity
3326  size_t len = tree["deadbeef"].deserialize_val(ryml::fmt::base64(valout));
3327  CHECK(len <= sizeof(valout));
3328  CHECK(valout == UINT64_C(0xdeadbeef)); // base64 roundtrip is bit-accurate
3329  }
3330  // directly encode memory ranges
3331  {
3332  const uint32_t data_in[11] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xdeadbeef};
3333  uint32_t data_out[11] = {};
3334  CHECK(memcmp(data_in, data_out, sizeof(data_in)) != 0); // before the roundtrip
3335  tree["int_data"] << c4::fmt::base64(data_in);
3336  size_t len = tree["int_data"].deserialize_val(ryml::fmt::base64(data_out));
3337  CHECK(len <= sizeof(data_out));
3338  CHECK(memcmp(data_in, data_out, sizeof(data_in)) == 0); // after the roundtrip
3339  }
3340 }
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_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 3562 of file quickstart.cpp.

3563 {
3564  ryml::Tree t;
3565 
3566  auto r = t.rootref();
3567  r |= ryml::MAP;
3568 
3569  vec2<int> v2in{10, 11};
3570  vec2<int> v2out{1, 2};
3571  r["v2"] << v2in; // serializes to the tree's arena, and then sets the keyval
3572  r["v2"] >> v2out;
3573  CHECK(v2in.x == v2out.x);
3574  CHECK(v2in.y == v2out.y);
3575  vec3<int> v3in{100, 101, 102};
3576  vec3<int> v3out{1, 2, 3};
3577  r["v3"] << v3in; // serializes to the tree's arena, and then sets the keyval
3578  r["v3"] >> v3out;
3579  CHECK(v3in.x == v3out.x);
3580  CHECK(v3in.y == v3out.y);
3581  CHECK(v3in.z == v3out.z);
3582  vec4<int> v4in{1000, 1001, 1002, 1003};
3583  vec4<int> v4out{1, 2, 3, 4};
3584  r["v4"] << v4in; // serializes to the tree's arena, and then sets the keyval
3585  r["v4"] >> v4out;
3586  CHECK(v4in.x == v4out.x);
3587  CHECK(v4in.y == v4out.y);
3588  CHECK(v4in.z == v4out.z);
3589  CHECK(v4in.w == v4out.w);
3590  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(v2: '(10,11)'
3591 v3: '(100,101,102)'
3592 v4: '(1000,1001,1002,1003)'
3593 )");
3594 
3595  // note that only the used functions are needed:
3596  // - if a type is only parsed, then only from_chars() is needed
3597  // - if a type is only emitted, then only to_chars() is needed
3598  emit_only_vec2<int> eov2in{20, 21}; // only has to_chars()
3599  parse_only_vec2<int> pov2out{1, 2}; // only has from_chars()
3600  r["v2"] << eov2in; // serializes to the tree's arena, and then sets the keyval
3601  r["v2"] >> pov2out;
3602  CHECK(eov2in.x == pov2out.x);
3603  CHECK(eov2in.y == pov2out.y);
3604  emit_only_vec3<int> eov3in{30, 31, 32}; // only has to_chars()
3605  parse_only_vec3<int> pov3out{1, 2, 3}; // only has from_chars()
3606  r["v3"] << eov3in; // serializes to the tree's arena, and then sets the keyval
3607  r["v3"] >> pov3out;
3608  CHECK(eov3in.x == pov3out.x);
3609  CHECK(eov3in.y == pov3out.y);
3610  CHECK(eov3in.z == pov3out.z);
3611  emit_only_vec4<int> eov4in{40, 41, 42, 43}; // only has to_chars()
3612  parse_only_vec4<int> pov4out{1, 2, 3, 4}; // only has from_chars()
3613  r["v4"] << eov4in; // serializes to the tree's arena, and then sets the keyval
3614  r["v4"] >> pov4out;
3615  CHECK(eov4in.x == pov4out.x);
3616  CHECK(eov4in.y == pov4out.y);
3617  CHECK(eov4in.z == pov4out.z);
3618  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(v2: '(20,21)'
3619 v3: '(30,31,32)'
3620 v4: '(40,41,42,43)'
3621 )");
3622 }
example scalar type, serialized only
example scalar type, serialized only
example scalar type, serialized only
example scalar type, deserialized only
example scalar type, deserialized only
example scalar type, deserialized only

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

◆ sample_user_container_types()

void sample_user_container_types ( )

shows how to serialize/deserialize container types.

See also
Serialize/deserialize container types
sample_std_types

Definition at line 3758 of file quickstart.cpp.

3759 {
3760  my_type mt_in{
3761  {20, 21},
3762  {30, 31, 32},
3763  {40, 41, 42, 43},
3764  {{101, 102, 103, 104, 105, 106, 107}},
3765  {{{1001, 2001}, {1002, 2002}, {1003, 2003}}},
3766  };
3767  my_type mt_out;
3768 
3769  ryml::Tree t;
3770  t.rootref() << mt_in; // read from this
3771  t.crootref() >> mt_out; // assign here
3772  CHECK(mt_out.v2.x == mt_in.v2.x);
3773  CHECK(mt_out.v2.y == mt_in.v2.y);
3774  CHECK(mt_out.v3.x == mt_in.v3.x);
3775  CHECK(mt_out.v3.y == mt_in.v3.y);
3776  CHECK(mt_out.v3.z == mt_in.v3.z);
3777  CHECK(mt_out.v4.x == mt_in.v4.x);
3778  CHECK(mt_out.v4.y == mt_in.v4.y);
3779  CHECK(mt_out.v4.z == mt_in.v4.z);
3780  CHECK(mt_out.v4.w == mt_in.v4.w);
3781  CHECK(mt_in.seq.seq_member.size() > 0);
3782  CHECK(mt_out.seq.seq_member.size() == mt_in.seq.seq_member.size());
3783  for(size_t i = 0; i < mt_in.seq.seq_member.size(); ++i)
3784  {
3785  CHECK(mt_out.seq.seq_member[i] == mt_in.seq.seq_member[i]);
3786  }
3787  CHECK(mt_in.map.map_member.size() > 0);
3788  CHECK(mt_out.map.map_member.size() == mt_in.map.map_member.size());
3789  for(auto const& kv : mt_in.map.map_member)
3790  {
3791  CHECK(mt_out.map.map_member.find(kv.first) != mt_out.map.map_member.end());
3792  CHECK(mt_out.map.map_member[kv.first] == kv.second);
3793  }
3794  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(v2: '(20,21)'
3795 v3: '(30,31,32)'
3796 v4: '(40,41,42,43)'
3797 seq:
3798  - 101
3799  - 102
3800  - 103
3801  - 104
3802  - 105
3803  - 106
3804  - 107
3805 map:
3806  1001: 2001
3807  1002: 2002
3808  1003: 2003
3809 )");
3810 }
std::map< K, V > map_member
std::vector< T > seq_member
example user container type with nested container members.
vec2< int > v2
my_map_type< int, int > map
vec3< int > v3
vec4< int > v4
my_seq_type< int > seq

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

◆ sample_std_types()

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

3820 {
3821  std::string yml_std_string = R"(- v2: '(20,21)'
3822  v3: '(30,31,32)'
3823  v4: '(40,41,42,43)'
3824  seq:
3825  - 101
3826  - 102
3827  - 103
3828  - 104
3829  - 105
3830  - 106
3831  - 107
3832  map:
3833  1001: 2001
3834  1002: 2002
3835  1003: 2003
3836 - v2: '(120,121)'
3837  v3: '(130,131,132)'
3838  v4: '(140,141,142,143)'
3839  seq:
3840  - 1101
3841  - 1102
3842  - 1103
3843  - 1104
3844  - 1105
3845  - 1106
3846  - 1107
3847  map:
3848  11001: 12001
3849  11002: 12002
3850  11003: 12003
3851 - v2: '(220,221)'
3852  v3: '(230,231,232)'
3853  v4: '(240,241,242,243)'
3854  seq:
3855  - 2101
3856  - 2102
3857  - 2103
3858  - 2104
3859  - 2105
3860  - 2106
3861  - 2107
3862  map:
3863  21001: 22001
3864  21002: 22002
3865  21003: 22003
3866 )";
3867  // parse in-place using the std::string above
3868  ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(yml_std_string));
3869  // my_type is a container-of-containers type. see above its
3870  // definition implementation for ryml.
3871  std::vector<my_type> vmt;
3872  tree.rootref() >> vmt;
3873  CHECK(vmt.size() == 3);
3874  ryml::Tree tree_out;
3875  tree_out.rootref() << vmt;
3876  CHECK(ryml::emitrs_yaml<std::string>(tree_out) == yml_std_string);
3877 }

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

◆ sample_float_precision()

void sample_float_precision ( )

control precision of serialized floats

Definition at line 3883 of file quickstart.cpp.

3884 {
3885  std::vector<double> reference{1.23234412342131234, 2.12323123143434237, 3.67847983572591234};
3886  // A safe precision for comparing doubles. May vary depending on
3887  // compiler flags. Double goes to about 15 digits, so 14 should be
3888  // safe enough for this test to succeed.
3889  const double precision_safe = 1.e-14;
3890  const size_t num_digits_safe = 14;
3891  const size_t num_digits_original = 17;
3892  auto get_num_digits = [](ryml::csubstr number){ return number.sub(2).len; };
3893  //
3894  // no significant precision is lost when reading
3895  // floating point numbers:
3896  {
3897  ryml::Tree tree = ryml::parse_in_arena(R"([1.23234412342131234, 2.12323123143434237, 3.67847983572591234])");
3898  std::vector<double> output;
3899  tree.rootref() >> output;
3900  CHECK(output.size() == reference.size());
3901  for(size_t i = 0; i < reference.size(); ++i)
3902  {
3903  CHECK(get_num_digits(tree[(ryml::id_type)i].val()) == num_digits_original);
3904  CHECK(fabs(output[i] - reference[i]) < precision_safe);
3905  }
3906  }
3907  //
3908  // However, depending on the compilation settings, there may be a
3909  // significant precision loss when serializing with the default
3910  // approach, operator<<(double):
3911  {
3912  ryml::Tree serialized;
3913  serialized.rootref() << reference;
3914  std::cout << serialized;
3915  // Without std::to_chars() there is a loss of precision:
3916  #if (!C4CORE_HAVE_STD_TOCHARS) // This macro is defined when std::to_chars() is available.
3917  CHECK(ryml::emitrs_yaml<std::string>(serialized) == R"(- 1.23234
3918 - 2.12323
3919 - 3.67848
3920 )" || (bool)"this is indicative; the exact results will vary from platform to platform.");
3921  C4_UNUSED(num_digits_safe);
3922  #else // ... but when using C++17 and above, the results are eminently equal:
3923  CHECK((ryml::emitrs_yaml<std::string>(serialized) == R"(- 1.2323441234213124
3924 - 2.1232312314343424
3925 - 3.6784798357259123
3926 )") || (bool)"this is indicative; the exact results will vary from platform to platform.");
3927  size_t pos = 0;
3928  for(ryml::ConstNodeRef child : serialized.rootref().children())
3929  {
3930  CHECK(get_num_digits(child.val()) >= num_digits_safe);
3931  double out = {};
3932  child >> out;
3933  CHECK(fabs(out - reference[pos++]) < precision_safe);
3934  }
3935  #endif
3936  }
3937  //
3938  // The difference is explained by the availability of
3939  // fastfloat::from_chars(), std::from_chars() and std::to_chars().
3940  //
3941  // ryml prefers the fastfloat::from_chars() version. Unfortunately
3942  // fastfloat does not have to_chars() (see
3943  // https://github.com/fastfloat/fast_float/issues/23).
3944  //
3945  // When C++17 is used, ryml uses std::to_chars(), which produces
3946  // good defaults.
3947  //
3948  // However, with earlier standards, or in some library
3949  // implementations, there's only snprintf() available. Every other
3950  // std library function will either disrespect the string limits,
3951  // or more precisely, accept no string size limits. So the
3952  // implementation of c4core (which ryml uses) falls back to
3953  // snprintf("%g"), and that picks by default a (low) number of
3954  // digits.
3955  //
3956  // But all is not lost for C++11/C++14 users!
3957  //
3958  // To force a particular precision when serializing, you can use
3959  // c4::fmt::real() (brought into the ryml:: namespace). Or you can
3960  // serialize the number yourself! The small downside is that you
3961  // have to build the container.
3962  //
3963  // First a function to check the result:
3964  auto check_precision = [&](ryml::Tree const& serialized){
3965  std::cout << serialized;
3966  // now it works!
3967  CHECK((ryml::emitrs_yaml<std::string>(serialized) == R"(- 1.23234412342131239
3968 - 2.12323123143434245
3969 - 3.67847983572591231
3970 )") || (bool)"this is indicative; the exact results will vary from platform to platform.");
3971  size_t pos = 0;
3972  for(ryml::ConstNodeRef child : serialized.rootref().children())
3973  {
3974  CHECK(get_num_digits(child.val()) == num_digits_original);
3975  double out = {};
3976  child >> out;
3977  CHECK(fabs(out - reference[pos++]) < precision_safe);
3978  }
3979  };
3980  //
3981  // Serialization example using fmt::real()
3982  {
3983  ryml::Tree serialized;
3984  ryml::NodeRef root = serialized.rootref();
3985  root |= ryml::SEQ;
3986  for(const double v : reference)
3987  root.append_child() << ryml::fmt::real(v, num_digits_original, ryml::FTOA_FLOAT);
3988  check_precision(serialized); // OK - now within bounds!
3989  }
3990  //
3991  // Serialization example using snprintf
3992  {
3993  ryml::Tree serialized;
3994  ryml::NodeRef root = serialized.rootref();
3995  root |= ryml::SEQ;
3996  char tmp[64];
3997  for(const double v : reference)
3998  {
3999  // reuse a buffer to serialize.
4000  // add 1 to the significant digits because the %g
4001  // specifier counts the integral digits.
4002  (void)snprintf(tmp, sizeof(tmp), "%.18g", v);
4003  // copy the serialized string to the tree (operator<<
4004  // copies to the arena, operator= just assigns the string
4005  // pointer and would be wrong in this case):
4006  root.append_child() << ryml::to_csubstr((const char*)tmp);
4007  }
4008  check_precision(serialized); // OK - now within bounds!
4009  }
4010 }

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_emit_to_container ( )

demonstrates how to emit to a linear container of char

Definition at line 4016 of file quickstart.cpp.

4017 {
4018  // it is possible to emit to any linear container of char.
4019 
4020  ryml::csubstr ymla = "- 1\n- 2\n";
4021  ryml::csubstr ymlb = R"(- a
4022 - b
4023 - x0: 1
4024  x1: 2
4025 - champagne: Dom Perignon
4026  coffee: Arabica
4027  more:
4028  vinho verde: Soalheiro
4029  vinho tinto: Redoma 2017
4030  beer:
4031  - Rochefort 10
4032  - Busch
4033  - Leffe Rituel
4034 - foo
4035 - bar
4036 - baz
4037 - bat
4038 )";
4039  const ryml::Tree treea = ryml::parse_in_arena(ymla);
4040  const ryml::Tree treeb = ryml::parse_in_arena(ymlb);
4041 
4042  // eg, std::vector<char>
4043  {
4044  // do a blank call on an empty buffer to find the required size.
4045  // no overflow will occur, and returns a substr with the size
4046  // required to output
4047  ryml::csubstr output = ryml::emit_yaml(treea, treea.root_id(), ryml::substr{}, /*error_on_excess*/false);
4048  CHECK(output.str == nullptr);
4049  CHECK(output.len > 0);
4050  size_t num_needed_chars = output.len;
4051  std::vector<char> buf(num_needed_chars);
4052  // now try again with the proper buffer
4053  output = ryml::emit_yaml(treea, treea.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4054  CHECK(output == ymla);
4055 
4056  // it is possible to reuse the buffer and grow it as needed.
4057  // first do a blank run to find the size:
4058  output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::substr{}, /*error_on_excess*/false);
4059  CHECK(output.str == nullptr);
4060  CHECK(output.len > 0);
4061  CHECK(output.len == ymlb.len);
4062  num_needed_chars = output.len;
4063  buf.resize(num_needed_chars);
4064  // now try again with the proper buffer
4065  output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4066  CHECK(output == ymlb);
4067 
4068  // there is a convenience wrapper performing the same as above:
4069  // provided to_substr() is defined for that container.
4070  output = ryml::emitrs_yaml(treeb, &buf);
4071  CHECK(output == ymlb);
4072 
4073  // or you can just output a new container:
4074  // provided to_substr() is defined for that container.
4075  std::vector<char> another = ryml::emitrs_yaml<std::vector<char>>(treeb);
4076  CHECK(ryml::to_csubstr(another) == ymlb);
4077 
4078  // you can also emit nested nodes:
4079  another = ryml::emitrs_yaml<std::vector<char>>(treeb[3][2]);
4080  CHECK(ryml::to_csubstr(another) == R"(more:
4081  vinho verde: Soalheiro
4082  vinho tinto: Redoma 2017
4083 )");
4084  }
4085 
4086 
4087  // eg, std::string. notice this is the same code as above
4088  {
4089  // do a blank call on an empty buffer to find the required size.
4090  // no overflow will occur, and returns a substr with the size
4091  // required to output
4092  ryml::csubstr output = ryml::emit_yaml(treea, treea.root_id(), ryml::substr{}, /*error_on_excess*/false);
4093  CHECK(output.str == nullptr);
4094  CHECK(output.len > 0);
4095  size_t num_needed_chars = output.len;
4096  std::string buf;
4097  buf.resize(num_needed_chars);
4098  // now try again with the proper buffer
4099  output = ryml::emit_yaml(treea, treea.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4100  CHECK(output == ymla);
4101 
4102  // it is possible to reuse the buffer and grow it as needed.
4103  // first do a blank run to find the size:
4104  output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::substr{}, /*error_on_excess*/false);
4105  CHECK(output.str == nullptr);
4106  CHECK(output.len > 0);
4107  CHECK(output.len == ymlb.len);
4108  num_needed_chars = output.len;
4109  buf.resize(num_needed_chars);
4110  // now try again with the proper buffer
4111  output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4112  CHECK(output == ymlb);
4113 
4114  // there is a convenience wrapper performing the above instructions:
4115  // provided to_substr() is defined for that container
4116  output = ryml::emitrs_yaml(treeb, &buf);
4117  CHECK(output == ymlb);
4118 
4119  // or you can just output a new container:
4120  // provided to_substr() is defined for that container.
4121  std::string another = ryml::emitrs_yaml<std::string>(treeb);
4122  CHECK(ryml::to_csubstr(another) == ymlb);
4123 
4124  // you can also emit nested nodes:
4125  another = ryml::emitrs_yaml<std::string>(treeb[3][2]);
4126  CHECK(ryml::to_csubstr(another) == R"(more:
4127  vinho verde: Soalheiro
4128  vinho tinto: Redoma 2017
4129 )");
4130  }
4131 }
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:764

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

◆ sample_emit_to_stream()

void sample_emit_to_stream ( )

demonstrates how to emit to a stream-like structure

Definition at line 4137 of file quickstart.cpp.

4138 {
4139  ryml::csubstr ymlb = R"(- a
4140 - b
4141 - x0: 1
4142  x1: 2
4143 - champagne: Dom Perignon
4144  coffee: Arabica
4145  more:
4146  vinho verde: Soalheiro
4147  vinho tinto: Redoma 2017
4148  beer:
4149  - Rochefort 10
4150  - Busch
4151  - Leffe Rituel
4152 - foo
4153 - bar
4154 - baz
4155 - bat
4156 )";
4157  const ryml::Tree tree = ryml::parse_in_arena(ymlb);
4158 
4159  std::string s;
4160 
4161  // emit a full tree
4162  {
4163  std::stringstream ss;
4164  ss << tree; // works with any stream having .operator<<() and .write()
4165  s = ss.str();
4166  CHECK(ryml::to_csubstr(s) == ymlb);
4167  }
4168 
4169  // emit a full tree as json
4170  {
4171  std::stringstream ss;
4172  ss << ryml::as_json(tree); // works with any stream having .operator<<() and .write()
4173  s = ss.str();
4174  CHECK(ryml::to_csubstr(s) == R"([
4175  "a",
4176  "b",
4177  {
4178  "x0": 1,
4179  "x1": 2
4180  },
4181  {
4182  "champagne": "Dom Perignon",
4183  "coffee": "Arabica",
4184  "more": {
4185  "vinho verde": "Soalheiro",
4186  "vinho tinto": "Redoma 2017"
4187  },
4188  "beer": [
4189  "Rochefort 10",
4190  "Busch",
4191  "Leffe Rituel"
4192  ]
4193  },
4194  "foo",
4195  "bar",
4196  "baz",
4197  "bat"
4198 ]
4199 )");
4200  }
4201 
4202  // emit a nested node
4203  {
4204  std::stringstream ss;
4205  ss << tree[3][2]; // works with any stream having .operator<<() and .write()
4206  s = ss.str();
4207  CHECK(ryml::to_csubstr(s) == R"(more:
4208  vinho verde: Soalheiro
4209  vinho tinto: Redoma 2017
4210 )");
4211  }
4212 
4213  // emit a nested node as json
4214  {
4215  std::stringstream ss;
4216  ss << ryml::as_json(tree[3][2]); // works with any stream having .operator<<() and .write()
4217  s = ss.str();
4218  CHECK(ryml::to_csubstr(s) == R"("more": {
4219  "vinho verde": "Soalheiro",
4220  "vinho tinto": "Redoma 2017"
4221 }
4222 )");
4223  }
4224 }
mark a tree or node to be emitted as yaml when using operator<<, with options.
Definition: emit.hpp:551

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

◆ sample_emit_to_file()

void sample_emit_to_file ( )

demonstrates how to emit to a FILE*

Definition at line 4230 of file quickstart.cpp.

4231 {
4232  ryml::csubstr yml = R"(- a
4233 - b
4234 - x0: 1
4235  x1: 2
4236 - champagne: Dom Perignon
4237  coffee: Arabica
4238  more:
4239  vinho verde: Soalheiro
4240  vinho tinto: Redoma 2017
4241  beer:
4242  - Rochefort 10
4243  - Busch
4244  - Leffe Rituel
4245 - foo
4246 - bar
4247 - baz
4248 - bat
4249 )";
4250  const ryml::Tree tree = ryml::parse_in_arena(yml);
4251  // this is emitting to stdout, but of course you can pass in any
4252  // FILE* obtained from fopen()
4253  size_t len = ryml::emit_yaml(tree, tree.root_id(), stdout);
4254  // the return value is the number of characters that were written
4255  // to the file
4256  CHECK(len == yml.len);
4257 }

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

◆ sample_emit_nested_node()

void sample_emit_nested_node ( )

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

Definition at line 4263 of file quickstart.cpp.

4264 {
4265  const ryml::Tree tree = ryml::parse_in_arena(R"(- a
4266 - b
4267 - x0: 1
4268  x1: 2
4269 - champagne: Dom Perignon
4270  coffee: Arabica
4271  more:
4272  vinho verde: Soalheiro
4273  vinho tinto: Redoma 2017
4274  beer:
4275  - Rochefort 10
4276  - Busch
4277  - Leffe Rituel
4278  - - and so
4279  - many other
4280  - wonderful beers
4281 - more
4282 - seq
4283 - members
4284 - here
4285 )");
4286  CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"]) == R"(beer:
4287  - Rochefort 10
4288  - Busch
4289  - Leffe Rituel
4290  - - and so
4291  - many other
4292  - wonderful beers
4293 )");
4294  CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][0]) == "Rochefort 10");
4295  CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][3]) == R"(- and so
4296 - many other
4297 - wonderful beers
4298 )");
4299 }

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

◆ sample_style()

void sample_style ( )

[experimental] query/set/modify node style to control formatting of emitted YAML code.

Definition at line 4306 of file quickstart.cpp.

4307 {
4308  // we will be using this helper throughout this function
4309  auto tostr = [](ryml::ConstNodeRef n) { return ryml::emitrs_yaml<std::string>(n); };
4310  // let's parse this yaml:
4311  ryml::csubstr yaml = R"(block map:
4312  block key: block val
4313 block seq:
4314  - block val 1
4315  - block val 2
4316  - 'quoted'
4317 flow map, singleline: {flow key: flow val}
4318 flow seq, singleline: [flow val,flow val]
4319 flow map, multiline: {
4320  flow key: flow val
4321  }
4322 flow seq, multiline: [
4323  flow val,
4324  flow val
4325  ]
4326 )";
4327  ryml::Tree tree = ryml::parse_in_arena(yaml);
4328  // while parsing, ryml marks parsed nodes with their original style:
4329  CHECK(tree.rootref().is_block());
4330  CHECK(tree["block map"].is_key_plain());
4331  CHECK(tree["block seq"].is_key_plain());
4332  CHECK(tree["flow map, singleline"].is_key_plain());
4333  CHECK(tree["flow seq, singleline"].is_key_plain());
4334  CHECK(tree["flow map, multiline"].is_key_plain());
4335  CHECK(tree["flow seq, multiline"].is_key_plain());
4336  CHECK(tree["block map"].is_block());
4337  CHECK(tree["block seq"].is_block());
4338  // flow is either singleline (FLOW_SL) or multiline (FLOW_ML)
4339  CHECK(tree["flow map, singleline"].is_flow_sl());
4340  CHECK(tree["flow seq, singleline"].is_flow_sl());
4341  CHECK(tree["flow map, multiline"].is_flow_ml());
4342  CHECK(tree["flow seq, multiline"].is_flow_ml());
4343  // is_flow() is equivalent to (is_flow_sl() || is_flow_ml())
4344  CHECK(tree["flow map, singleline"].is_flow());
4345  CHECK(tree["flow seq, singleline"].is_flow());
4346  CHECK(tree["flow map, multiline"].is_flow());
4347  CHECK(tree["flow seq, multiline"].is_flow());
4348  //
4349  // since the tree nodes are marked with their original parsed
4350  // style, emitting the parsed tree will preserve the original
4351  // style (minus whitespace):
4352  //
4353  CHECK(tostr(tree) == yaml); // same as before!
4354  //
4355  // you can set/modify the style programatically!
4356  //
4357  // here are more examples.
4358  //
4359  {
4360  ryml::NodeRef n = tree["block map"]; // Let's look at one node
4361  // It looks like this originally:
4362  CHECK(tostr(n) == "block map:\n block key: block val\n");
4363  // let's modify its style:
4364  n.set_key_style(ryml::KEY_SQUO); // scalar style: to single-quoted scalar
4365  n.set_container_style(ryml::FLOW_SL); // container style: to flow singleline
4366  // now it looks like this:
4367  CHECK(tostr(n) == "'block map': {block key: block val}\n");
4368  }
4369  // next example
4370  {
4371  ryml::NodeRef n = tree["block seq"];
4372  CHECK(tostr(n) == "block seq:\n - block val 1\n - block val 2\n - 'quoted'\n");
4373  n.set_key_style(ryml::KEY_DQUO); // scalar style: to double-quoted scalar
4374  n.set_container_style(ryml::FLOW_ML); // container style: to flow multiline
4375  n[2].set_val_style(ryml::VAL_PLAIN); // scalar style: to plain
4376  CHECK(tostr(n) == "\"block seq\": [\n block val 1,\n block val 2,\n quoted\n ]\n");
4377  }
4378  // next example
4379  {
4380  ryml::NodeRef n = tree["flow map, singleline"];
4381  CHECK(tostr(n) == "flow map, singleline: {flow key: flow val}\n");
4383  n["flow key"].set_val_style(ryml::VAL_LITERAL);
4384  CHECK(tostr(n) == "flow map, singleline:\n flow key: |-\n flow val\n");
4385  }
4386  // next example
4387  {
4388  ryml::NodeRef n = tree["flow map, multiline"];
4389  CHECK(tostr(n) == "flow map, multiline: {\n flow key: flow val\n }\n");
4391  CHECK(tostr(n) == "flow map, multiline:\n flow key: flow val\n");
4392  }
4393  // next example
4394  {
4395  ryml::NodeRef n = tree["flow seq, singleline"];
4396  CHECK(tostr(n) == "flow seq, singleline: [flow val,flow val]\n");
4401  CHECK(tostr(n) == "? >-\n flow seq, singleline\n:\n - 'flow val'\n - \"flow val\"\n");
4402  }
4403  // next example
4404  {
4405  ryml::NodeRef n = tree["flow seq, multiline"];
4406  CHECK(tostr(n) == "flow seq, multiline: [\n flow val,\n flow val\n ]\n");
4408  CHECK(tostr(n) == "flow seq, multiline: [flow val,flow val]\n");
4409  }
4410  // note the full tree now:
4411  CHECK(tostr(tree) != yaml);
4412  CHECK(tostr(tree) ==
4413  R"('block map': {block key: block val}
4414 "block seq": [
4415  block val 1,
4416  block val 2,
4417  quoted
4418  ]
4419 flow map, singleline:
4420  flow key: |-
4421  flow val
4422 ? >-
4423  flow seq, singleline
4424 :
4425  - 'flow val'
4426  - "flow val"
4427 flow map, multiline:
4428  flow key: flow val
4429 flow seq, multiline: [flow val,flow val]
4430 )");
4431  // you can clear the style of single nodes:
4432  tree["block map"].clear_style();
4433  tree["block seq"].clear_style();
4434  CHECK(tostr(tree) ==
4435  R"(block map:
4436  block key: block val
4437 block seq:
4438  - block val 1
4439  - block val 2
4440  - quoted
4441 flow map, singleline:
4442  flow key: |-
4443  flow val
4444 ? >-
4445  flow seq, singleline
4446 :
4447  - 'flow val'
4448  - "flow val"
4449 flow map, multiline:
4450  flow key: flow val
4451 flow seq, multiline: [flow val,flow val]
4452 )");
4453  // you can clear the style recursively:
4454  tree.rootref().clear_style(/*recurse*/true);
4455  // when emitting nodes which have no style set, ryml will default
4456  // to block format for containers, and call
4457  // ryml::scalar_style_choose() to pick the style for each scalar
4458  // (at the cost of a scan over each scalar). Note that ryml picks
4459  // single-quoted for scalars containing commas:
4460  CHECK(tostr(tree) ==
4461  R"(block map:
4462  block key: block val
4463 block seq:
4464  - block val 1
4465  - block val 2
4466  - quoted
4467 'flow map, singleline':
4468  flow key: flow val
4469 'flow seq, singleline':
4470  - flow val
4471  - flow val
4472 'flow map, multiline':
void set_key_style(NodeType_e style)
Definition: node.hpp:1138
void clear_style(bool recurse=false)
Definition: node.hpp:1140
void set_val_style(NodeType_e style)
Definition: node.hpp:1139
void set_container_style(NodeType_e style)
Definition: node.hpp:1137
void clear_style(id_type node, bool recurse=false)
Definition: tree.cpp:1392
@ 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
@ FLOW_ML
mark container with multi-line flow style (seqs as '[ val1, val2 ], maps as '{ key: val,...
Definition: node_type.hpp:60
@ 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
@ VAL_LITERAL
mark val scalar as multiline, block literal |
Definition: node_type.hpp:63
@ 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:277

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

◆ sample_style_flow_ml_indent()

void sample_style_flow_ml_indent ( )

[experimental] control the indentation of emitted FLOW_ML containers

Definition at line 4478 of file quickstart.cpp.

4486  :
4487  block key: block val
4488 block seq:
4489  - block val 1
4490  - block val 2
4491  - quoted
4492 'flow map, singleline':
4493  flow key: flow val
4494 'flow seq, singleline':
4495  - flow val
4496  - flow val
4497 'flow map, multiline':
4498  flow key: flow val
4499 'flow seq, multiline':
4500  - flow val
4501  - flow val
4502 )");
4503  // change all keys to single-quoted:
4504  tree.rootref().set_style_conditionally(/*type_mask*/ryml::KEY,
4505  /*remflags*/ryml::KEY_STYLE,
4506  /*addflags*/ryml::KEY_SQUO,
4507  /*recurse*/true);
4508  // change all vals to double-quoted
4509  tree.rootref().set_style_conditionally(/*type_mask*/ryml::VAL,
4510  /*remflags*/ryml::VAL_STYLE,

◆ sample_style_flow_ml_filter()

void sample_style_flow_ml_filter ( )

[experimental] set the parser to pick FLOW_SL even if the container being parsed is FLOW_ML

Definition at line 4517 of file quickstart.cpp.

4525  :
4526  'block key': "block val"
4527 'block seq': ["block val 1","block val 2","quoted"]
4528 'flow map, singleline':
4529  'flow key': "flow val"
4530 'flow seq, singleline': ["flow val","flow val"]
4531 'flow map, multiline':
4532  'flow key': "flow val"
4533 'flow seq, multiline': ["flow val","flow val"]
4534 )");
4535  // you can also set a conditional style in a single node (or its branch if recurse is true):
4536  tree["flow seq, singleline"].set_style_conditionally(/*type_mask*/ryml::SEQ,
4537  /*remflags*/ryml::CONTAINER_STYLE,
4538  /*addflags*/ryml::BLOCK,
4539  /*recurse*/false);
4540  CHECK(tostr(tree) ==
4541  R"('block map':
4542  'block key': "block val"
4543 'block seq': ["block val 1","block val 2","quoted"]
4544 'flow map, singleline':
4545  'flow key': "flow val"
4546 'flow seq, singleline':
4547  - "flow val"
4548  - "flow val"
4549 'flow map, multiline':
4550  'flow key': "flow val"
4551 'flow seq, multiline': ["flow val","flow val"]
4552 )");
4553  // see also:
4554  // - ryml::scalar_style_choose()
4555  // - ryml::scalar_style_json_choose()
4556  // - ryml::scalar_style_query_squo()
4557  // - ryml::scalar_style_query_plain()
4558 }
4559 
4560 
4561 //-----------------------------------------------------------------------------
4562 
4563 /** [experimental] control the indentation of emitted FLOW_ML containers */
4564 void sample_style_flow_ml_indent()
4565 {
4566  // we will be using this helper throughout this function
4567  auto tostr = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) {
4568  return ryml::emitrs_yaml<std::string>(n, opts);
4569  };
4570  ryml::csubstr yaml = "{map: {seq: [0, 1, 2, 3, [40, 41]]}}";
4571  ryml::Tree tree = ryml::parse_in_arena(yaml);
4572  ryml::EmitOptions defaults = {};
4573  ryml::EmitOptions noindent = ryml::EmitOptions{}.indent_flow_ml(false);
4574  CHECK(tostr(tree, defaults) == "{map: {seq: [0,1,2,3,[40,41]]}}");
4575  // let's now set the style to FLOW_ML (it was FLOW_SL)

◆ sample_json()

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

4584  : [
4585  0,
4586  1,
4587  2,
4588  3,
4589  [
4590  40,
4591  41
4592  ]
4593  ]
4594  }
4595 }
4596 )");
4597  // if we use the noindent options, then each value is put at the
4598  // beginning of the line
4599  CHECK(tostr(tree, noindent) ==
4600  R"({
4601 map: {
4602 seq: [
4603 0,
4604 1,
4605 2,
4606 3,
4607 [
4608 40,
4609 41
4610 ]
4611 ]
4612 }
4613 }
4614 )");
4615  // Note that the noindent option will safely respect any prior
4616  // indent level from enclosing block containers! For example:
4617  tree.rootref().set_container_style(ryml::BLOCK);
4618  CHECK(tostr(tree, noindent) == // notice it is indented at the map level
4619  R"(map: {
4620  seq: [
4621  0,
4622  1,
4623  2,
4624  3,
4625  [
4626  40,
4627  41
4628  ]
4629  ]
4630  }
4631 )");
4632  // Let's set it one BLOCK level further:
4633  tree["map"].set_container_style(ryml::BLOCK);
4634  CHECK(tostr(tree, noindent) == // notice it is indented one more level
4635  R"(map:
4636  seq: [
4637  0,
4638  1,
4639  2,
4640  3,
4641  [
4642  40,
4643  41
4644  ]
4645  ]
4646 )");
4647 }

◆ sample_anchors_and_aliases()

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

4672  : {
4673 seq: [
4674 0,
4675 1,
4676 2,
4677 3,
4678 [
4679 40,
4680 41
4681 ]
4682 ]
4683 }
4684 }
4685 )";
4686  // note that the parser defaults to detect multiline flow
4687  // (FLOW_ML) containers:
4688  {
4689  const ryml::Tree tree = ryml::parse_in_arena(yaml);
4690  CHECK(tree["map"].is_flow_ml()); // etc
4691  // emitted yaml is exactly equal to parsed yaml:
4692  CHECK(ryml::emitrs_yaml<std::string>(tree) == yaml);
4693  }
4694  // if you prefer to shorten the emitted yaml, you can set the
4695  // parser to set singleline flow (FLOW_SL) on all flow containers:
4696  {
4697  const ryml::ParserOptions opts = ryml::ParserOptions{}.detect_flow_ml(false);
4698  const ryml::Tree tree = ryml::parse_in_arena(yaml, opts);
4699  CHECK(tree["map"].is_flow_sl()); // etc
4700  // notice how this is smaller now:
4701  CHECK(ryml::emitrs_yaml<std::string>(tree) ==
4702  R"({map: {seq: [0,1,2,3,[40,41]]}})");
4703  }
4704  // you can also keep FLOW_ML, but control its indentation:
4705  // (see more details in @ref sample_style_flow_ml_indent())
4706  {
4707  const ryml::EmitOptions noindent = ryml::EmitOptions{}.indent_flow_ml(false);
4708  const ryml::Tree tree = ryml::parse_in_arena(yaml);
4709  CHECK(tree["map"].is_flow_ml()); // etc
4710  CHECK(ryml::emitrs_yaml<std::string>(tree, noindent) == yaml_not_indented);
4711  }
4712 }
4713 
4714 
4715 //-----------------------------------------------------------------------------
4716 
4717 /** shows how to parse and emit JSON.
4718  *
4719  * To emit YAML parsed from JSON, see also @ref sample_style() for
4720  * info on clearing the style flags (example below). */
4721 void sample_json()
4722 {
4723  ryml::csubstr json = R"({
4724  "doe": "a deer, a female deer",
4725  "ray": "a drop of golden sun",
4726  "me": "a name, I call myself",
4727  "far": "a long long way to go"
4728 }
4729 )";
4730  // Since JSON is a subset of YAML, parsing JSON is just the
4731  // same as YAML:
4732  ryml::Tree tree = ryml::parse_in_arena(json);
4733  // If you are sure the source is valid json, you can use the
4734  // appropriate parse_json overload, which is faster because json
4735  // has a smaller grammar:
4736  ryml::Tree json_tree = ryml::parse_json_in_arena(json);
4737  // to emit JSON:
4738  CHECK(ryml::emitrs_json<std::string>(tree) == json);
4739  CHECK(ryml::emitrs_json<std::string>(json_tree) == json);
4740  // to emit JSON to a stream:
4741  std::stringstream ss;
4742  ss << ryml::as_json(tree); // <- mark it like this
4743  CHECK(ss.str() == json);

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

◆ sample_anchors_and_aliases_create()

void sample_anchors_and_aliases_create ( )

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

Definition at line 4747 of file quickstart.cpp.

4767  : "a deer, a female deer"
4768 "ray": "a drop of golden sun"
4769 "me": "a name, I call myself"
4770 "far": "a long long way to go"
4771 )");
4772  // if you don't want the double quotes in the scalar, you can
4773  // recurse:
4774  json_tree.rootref().clear_style(/*recurse*/true);
4775  // so now when emitting you will get this:
4776  // (the scalars with a comma are single-quote)
4777  CHECK(ryml::emitrs_yaml<std::string>(json_tree) ==
4778  R"(doe: 'a deer, a female deer'
4779 ray: a drop of golden sun
4780 me: 'a name, I call myself'
4781 far: a long long way to go
4782 )");
4783  // you can do custom style changes based on a type mask. this
4784  // will change set the style of all scalar values to single-quoted
4785  json_tree.rootref().set_style_conditionally(ryml::VAL,
4786  /*remflags*/ryml::VAL_STYLE,
4787  /*addflags*/ryml::VAL_SQUO,
4788  /*recurse*/true);
4789  CHECK(ryml::emitrs_yaml<std::string>(json_tree) ==
4790  R"(doe: 'a deer, a female deer'
4791 ray: 'a drop of golden sun'
4792 me: 'a name, I call myself'
4793 far: 'a long long way to go'
4794 )");
4795  // see in particular sample_style() for more examples
4796 }
4797 
4798 
4799 //-----------------------------------------------------------------------------
4800 
4801 /** demonstrates usage with anchors and alias references.
4802 
4803 Note that dereferencing is opt-in; after parsing, you have to call
4804 @ref c4::yml::Tree::resolve() explicitly if you want resolved
4805 references in the tree. This method will resolve all references and
4806 substitute the anchored values in place of the reference.
4807 
4808 The @ref c4::yml::Tree::resolve() method first does a full traversal
4809 of the tree to gather all anchors and references in a separate
4810 collection, then it goes through that collection to locate the names,
4811 which it does by obeying the YAML standard diktat that
4812 
4813  > an alias node refers to the most recent node in
4814  > the serialization having the specified anchor
4815 
4816 So, depending on the number of anchor/alias nodes, this is a
4817 potentially expensive operation, with a best-case linear complexity
4818 (from the initial traversal) and a worst-case quadratic complexity (if
4819 every node has an alias/anchor). This potential cost is the reason for
4820 requiring an explicit call to @ref c4::yml::Tree::resolve() */
4821 void sample_anchors_and_aliases()
4822 {
4823  std::string unresolved = R"(base: &base
4824  name: Everyone has same name
4825 foo: &foo

◆ sample_tags()

void sample_tags ( )

Definition at line 4830 of file quickstart.cpp.

4830  : 20
4831 bill_to: &id001
4832  street: |-
4833  123 Tornado Alley
4834  Suite 16
4835  city: East Centerville
4836  state: KS
4837 ship_to: *id001
4838 &keyref key: &valref val
4839 *valref : *keyref
4840 )";
4841  std::string resolved = R"(base:
4842  name: Everyone has same name
4843 foo:
4844  name: Everyone has same name
4845  age: 10
4846 bar:
4847  name: Everyone has same name
4848  age: 20
4849 bill_to:
4850  street: |-
4851  123 Tornado Alley
4852  Suite 16
4853  city: East Centerville
4854  state: KS
4855 ship_to:
4856  street: |-
4857  123 Tornado Alley
4858  Suite 16
4859  city: East Centerville
4860  state: KS
4861 key: val
4862 val: key
4863 )";
4864 
4865  ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(unresolved));
4866  // by default, references are not resolved when parsing:
4867  CHECK( ! tree["base"].has_key_anchor());
4868  CHECK( tree["base"].has_val_anchor());
4869  CHECK( tree["base"].val_anchor() == "base");
4870  CHECK( tree["key"].key_anchor() == "keyref");
4871  CHECK( tree["key"].val_anchor() == "valref");
4872  CHECK( tree["*valref"].is_key_ref());
4873  CHECK( tree["*valref"].is_val_ref());
4874  CHECK( tree["*valref"].key_ref() == "valref");
4875  CHECK( tree["*valref"].val_ref() == "keyref");
4876 
4877  // to resolve references, simply call tree.resolve(),
4878  // which will perform the reference instantiations:
4879  tree.resolve();
4880 
4881  // all the anchors and references are substistuted and then removed:
4882  CHECK( ! tree["base"].has_key_anchor());
4883  CHECK( ! tree["base"].has_val_anchor());
4884  CHECK( ! tree["base"].has_val_anchor());
4885  CHECK( ! tree["key"].has_key_anchor());
4886  CHECK( ! tree["key"].has_val_anchor());
4887  CHECK( ! tree["val"].is_key_ref()); // notice *valref is now turned to val
4888  CHECK( ! tree["val"].is_val_ref()); // notice *valref is now turned to val
4889 
4890  CHECK(tree["ship_to"]["city"].val() == "East Centerville");
4891  CHECK(tree["ship_to"]["state"].val() == "KS");
4892 }
4893 
4894 /** demonstrates how to use the API to programatically create anchors
4895  * and aliases */
4896 void sample_anchors_and_aliases_create()
4897 {
4898  // part 1: anchor/ref
4899  {
4900  ryml::Tree t;
4901  t.rootref() |= ryml::MAP|ryml::BLOCK;
4902  t["kanchor"] = "2";
4903  t["kanchor"].set_key_anchor("kanchor");
4904  t["vanchor"] = "3";
4905  t["vanchor"].set_val_anchor("vanchor");
4906  // to set a reference, need to call .set_val_ref()/.set_key_ref()
4907  t["kref"].set_val_ref("kanchor");
4908  t["vref"].set_val_ref("vanchor");
4909  t["nref"] = "*vanchor"; // NOTE: this is not set as a reference in the tree!
4910  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(&kanchor kanchor: 2
4911 vanchor: &vanchor 3
4912 kref: *kanchor
4913 vref: *vanchor
4914 nref: '*vanchor'
4915 )"); // note that ryml emits nref with quotes to disambiguate (because no style was set)
4916  t.resolve();
4917  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(kanchor: 2
4918 vanchor: 3
4919 kref: kanchor
4920 vref: 3
4921 nref: '*vanchor'
4922 )"); // note that nref was not resolved
4923  }
4924 
4925  // part 2: simple inheritance (ie, adding `<<: *anchor` nodes)
4926  {
4927  ryml::Tree t = ryml::parse_in_arena(R"(
4928 orig: &orig {foo: bar, baz: bat}
4929 copy: {}
4930 notcopy: {}
4931 notref: {}
4932 )");
4933  t["copy"]["<<"].set_val_ref("orig");
4934  t["notcopy"]["test"].set_val_ref("orig");
4935  t["notcopy"]["<<"].set_val_ref("orig");
4936  t["notref"]["<<"] = "*orig"; // not a reference! .set_val_ref() was not called
4937  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(orig: &orig {foo: bar,baz: bat}
4938 copy: {<<: *orig}
4939 notcopy: {test: *orig,<<: *orig}
4940 notref: {<<: '*orig'}
4941 )");
4942  t.resolve();
4943  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(orig: {foo: bar,baz: bat}
4944 copy: {foo: bar,baz: bat}
4945 notcopy: {test: {foo: bar,baz: bat},foo: bar,baz: bat}
4946 notref: {<<: '*orig'}
4947 )");
4948  }
4949 
4950  // part 3: multiple inheritance (ie, `<<: [*ref1,*ref2,*etc]`)
4951  {
4952  ryml::Tree t = ryml::parse_in_arena(R"(orig1: &orig1 {foo: bar}
4953 orig2: &orig2 {baz: bat}
4954 orig3: &orig3 {and: more}
4955 copy: {}
4956 )");
4957  ryml::NodeRef seq = t["copy"]["<<"];
4958  seq |= ryml::SEQ;
4959  seq.append_child().set_val_ref("orig1");
4960  seq.append_child().set_val_ref("orig2");
4961  seq.append_child().set_val_ref("orig3");
4962  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(orig1: &orig1 {foo: bar}
4963 orig2: &orig2 {baz: bat}
4964 orig3: &orig3 {and: more}
4965 copy: {<<: [*orig1,*orig2,*orig3]}

◆ sample_tag_directives()

void sample_tag_directives ( )

Definition at line 4970 of file quickstart.cpp.

4970  : {and: more}
4971 copy: {foo: bar,baz: bat,and: more}
4972 )");
4973  }
4974 }
4975 
4976 
4977 //-----------------------------------------------------------------------------
4978 
4979 void sample_tags()
4980 {
4981  const std::string yaml = R"(--- !!map
4982 a: 0
4983 b: 1
4984 --- !map
4985 a: b
4986 --- !!seq
4987 - a
4988 - b
4989 --- !!str a b
4990 --- !!str 'a: b'
4991 ---
4992 !!str a: b
4993 --- !!set
4994 ? a
4995 ? b
4996 --- !!set
4997 a:
4998 --- !!seq
4999 - !!int 0

◆ sample_docs()

void sample_docs ( )

Definition at line 5004 of file quickstart.cpp.

5006  : root.children())
5007  CHECK(doc.is_doc());
5008  // tags are kept verbatim from the source:
5009  CHECK(root[0].has_val_tag());
5010  CHECK(root[0].val_tag() == "!!map"); // valid only if the node has a val tag
5011  CHECK(root[1].val_tag() == "!map");
5012  CHECK(root[2].val_tag() == "!!seq");
5013  CHECK(root[3].val_tag() == "!!str");
5014  CHECK(root[4].val_tag() == "!!str");
5015  CHECK(root[5]["a"].has_key_tag());
5016  CHECK(root[5]["a"].key_tag() == "!!str"); // valid only if the node has a key tag
5017  CHECK(root[6].val_tag() == "!!set");
5018  CHECK(root[7].val_tag() == "!!set");
5019  CHECK(root[8].val_tag() == "!!seq");
5020  CHECK(root[8][0].val_tag() == "!!int");
5021  CHECK(root[8][1].val_tag() == "!!str");
5022  // ryml also provides a complete toolbox to deal with tags.
5023  // there is an enumeration for the standard YAML tags:
5024  CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
5025  CHECK(ryml::to_tag("!!map") == ryml::TAG_MAP);
5026  CHECK(ryml::to_tag("!!seq") == ryml::TAG_SEQ);
5027  CHECK(ryml::to_tag("!!str") == ryml::TAG_STR);
5028  CHECK(ryml::to_tag("!!int") == ryml::TAG_INT);
5029  CHECK(ryml::to_tag("!!set") == ryml::TAG_SET);
5030  // given a tag enum, you can fetch the short tag string:
5032  CHECK(ryml::from_tag(ryml::TAG_MAP) == "!!map");
5033  CHECK(ryml::from_tag(ryml::TAG_SEQ) == "!!seq");
5034  CHECK(ryml::from_tag(ryml::TAG_STR) == "!!str");
5035  CHECK(ryml::from_tag(ryml::TAG_INT) == "!!int");
5036  CHECK(ryml::from_tag(ryml::TAG_SET) == "!!set");
5037  // you can also fetch the long tag string:
5039  CHECK(ryml::from_tag_long(ryml::TAG_MAP) == "<tag:yaml.org,2002:map>");
5040  CHECK(ryml::from_tag_long(ryml::TAG_SEQ) == "<tag:yaml.org,2002:seq>");
5041  CHECK(ryml::from_tag_long(ryml::TAG_STR) == "<tag:yaml.org,2002:str>");
5042  CHECK(ryml::from_tag_long(ryml::TAG_INT) == "<tag:yaml.org,2002:int>");
5043  CHECK(ryml::from_tag_long(ryml::TAG_SET) == "<tag:yaml.org,2002:set>");
5044  // and likewise:
5045  CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
5046  CHECK(ryml::to_tag("<tag:yaml.org,2002:map>") == ryml::TAG_MAP);
5047  CHECK(ryml::to_tag("<tag:yaml.org,2002:seq>") == ryml::TAG_SEQ);
5048  CHECK(ryml::to_tag("<tag:yaml.org,2002:str>") == ryml::TAG_STR);
5049  CHECK(ryml::to_tag("<tag:yaml.org,2002:int>") == ryml::TAG_INT);
5050  CHECK(ryml::to_tag("<tag:yaml.org,2002:set>") == ryml::TAG_SET);
5051  // to normalize a tag as much as possible, use normalize_tag():
5052  CHECK(ryml::normalize_tag("!!map") == "!!map");
5053  CHECK(ryml::normalize_tag("!<tag:yaml.org,2002:map>") == "!!map");
5054  CHECK(ryml::normalize_tag("<tag:yaml.org,2002:map>") == "!!map");
5055  CHECK(ryml::normalize_tag("tag:yaml.org,2002:map") == "!!map");
5056  CHECK(ryml::normalize_tag("!<!!map>") == "<!!map>");
5057  CHECK(ryml::normalize_tag("!map") == "!map");
5058  CHECK(ryml::normalize_tag("!my!foo") == "!my!foo");
5059  // and also for the long form:
5060  CHECK(ryml::normalize_tag_long("!!map") == "<tag:yaml.org,2002:map>");
5061  CHECK(ryml::normalize_tag_long("!<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
5062  CHECK(ryml::normalize_tag_long("<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
5063  CHECK(ryml::normalize_tag_long("tag:yaml.org,2002:map") == "<tag:yaml.org,2002:map>");
5064  CHECK(ryml::normalize_tag_long("!<!!map>") == "<!!map>");
5065  CHECK(ryml::normalize_tag_long("!map") == "!map");
5066  // The tree provides the following methods applying to every node
5067  // with a key and/or val tag:
5068  ryml::Tree normalized_tree = tree;
5069  normalized_tree.normalize_tags(); // normalize all tags in short form
5070  CHECK(ryml::emitrs_yaml<std::string>(normalized_tree) == R"(--- !!map
5071 a: 0
5072 b: 1
5073 --- !map
5074 a: b
5075 --- !!seq
5076 - a
5077 - b
5078 --- !!str a b
5079 --- !!str 'a: b'
5080 ---
5081 !!str a: b
5082 --- !!set
5083 a:
5084 b:
5085 --- !!set
5086 a:
5087 --- !!seq
5088 - !!int 0
5089 - !!str 1
5090 )");
5091  ryml::Tree normalized_tree_long = tree;
5092  normalized_tree_long.normalize_tags_long(); // normalize all tags in short form
5093  CHECK(ryml::emitrs_yaml<std::string>(normalized_tree_long) == R"(--- !<tag:yaml.org,2002:map>
5094 a: 0
5095 b: 1
5096 --- !map
5097 a: b
5098 --- !<tag:yaml.org,2002:seq>
5099 - a
5100 - b
5101 --- !<tag:yaml.org,2002:str> a b
5102 --- !<tag:yaml.org,2002:str> 'a: b'
5103 ---
5104 !<tag:yaml.org,2002:str> a: b
5105 --- !<tag:yaml.org,2002:set>
5106 a:
5107 b:
5108 --- !<tag:yaml.org,2002:set>
5109 a:
5110 --- !<tag:yaml.org,2002:seq>
5111 - !<tag:yaml.org,2002:int> 0
5112 - !<tag:yaml.org,2002:str> 1
5113 )");
5114 }
5115 
5116 
5117 //-----------------------------------------------------------------------------
5118 
5119 void sample_tag_directives()
5120 {
5121  const std::string yaml = R"(
void normalize_tags()
Definition: tree.cpp:1579
void normalize_tags_long()
Definition: tree.cpp:1586
void sample_tag_directives()
csubstr from_tag_long(YamlTag_e tag)
Definition: tag.cpp:128
csubstr normalize_tag_long(csubstr tag)
Definition: tag.cpp:31
csubstr normalize_tag(csubstr tag)
Definition: tag.cpp:19
csubstr from_tag(YamlTag_e tag)
Definition: tag.cpp:168
YamlTag_e to_tag(csubstr tag)
Definition: tag.cpp:67
@ TAG_SET
!!set Unordered set of non-equal values.
Definition: tag.hpp:32
@ TAG_INT
!!float Mathematical integers.
Definition: tag.hpp:38
@ TAG_SEQ
!!seq Sequence of arbitrary values.
Definition: tag.hpp:33
@ TAG_NONE
Definition: tag.hpp:27
@ TAG_STR
!!str A sequence of zero or more Unicode characters.
Definition: tag.hpp:41
@ TAG_MAP
!!map Unordered set of key: value pairs without duplicates.
Definition: tag.hpp:29

◆ sample_error_handler()

void sample_error_handler ( )

demonstrates how to set a custom error handler for ryml

Definition at line 5144 of file quickstart.cpp.

5154 {
5155  std::string yml = R"(---
5156 a: 0
5157 b: 1
5158 ---
5159 c: 2

◆ sample_error_basic()

void sample_error_basic ( )

Definition at line 5164 of file quickstart.cpp.

5171  {
5172  // using the node API
5173  const ryml::ConstNodeRef stream = tree.rootref();
5174  CHECK(stream.is_root());
5175  CHECK(stream.is_stream());
5176  CHECK(!stream.is_doc());
5177  CHECK(stream.num_children() == 3);
5178  for(const ryml::ConstNodeRef doc : stream.children())
5179  CHECK(doc.is_doc());
5180  CHECK(tree.docref(0).id() == stream.child(0).id());
5181  CHECK(tree.docref(1).id() == stream.child(1).id());
5182  CHECK(tree.docref(2).id() == stream.child(2).id());
5183  // equivalent: using the lower level index API
5184  const ryml::id_type stream_id = tree.root_id();
5185  CHECK(tree.is_root(stream_id));
5186  CHECK(tree.is_stream(stream_id));
5187  CHECK(!tree.is_doc(stream_id));
5188  CHECK(tree.num_children(stream_id) == 3);
5189  for(ryml::id_type doc_id = tree.first_child(stream_id); doc_id != ryml::NONE; doc_id = tree.next_sibling(stream_id))
bool is_root() const RYML_NOEXCEPT
Forward to Tree::is_root().
Definition: node.hpp:307
bool is_stream() const RYML_NOEXCEPT
Forward to Tree::is_stream().
Definition: node.hpp:235
bool is_doc() const RYML_NOEXCEPT
Forward to Tree::is_doc().
Definition: node.hpp:236
auto child(id_type pos) RYML_NOEXCEPT -> Impl
Forward to Tree::child().
Definition: node.hpp:349

◆ sample_error_parse()

void sample_error_parse ( )

Definition at line 5191 of file quickstart.cpp.

5243  {
5244  const std::string expected_json[] = {
5245  "{\n \"a\": 0,\n \"b\": 1\n}\n",
5246  "{\n \"c\": 2,\n \"d\": 3\n}\n",
5247  "[\n 4,\n 5,\n 6,\n 7\n]\n",
5248  };
5249  // using the node API
5250  {
5251  ryml::id_type count = 0;
5252  const ryml::ConstNodeRef stream = tree.rootref();
5253  CHECK(stream.num_children() == (ryml::id_type)C4_COUNTOF(expected_json));
5254  for(ryml::ConstNodeRef doc : stream.children())
5255  {
5256  CHECK(ryml::emitrs_json<std::string>(doc) == expected_json[count++]);
5257  }
5258  }
5259  // equivalent: using the index API
5260  {
5261  ryml::id_type count = 0;
5262  const ryml::id_type stream_id = tree.root_id();
5263  CHECK(tree.num_children(stream_id) == (ryml::id_type)C4_COUNTOF(expected_json));
5264  for(ryml::id_type doc_id = tree.first_child(stream_id); doc_id != ryml::NONE; doc_id = tree.next_sibling(doc_id))
5265  {
5266  CHECK(ryml::emitrs_json<std::string>(tree, doc_id) == expected_json[count++]);
5267  }
5268  }
5269  }
5270 }
5271 
5272 
5273 //-----------------------------------------------------------------------------
5274 
5275 // To avoid imposing a particular type of error handling, ryml uses
5276 // error handler callbacks. This enables users to use exceptions, or
5277 // setjmp()/longjmp(), or plain calls to abort(), as they see fit.
5278 //
5279 // However, it is important to note that the error callbacks must never
5280 // return to the caller! Otherwise, an infinite loop or program crash
5281 // will likely occur.
5282 //
5283 // For this reason, to recover from an error when exceptions are disabled,
5284 // then a non-local jump must be performed using setjmp()/longjmp().
5285 // The code below demonstrates both flows.
5286 //
5287 // ryml provides default error handlers, which call
5288 // std::abort(). You can use the cmake option and the macro
5289 // RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS to have the default error
5290 // handler throw an exception instead.
5291 
5292 /** demonstrates how to set a custom error handler for ryml */
5293 void sample_error_handler()
5294 {
5295  ErrorHandlerExample errh; // browse this class to understand more details
5296  errh.check_disabled();
5297  // set the global error handlers. Note the error callbacks must
5298  // never return: they must either throw an exception, use setjmp()
5299  // and longjmp(), or abort. Otherwise, the parser will enter into
5300  // an infinite loop, or the program may crash.
5302  errh.check_enabled();
5303  CHECK(errh.check_error_occurs([&]{
5304  ryml::Tree tree = ryml::parse_in_arena("errorhandler.yml", "[a: b\n}");
5305  }));
5306  ryml::set_callbacks(errh.defaults); // restore defaults.
5307  errh.check_disabled();
5308 }
5309 
5310 
5311 //-----------------------------------------------------------------------------
5312 
5313 void sample_error_basic()
5314 {
5315  auto cause_basic_error = []{
5316  ryml::TagDirective tag = {};
5317  return tag.create_from_str("%%%TAG abc");
5318  };
5319  {
5320  ScopedErrorHandlerExample errh; // set the example callbacks (scoped)
5321  CHECK(errh.check_error_occurs(cause_basic_error));
5322  }
5323 #ifdef _RYML_WITH_EXCEPTIONS
5324  bool gotit = false;
5325  try
5326  {
5327  cause_basic_error();
5328  }
5329  catch(ryml::ExceptionBasic const& exc)
5330  {
5331  gotit = true;
5332  ryml::csubstr msg = ryml::to_csubstr(exc.what());
5333  CHECK(!exc.errdata_basic.location.name.empty());
5334  CHECK(!msg.empty());
5335  }
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:89
void sample_error_basic()
void sample_error_handler()
demonstrates how to set a custom error handler for ryml
void check_disabled() const
test that this handler is currently not set
void check_enabled() const
test that this handler is currently set
an example error handler, required for some of the quickstart examples.
Definition: quickstart.cpp:224
ryml::Callbacks defaults
Definition: quickstart.cpp:226
Location location
location where the error was detected (may be from YAML or C++ source code)
Definition: common.hpp:313
Exception thrown by the default basic error implementation.
Definition: error.hpp:456
ErrorDataBasic errdata_basic
error data
Definition: error.hpp:459
const char * what() const noexcept override
Definition: error.hpp:458
csubstr name
name of the file
Definition: common.hpp:287
bool create_from_str(csubstr directive_)
leaves next_node_id unfilled
Definition: tag.cpp:209

◆ sample_error_visit()

void sample_error_visit ( )

Visit errors happen when an error is triggered while reading from a node.

Definition at line 5340 of file quickstart.cpp.

5341 {
5342  ryml::csubstr ymlsrc = R"({
5343  a: b
5344  [
5345 )";
5346  ryml::csubstr ymlfile = "file.yml";
5347  auto cause_parse_error = [&]{
5348  return ryml::parse_in_arena(ymlfile, ymlsrc);
5349  };
5350  // the YAML in ymlsrc must cause a parse error while it is being
5351  // parsed. We use our error handler to catch that error, and save
5352  // the error info:
5353  ErrorHandlerExample errh;
5354  {
5356  CHECK(errh.check_error_occurs(cause_parse_error));
5357  // the handler in errh saves the error info in itself. Let's
5358  // use that to see the messages we get.
5359  //
5360  // this message is the short message passed into the parse
5361  // error handler:
5362  CHECK(errh.saved_msg_short == "invalid character: '['");
5363  // this message was created inside the handler, by calling
5364  // ryml::err_parse_format():
5365  CHECK(ryml::to_csubstr(errh.saved_msg_full).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
5366  // If you keep the YAML source buffer around, you can also use
5367  // it to create/print a larger error message showing the
5368  // YAML source code context which causes the error:
5369  std::string msg_ctx = errh.saved_msg_full + "\n";
5370  ryml::location_format_with_context([&msg_ctx](ryml::csubstr s){
5371  msg_ctx.append(s.str, s.len);
5372  }, errh.saved_parse_loc, ymlsrc, "err");
5373  CHECK(ryml::to_csubstr(msg_ctx).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
5374  CHECK(ryml::to_csubstr(msg_ctx).ends_with(R"(file.yml:3: col=4 (12B): err:
5375 err:
5376 err: [
5377 err: |
5378 err: (here)
5379 err:
5380 err: see region:
5381 err:
5382 err: {
5383 err: a: b
5384 err: [
5385 err: |
5386 err: (here)
5387 )"));
5388  //
5389  // Let's now check the location (see the message above):
5390  CHECK(errh.saved_parse_loc.name == ymlfile);
5391  CHECK(errh.saved_parse_loc.line == 3);
5392  CHECK(errh.saved_parse_loc.col == 4);
5393  CHECK(errh.saved_parse_loc.offset == 12);
5394  CHECK(errh.saved_parse_loc.offset <= ymlsrc.len);
5395  // ... and this is the location in the ryml source code file where
5396  // this error was found:
5397  CHECK(!errh.saved_basic_loc.name.empty());
5398  CHECK(errh.saved_basic_loc.line > 0);
5399  CHECK(errh.saved_basic_loc.col > 0);
5400  CHECK(errh.saved_basic_loc.offset > 0);
5402  }
5403  // A parse error is also a basic error. If no parse error handler
5404  // is set, then ryml falls back to a basic error:
5405  {
5406  ryml::Callbacks cb = errh.callbacks();
5407  cb.m_error_parse = nullptr;
5408  ryml::set_callbacks(cb);
5409  CHECK(ryml::get_callbacks().m_error_parse == nullptr);
5410  CHECK(errh.check_error_occurs(cause_parse_error));
5411  // we got a basic error instead of a parse error:
5412  CHECK(errh.saved_msg_short == "invalid character: '['");
5413  // notice that the full message now displays this as a basic
5414  // error:
5415  CHECK(errh.saved_msg_full == "file.yml:3: col=4 (12B): ERROR: [basic] invalid character: '['");
5416  // the yml location is now in the location saved from the basic error
5417  CHECK(errh.saved_basic_loc.name == ymlfile);
5418  CHECK(errh.saved_basic_loc.line == 3);
5419  CHECK(errh.saved_basic_loc.col == 4);
5420  CHECK(errh.saved_basic_loc.offset == 12);
5421  CHECK(errh.saved_basic_loc.offset <= ymlsrc.len);
5422  CHECK(errh.saved_parse_loc.name == ryml::csubstr{});
5427  }
5428 #ifdef _RYML_WITH_EXCEPTIONS
5429  bool gotit = false;
5430  try
5431  {
5432  cause_parse_error();
5433  }
5434  catch(ryml::ExceptionParse const& exc)
5435  {
5436  gotit = true;
Callbacks const & get_callbacks()
get the global callbacks
Definition: common.cpp:94
void location_format_with_context(DumpFn &&dumpfn, Location const &location, csubstr source_buffer, csubstr call, size_t num_lines_before, size_t num_lines_after, size_t first_col_highlight, size_t last_col_highlight, size_t maxlen)
Generic formatting of a location, printing the source code buffer region around the location.
Definition: error.def.hpp:83
ryml::Location saved_basic_loc
Definition: quickstart.cpp:251
std::string saved_msg_full
Definition: quickstart.cpp:249
std::string saved_msg_short
Definition: quickstart.cpp:248
ryml::Location saved_parse_loc
Definition: quickstart.cpp:252
A c-style callbacks class to customize behavior on errors or allocation.
Definition: common.hpp:511
pfn_error_parse m_error_parse
a pointer to a parse error handler function
Definition: common.hpp:516
Exception thrown by the default parse error implementation.
Definition: error.hpp:475
size_t offset
number of bytes from the beginning of the source buffer
Definition: common.hpp:284

References ErrorHandlerExample::callbacks(), CHECK, ErrorHandlerExample::check_error_occurs(), c4::yml::Location::col, ErrorHandlerExample::defaults, c4::yml::ExceptionParse::errdata_parse, c4::yml::get_callbacks(), c4::yml::Location::line, c4::yml::location_format_with_context(), c4::yml::Callbacks::m_error_parse, c4::yml::Location::name, c4::yml::npos, c4::yml::Location::offset, c4::yml::parse_in_arena(), ErrorHandlerExample::saved_basic_loc, ErrorHandlerExample::saved_msg_full, ErrorHandlerExample::saved_msg_short, ErrorHandlerExample::saved_parse_loc, c4::yml::set_callbacks(), c4::to_csubstr(), c4::yml::ExceptionBasic::what(), and c4::yml::ErrorDataParse::ymlloc.

◆ sample_error_visit_location()

void sample_error_visit_location ( )

It is possible to obtain the YAML location from a visit error: when the tree is obtained from parsing YAML, the messages may be enriched by using a parser set to track the locations.

See sample_location_tracking() for more details on how to use locations.

Definition at line 5444 of file quickstart.cpp.

5444  : '['");
5445  // to print richer error messages, ryml provides helpers to
5446  // format that description into a complete error message,
5447  // containing location and source context indication:
5448  std::string full;
5449  auto dumpfn = [&full](ryml::csubstr s) { full.append(s.str, s.len); };
5450  ryml::err_parse_format(dumpfn, msg, exc.errdata_parse);
5451  full += '\n';
5452  ryml::location_format_with_context(dumpfn, exc.errdata_parse.ymlloc, ymlsrc, "err", 3);
5453  CHECK(ryml::to_csubstr(full).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
5454  CHECK(ryml::to_csubstr(full).ends_with(R"(file.yml:3: col=4 (12B): err:
5455 err:
5456 err: [
5457 err: |
5458 err: (here)
5459 err:
5460 err: see region:
5461 err:
5462 err: {
5463 err: a: b
5464 err: [
5465 err: |
5466 err: (here)
5467 )"));
5468  }
5469  CHECK(gotit);
5470  gotit = false;
5471  try
5472  {
5473  cause_parse_error();
5474  }
5475  catch(ryml::ExceptionBasic const& exc) // use references! don't slice the exception
5476  {
5477  gotit = true;
5478  ryml::csubstr msg = ryml::to_csubstr(exc.what());
5479  CHECK(!exc.errdata_basic.location.name.empty());
5480  CHECK(!msg.empty());
5481  }
5482  CHECK(gotit);
5483 #endif
5484 }
5485 
5486 
5487 /** Visit errors happen when an error is triggered while reading from
5488  * a node. */
5489 void sample_error_visit()
5490 {
5491  ryml::csubstr ymlfile = "file.yml";
5492  ryml::csubstr ymlsrc = "float: 123.456";
5493  ErrorHandlerExample errh;
5494  {
5495  ryml::set_callbacks(errh.callbacks());
5496  ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
5497  CHECK(errh.check_error_occurs([&]{
5498  int intval = 0;
5499  tree["float"] >> intval; // cannot deserialize 123.456 to int
5500  }));
5501  // the handler in errh saves the error info in itself. Let's
5502  // use that to see the messages we get.
5503  //
5504  // this message is the short message passed into the visit error
5505  CHECK(errh.saved_msg_short == "could not deserialize value");
5506  // this message was created inside the handler, by calling
5507  // ryml::err_visit_format():
5508  CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [visit] could not deserialize value"));
5509  // The location of the visit error is of the C++ source file where
5510  // the error was detected -- NOT of the YAML source file:
5511  CHECK(errh.saved_basic_loc.name != ymlfile);
5512  // However, note that the tree and node id are available:
5513  CHECK(errh.saved_visit_tree == &tree);
5514  CHECK(errh.saved_visit_id == tree["float"].id());
5515  // see sample_error_visit_location() for an example on how
5516  // to extract the location.

◆ sample_global_allocator()

void sample_global_allocator ( )

demonstrates how to set the global allocator for ryml

Definition at line 5616 of file quickstart.cpp.

5622  {
5623  CHECK(errh.check_error_occurs(cause_visit_error));
5624  // the handler in errh saves the error info in itself. Let's
5625  // use that to see the messages we get.
5626  //
5627  // this message is the short message passed into the visit error
5628  CHECK(errh.saved_msg_short == "could not deserialize value");
5629  // this message was created inside the handler, by calling
5630  // ryml::err_visit_format():
5631  CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [visit] could not deserialize value"));
5632  // The location of the visit error is of the C++ source file where
5633  // the error was detected -- NOT of the YAML source file:
5634  CHECK(errh.saved_basic_loc.name != ymlfile);
5635  // However, note that the tree and node id are available:
5636  CHECK(errh.saved_visit_tree == &tree);
5637  CHECK(errh.saved_visit_id == tree["float"].id());
5638  // ... which we can use to get the location in the YAML source
5639  // from the parser (but see @ref sample_location_tracking()):
5640  ryml::Location ymlloc = errh.saved_visit_tree->location(parser, errh.saved_visit_id);
5641  CHECK(ymlloc.name == ymlfile);
5642  // In turn, we can use format_location_context() to
5643  // print/create an error message pointing at the YAML source
5644  // code:
5645  std::string msg = errh.saved_msg_full;
5646  ryml::location_format_with_context([&msg](ryml::csubstr s){
5647  msg.append(s.str, s.len);
5648  }, ymlloc, ymlsrc, "err", /*number of lines to show before the error*/3);
5649  CHECK(ryml::to_csubstr(msg).ends_with(R"(file.yml:3: col=0 (24B): err:
5650 err:
5651 err: float: 123.456
5652 err: |
5653 err: (here)
5654 err:
5655 err: see region:
5656 err:
5657 err: foo: bar
5658 err: char: a
5659 err: int: a
5660 err: float: 123.456
5661 err: |
5662 err: (here)
5663 )"));
5664  }
5665 }
5666 
5667 
5668 //-----------------------------------------------------------------------------
5669 

◆ sample_per_tree_allocator()

void sample_per_tree_allocator ( )

Definition at line 5739 of file quickstart.cpp.

5739  {
5740  ((GlobalAllocatorExample*)this_)->free(mem, len);
5741  }
5742 
5743  // checking
5745  {
5746  check_and_reset();
5747  }
5748  void check_and_reset()
5749  {
5750  std::cout << "size: alloc=" << alloc_size << " dealloc=" << dealloc_size << std::endl;
5751  std::cout << "count: #allocs=" << num_allocs << " #deallocs=" << num_deallocs << std::endl;
5752  CHECK(num_allocs == num_deallocs);
5753  CHECK(alloc_size >= dealloc_size); // failure here means a double free
5754  CHECK(alloc_size == dealloc_size); // failure here means a leak
5755  num_allocs = 0;
5756  num_deallocs = 0;
5757  alloc_size = 0;
5758  dealloc_size = 0;
5759  }
5760 };
5761 /** @} */
5762 
5763 
5764 /** demonstrates how to set the global allocator for ryml */

◆ sample_static_trees()

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

5784  {foo: bar})");
5785  mem.check_and_reset();
5786 
5787  // parse another tree and check
5788  (void)ryml::parse_in_arena(R"([a, b, c, d, {foo: bar, money: pennys}])");
5789  mem.check_and_reset();
5790 
5791  // verify that by reserving we save allocations

◆ sample_location_tracking()

void sample_location_tracking ( )

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

Definition at line 5799 of file quickstart.cpp.

5810  {foo: bar, money: pennys}])", &tree);
5811  CHECK(mem.alloc_size == size_before);
5812  CHECK(mem.num_allocs == 3);
5813  }
5814  mem.check_and_reset();
5815 
5816  // restore defaults.
5817  ryml::set_callbacks(defaults);
5818 }
5819 
5820 
5821 //-----------------------------------------------------------------------------
5822 
5823 /** @addtogroup doc_sample_helpers
5824  * @{ */
5825 
5826 /** an example for a per-tree memory allocator */
5827 struct PerTreeMemoryExample
5828 {
5829  std::vector<char> memory_pool = std::vector<char>(10u * 1024u); // 10KB
5830  size_t num_allocs = 0, alloc_size = 0;
5831  size_t num_deallocs = 0, dealloc_size = 0;
5832 
5833  ryml::Callbacks callbacks() const
5834  {
5835  // Above we used static functions to bridge to our methods.
5836  // To show a different approach, we employ lambdas here.
5837  // Note that there can be no captures in the lambdas
5838  // because these are C-style function pointers.
5839  ryml::Callbacks cb;
5840  cb.m_user_data = (void*) this;
5841  cb.m_allocate = [](size_t len, void *, void *data){ return ((PerTreeMemoryExample*) data)->allocate(len); };
5842  cb.m_free = [](void *mem, size_t len, void *data){ return ((PerTreeMemoryExample*) data)->free(mem, len); };
5843  return cb;
5844  }
5845 
5846  void *allocate(size_t len)
5847  {
5848  void *ptr = &memory_pool[alloc_size];
5849  alloc_size += len;
5850  ++num_allocs;
5851  if(C4_UNLIKELY(alloc_size > memory_pool.size()))
5852  {
5853  std::cerr << "out of memory! requested=" << alloc_size << " vs " << memory_pool.size() << " available" << std::endl;
5854  std::abort();
5855  }
5856  return ptr;
5857  }
5858 
5859  void free(void *mem, size_t len)
5860  {
5861  CHECK((char*)mem >= &memory_pool.front() && (char*)mem < &memory_pool.back());
5862  CHECK((char*)mem+len >= &memory_pool.front() && (char*)mem+len <= &memory_pool.back());
5863  dealloc_size += len;
5864  ++num_deallocs;
5865  // no need to free here
5866  }
5867 
5868  // checking
5869  ~PerTreeMemoryExample()
5870  {
5871  check_and_reset();
5872  }
5873  void check_and_reset()
5874  {
5875  std::cout << "size: alloc=" << alloc_size << " dealloc=" << dealloc_size << std::endl;
5876  std::cout << "count: #allocs=" << num_allocs << " #deallocs=" << num_deallocs << std::endl;
5877  CHECK(num_allocs == num_deallocs);
5878  CHECK(alloc_size >= dealloc_size); // failure here means a double free
5879  CHECK(alloc_size == dealloc_size); // failure here means a leak
5880  num_allocs = 0;
5881  num_deallocs = 0;
5882  alloc_size = 0;
5883  dealloc_size = 0;
5884  }
5885 };
5886 /** @} */
5887 
5888 void sample_per_tree_allocator()
5889 {
5890  PerTreeMemoryExample mrp;
5891  PerTreeMemoryExample mr1;
5892  PerTreeMemoryExample mr2;
5893 
5894  // the trees will use the memory in the resources above,
5895  // with each tree using a separate resource
5896  {
5897  // Watchout: ensure that the lifetime of the callbacks target
5898  // exceeds the lifetime of the tree.
5899  ryml::EventHandlerTree evt_handler(mrp.callbacks());
5900  ryml::Parser parser(&evt_handler);
5901  ryml::Tree tree1(mr1.callbacks());
5902  ryml::Tree tree2(mr2.callbacks());
5903 
5904  ryml::csubstr yml1 = "{a: b}";
5905  ryml::csubstr yml2 = "{c: d, e: f, g: [h, i, 0, 1, 2, 3]}";
5906 
5907  parse_in_arena(&parser, "file1.yml", yml1, &tree1);
5908  parse_in_arena(&parser, "file2.yml", yml2, &tree2);
5909  }
5910 
5911  CHECK(mrp.num_allocs == 0); // YAML depth not large enough to warrant a parser allocation
5912  CHECK(mr1.alloc_size <= mr2.alloc_size); // because yml2 has more nodes
5913 }
5914 
5915 
5916 //-----------------------------------------------------------------------------
5917 
5918 
5919 /** shows how to work around the static initialization order fiasco
5920  * when using a static-duration ryml tree
5921  * @see https://en.cppreference.com/w/cpp/language/siof */
5922 void sample_static_trees()
5923 {
5924  // Static trees may incur a static initialization order
5925  // problem. This happens because a default-constructed tree will
5926  // obtain the callbacks from the current global setting, which may
5927  // not have been initialized due to undefined static
5928  // initialization order:
5929  //
5930  // ERROR! depends on ryml::get_callbacks() which may not have been initialized.
5931  //static ryml::Tree tree;
5932  //
5933  // To work around the issue, declare static callbacks
5934  // to explicitly initialize the static tree:
5935  static ryml::Callbacks callbacks = {}; // use default callback members
5936  static ryml::Tree tree(callbacks); // OK
5937  // now you can use the tree as normal:
5938  ryml::parse_in_arena(R"(doe: "a deer, a female deer")", &tree);
5939  CHECK(tree["doe"].val() == "a deer, a female deer");
5940 }
5941 
5942 
5943 //-----------------------------------------------------------------------------
5944 //-----------------------------------------------------------------------------
5945 //-----------------------------------------------------------------------------
5946 /** demonstrates how to obtain the (zero-based) location of a node
5947  * from a recently parsed tree */
5948 void sample_location_tracking()
5949 {
5950  // NOTE: locations are zero-based. If you intend to show the
5951  // location to a human user, you may want to pre-increment the line
5952  // and column by 1.
5953  ryml::csubstr yaml = R"({
5954 aa: contents,
5955 foo: [one, [two, three]]
5956 })";
5957  // A parser is needed to track locations, and it has to be
5958  // explicitly set to do it. Location tracking is disabled by
5959  // default.
5960  ryml::ParserOptions opts = {};
5961  opts.locations(true); // enable locations, default is false
5962  ryml::EventHandlerTree evt_handler = {};
5963  ryml::Parser parser(&evt_handler, opts);
5964  CHECK(parser.options().locations());
5965  // When locations are enabled, the first task while parsing will
5966  // consist of building and caching (in the parser) a
5967  // source-to-node lookup structure to accelerate location lookups.
5968  //
5969  // The cost of building the location accelerator is linear in the