rapidyaml  0.11.1
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.1
GIT_SHALLOW FALSE # ensure submodules are checked out
)
FetchContent_MakeAvailable(ryml)
add_executable(ryml-quickstart ${ryml_SOURCE_DIR}/samples/quickstart.cpp)
target_link_libraries(ryml-quickstart ryml::ryml)
add_custom_target(run ryml-quickstart
COMMAND $<TARGET_FILE:ryml-quickstart>
DEPENDS ryml-quickstart
COMMENT "running: $<TARGET_FILE:ryml-quickstart>")

Now run the following commands in the same folder:

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

Function Documentation

◆ sample_lightning_overview()

void sample_lightning_overview ( )

a lightning tour over most features see sample_quick_overview

Definition at line 326 of file quickstart.cpp.

327 {
328  // Parse YAML code in place, potentially mutating the buffer:
329  char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}";
330  ryml::Tree tree = ryml::parse_in_place(yml_buf);
331 
332  // read from the tree:
333  ryml::NodeRef bar = tree["bar"];
334  CHECK(bar[0].val() == "2");
335  CHECK(bar[1].val() == "3");
336  CHECK(bar[0].val().str == yml_buf + 15); // points at the source buffer
337  CHECK(bar[1].val().str == yml_buf + 18);
338 
339  // deserializing:
340  int bar0 = 0, bar1 = 0;
341  bar[0] >> bar0;
342  bar[1] >> bar1;
343  CHECK(bar0 == 2);
344  CHECK(bar1 == 3);
345 
346  // serializing:
347  bar[0] << 10; // creates a string in the tree's arena
348  bar[1] << 11;
349  CHECK(bar[0].val() == "10");
350  CHECK(bar[1].val() == "11");
351 
352  // add nodes
353  bar.append_child() << 12; // see also operator= (explanation below)
354  CHECK(bar[2].val() == "12");
355 
356  // emit tree
357  // to std::string
358  CHECK(ryml::emitrs_yaml<std::string>(tree) == R"({foo: 1,bar: [10,11,12],john: doe})");
359  std::cout << tree; // emit to ostream
360  ryml::emit_yaml(tree, stdout); // emit to FILE*
361 
362  // emit node
363  ryml::ConstNodeRef foo = tree["foo"];
364  // to std::string
365  CHECK(ryml::emitrs_yaml<std::string>(foo) == "foo: 1\n");
366  std::cout << foo; // emit node to ostream
367  ryml::emit_yaml(foo, stdout); // emit node to FILE*
368 }
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:294

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

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

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

1806 {
1807  const char filename[] = "ryml_example.yml";
1808 
1809  // because this is a minimal sample, it assumes nothing on the
1810  // environment/OS (other than that it can read/write files). So we
1811  // create the file on the fly:
1812  file_put_contents(filename, ryml::csubstr(R"(
1813 foo: 1
1814 bar:
1815 - 2
1816 - 3
1817 )"));
1818 
1819  // now we can load it into a std::string (for example):
1820  {
1821  std::string contents = file_get_contents<std::string>(filename);
1822  ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(contents)); // immutable (csubstr) overload
1823  CHECK(tree["foo"].val() == "1");
1824  CHECK(tree["bar"][0].val() == "2");
1825  CHECK(tree["bar"][1].val() == "3");
1826  }
1827 
1828  // or we can use a vector<char> instead:
1829  {
1830  std::vector<char> contents = file_get_contents<std::vector<char>>(filename);
1831  ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(contents)); // mutable (csubstr) overload
1832  CHECK(tree["foo"].val() == "1");
1833  CHECK(tree["bar"][0].val() == "2");
1834  CHECK(tree["bar"][1].val() == "3");
1835  }
1836 
1837  // generally, any contiguous char container can be used with ryml,
1838  // provided that the ryml::substr/ryml::csubstr view can be
1839  // created out of it.
1840  //
1841  // ryml provides the overloads above for these two containers, but
1842  // if you are using another container it should be very easy (only
1843  // requires pointer and length).
1844 }
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 1851 of file quickstart.cpp.

1852 {
1853  // Like the name suggests, parse_in_place() directly mutates the
1854  // source buffer in place
1855  char src[] = "{foo: 1, bar: [2, 3]}"; // ryml can parse in situ
1856  ryml::substr srcview = src; // a mutable view to the source buffer
1857  ryml::Tree tree = ryml::parse_in_place(srcview); // you can also reuse the tree and/or parser
1858  ryml::ConstNodeRef root = tree.crootref(); // get a constant reference to the root
1859 
1860  CHECK(root.is_map());
1861  CHECK(root["foo"].is_keyval());
1862  CHECK(root["foo"].key() == "foo");
1863  CHECK(root["foo"].val() == "1");
1864  CHECK(root["bar"].is_seq());
1865  CHECK(root["bar"].has_key());
1866  CHECK(root["bar"].key() == "bar");
1867  CHECK(root["bar"][0].val() == "2");
1868  CHECK(root["bar"][1].val() == "3");
1869 
1870  // deserializing:
1871  int foo = 0, bar0 = 0, bar1 = 0;
1872  root["foo"] >> foo;
1873  root["bar"][0] >> bar0;
1874  root["bar"][1] >> bar1;
1875  CHECK(foo == 1);
1876  CHECK(bar0 == 2);
1877  CHECK(bar1 == 3);
1878 
1879  // after parsing, the tree holds views to the source buffer:
1880  CHECK(root["foo"].val().data() == src + strlen("{foo: "));
1881  CHECK(root["foo"].val().begin() == src + strlen("{foo: "));
1882  CHECK(root["foo"].val().end() == src + strlen("{foo: 1"));
1883  CHECK(root["foo"].val().is_sub(srcview)); // equivalent to the previous three assertions
1884  CHECK(root["bar"][0].val().data() == src + strlen("{foo: 1, bar: ["));
1885  CHECK(root["bar"][0].val().begin() == src + strlen("{foo: 1, bar: ["));
1886  CHECK(root["bar"][0].val().end() == src + strlen("{foo: 1, bar: [2"));
1887  CHECK(root["bar"][0].val().is_sub(srcview)); // equivalent to the previous three assertions
1888  CHECK(root["bar"][1].val().data() == src + strlen("{foo: 1, bar: [2, "));
1889  CHECK(root["bar"][1].val().begin() == src + strlen("{foo: 1, bar: [2, "));
1890  CHECK(root["bar"][1].val().end() == src + strlen("{foo: 1, bar: [2, 3"));
1891  CHECK(root["bar"][1].val().is_sub(srcview)); // equivalent to the previous three assertions
1892 
1893  // NOTE. parse_in_place() cannot accept ryml::csubstr
1894  // so this will cause a /compile/ error:
1895  ryml::csubstr csrcview = srcview; // ok, can assign from mutable to immutable
1896  //tree = ryml::parse_in_place(csrcview); // compile error, cannot mutate an immutable view
1897  (void)csrcview;
1898 }
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 1905 of file quickstart.cpp.

1906 {
1907  // to parse read-only memory, ryml will copy first to the tree's
1908  // arena, and then parse the copied buffer:
1909  ryml::Tree tree = ryml::parse_in_arena("{foo: 1, bar: [2, 3]}");
1910  ryml::ConstNodeRef root = tree.crootref(); // get a const reference to the root
1911 
1912  CHECK(root.is_map());
1913  CHECK(root["foo"].is_keyval());
1914  CHECK(root["foo"].key() == "foo");
1915  CHECK(root["foo"].val() == "1");
1916  CHECK(root["bar"].is_seq());
1917  CHECK(root["bar"].has_key());
1918  CHECK(root["bar"].key() == "bar");
1919  CHECK(root["bar"][0].val() == "2");
1920  CHECK(root["bar"][1].val() == "3");
1921 
1922  // deserializing:
1923  int foo = 0, bar0 = 0, bar1 = 0;
1924  root["foo"] >> foo;
1925  root["bar"][0] >> bar0;
1926  root["bar"][1] >> bar1;
1927  CHECK(foo == 1);
1928  CHECK(bar0 == 2);
1929  CHECK(bar1 == 3);
1930 
1931  // NOTE. parse_in_arena() cannot accept ryml::substr. Overloads
1932  // receiving substr buffers are declared, but intentionally left
1933  // undefined, so this will cause a /linker/ error
1934  char src[] = "{foo: is it really true}";
1935  ryml::substr srcview = src;
1936  //tree = ryml::parse_in_place(srcview); // linker error, overload intentionally undefined
1937 
1938  // If you really intend to parse a mutable buffer in the arena,
1939  // then simply convert it to immutable prior to calling
1940  // parse_in_arena():
1941  ryml::csubstr csrcview = srcview; // assigning from src also works
1942  tree = ryml::parse_in_arena(csrcview); // OK! csrcview is immutable
1943  CHECK(tree["foo"].val() == "is it really true");
1944 }

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

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

2104 {
2105  ryml::EventHandlerTree evt_handler = {};
2106  ryml::Parser parser(&evt_handler);
2107 
2108  // it is also advised to reserve the parser depth
2109  // to the expected depth of the data tree:
2110  parser.reserve_stack(10); // uses small storage optimization
2111  // defaulting to 16 depth, so this
2112  // instruction is a no-op, and the stack
2113  // will located in the parser object.
2114  parser.reserve_stack(20); // But this will cause an allocation
2115  // because it is above 16.
2116 
2117  ryml::Tree champagnes = parse_in_arena(&parser, "champagnes.yml", "[Dom Perignon, Gosset Grande Reserve, Jacquesson 742]");
2118  CHECK(ryml::emitrs_yaml<std::string>(champagnes) == "[Dom Perignon,Gosset Grande Reserve,Jacquesson 742]");
2119 
2120  ryml::Tree beers = parse_in_arena(&parser, "beers.yml", "[Rochefort 10, Busch, Leffe Rituel, Kasteel Donker]");
2121  CHECK(ryml::emitrs_yaml<std::string>(beers) == "[Rochefort 10,Busch,Leffe Rituel,Kasteel Donker]");
2122 }

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

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

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

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

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

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

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

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

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

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

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

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

3579 {
3580  ryml::Tree t;
3581 
3582  auto r = t.rootref();
3583  r |= ryml::MAP;
3584 
3585  vec2<int> v2in{10, 11};
3586  vec2<int> v2out{1, 2};
3587  r["v2"] << v2in; // serializes to the tree's arena, and then sets the keyval
3588  r["v2"] >> v2out;
3589  CHECK(v2in.x == v2out.x);
3590  CHECK(v2in.y == v2out.y);
3591  vec3<int> v3in{100, 101, 102};
3592  vec3<int> v3out{1, 2, 3};
3593  r["v3"] << v3in; // serializes to the tree's arena, and then sets the keyval
3594  r["v3"] >> v3out;
3595  CHECK(v3in.x == v3out.x);
3596  CHECK(v3in.y == v3out.y);
3597  CHECK(v3in.z == v3out.z);
3598  vec4<int> v4in{1000, 1001, 1002, 1003};
3599  vec4<int> v4out{1, 2, 3, 4};
3600  r["v4"] << v4in; // serializes to the tree's arena, and then sets the keyval
3601  r["v4"] >> v4out;
3602  CHECK(v4in.x == v4out.x);
3603  CHECK(v4in.y == v4out.y);
3604  CHECK(v4in.z == v4out.z);
3605  CHECK(v4in.w == v4out.w);
3606  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(v2: '(10,11)'
3607 v3: '(100,101,102)'
3608 v4: '(1000,1001,1002,1003)'
3609 )");
3610 
3611  // note that only the used functions are needed:
3612  // - if a type is only parsed, then only from_chars() is needed
3613  // - if a type is only emitted, then only to_chars() is needed
3614  emit_only_vec2<int> eov2in{20, 21}; // only has to_chars()
3615  parse_only_vec2<int> pov2out{1, 2}; // only has from_chars()
3616  r["v2"] << eov2in; // serializes to the tree's arena, and then sets the keyval
3617  r["v2"] >> pov2out;
3618  CHECK(eov2in.x == pov2out.x);
3619  CHECK(eov2in.y == pov2out.y);
3620  emit_only_vec3<int> eov3in{30, 31, 32}; // only has to_chars()
3621  parse_only_vec3<int> pov3out{1, 2, 3}; // only has from_chars()
3622  r["v3"] << eov3in; // serializes to the tree's arena, and then sets the keyval
3623  r["v3"] >> pov3out;
3624  CHECK(eov3in.x == pov3out.x);
3625  CHECK(eov3in.y == pov3out.y);
3626  CHECK(eov3in.z == pov3out.z);
3627  emit_only_vec4<int> eov4in{40, 41, 42, 43}; // only has to_chars()
3628  parse_only_vec4<int> pov4out{1, 2, 3, 4}; // only has from_chars()
3629  r["v4"] << eov4in; // serializes to the tree's arena, and then sets the keyval
3630  r["v4"] >> pov4out;
3631  CHECK(eov4in.x == pov4out.x);
3632  CHECK(eov4in.y == pov4out.y);
3633  CHECK(eov4in.z == pov4out.z);
3634  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(v2: '(20,21)'
3635 v3: '(30,31,32)'
3636 v4: '(40,41,42,43)'
3637 )");
3638 }
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 3774 of file quickstart.cpp.

3775 {
3776  my_type mt_in{
3777  {20, 21},
3778  {30, 31, 32},
3779  {40, 41, 42, 43},
3780  {{101, 102, 103, 104, 105, 106, 107}},
3781  {{{1001, 2001}, {1002, 2002}, {1003, 2003}}},
3782  };
3783  my_type mt_out;
3784 
3785  ryml::Tree t;
3786  t.rootref() << mt_in; // read from this
3787  t.crootref() >> mt_out; // assign here
3788  CHECK(mt_out.v2.x == mt_in.v2.x);
3789  CHECK(mt_out.v2.y == mt_in.v2.y);
3790  CHECK(mt_out.v3.x == mt_in.v3.x);
3791  CHECK(mt_out.v3.y == mt_in.v3.y);
3792  CHECK(mt_out.v3.z == mt_in.v3.z);
3793  CHECK(mt_out.v4.x == mt_in.v4.x);
3794  CHECK(mt_out.v4.y == mt_in.v4.y);
3795  CHECK(mt_out.v4.z == mt_in.v4.z);
3796  CHECK(mt_out.v4.w == mt_in.v4.w);
3797  CHECK(mt_in.seq.seq_member.size() > 0);
3798  CHECK(mt_out.seq.seq_member.size() == mt_in.seq.seq_member.size());
3799  for(size_t i = 0; i < mt_in.seq.seq_member.size(); ++i)
3800  {
3801  CHECK(mt_out.seq.seq_member[i] == mt_in.seq.seq_member[i]);
3802  }
3803  CHECK(mt_in.map.map_member.size() > 0);
3804  CHECK(mt_out.map.map_member.size() == mt_in.map.map_member.size());
3805  for(auto const& kv : mt_in.map.map_member)
3806  {
3807  CHECK(mt_out.map.map_member.find(kv.first) != mt_out.map.map_member.end());
3808  CHECK(mt_out.map.map_member[kv.first] == kv.second);
3809  }
3810  CHECK(ryml::emitrs_yaml<std::string>(t) == R"(v2: '(20,21)'
3811 v3: '(30,31,32)'
3812 v4: '(40,41,42,43)'
3813 seq:
3814  - 101
3815  - 102
3816  - 103
3817  - 104
3818  - 105
3819  - 106
3820  - 107
3821 map:
3822  1001: 2001
3823  1002: 2002
3824  1003: 2003
3825 )");
3826 }
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 3835 of file quickstart.cpp.

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

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

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

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

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

4154 {
4155  ryml::csubstr ymlb = R"(- a
4156 - b
4157 - x0: 1
4158  x1: 2
4159 - champagne: Dom Perignon
4160  coffee: Arabica
4161  more:
4162  vinho verde: Soalheiro
4163  vinho tinto: Redoma 2017
4164  beer:
4165  - Rochefort 10
4166  - Busch
4167  - Leffe Rituel
4168 - foo
4169 - bar
4170 - baz
4171 - bat
4172 )";
4173  const ryml::Tree tree = ryml::parse_in_arena(ymlb);
4174 
4175  std::string s;
4176 
4177  // emit a full tree
4178  {
4179  std::stringstream ss;
4180  ss << tree; // works with any stream having .operator<<() and .write()
4181  s = ss.str();
4182  CHECK(ryml::to_csubstr(s) == ymlb);
4183  }
4184 
4185  // emit a full tree as json
4186  {
4187  std::stringstream ss;
4188  ss << ryml::as_json(tree); // works with any stream having .operator<<() and .write()
4189  s = ss.str();
4190  CHECK(ryml::to_csubstr(s) == R"([
4191  "a",
4192  "b",
4193  {
4194  "x0": 1,
4195  "x1": 2
4196  },
4197  {
4198  "champagne": "Dom Perignon",
4199  "coffee": "Arabica",
4200  "more": {
4201  "vinho verde": "Soalheiro",
4202  "vinho tinto": "Redoma 2017"
4203  },
4204  "beer": [
4205  "Rochefort 10",
4206  "Busch",
4207  "Leffe Rituel"
4208  ]
4209  },
4210  "foo",
4211  "bar",
4212  "baz",
4213  "bat"
4214 ]
4215 )");
4216  }
4217 
4218  // emit a nested node
4219  {
4220  std::stringstream ss;
4221  ss << tree[3][2]; // works with any stream having .operator<<() and .write()
4222  s = ss.str();
4223  CHECK(ryml::to_csubstr(s) == R"(more:
4224  vinho verde: Soalheiro
4225  vinho tinto: Redoma 2017
4226 )");
4227  }
4228 
4229  // emit a nested node as json
4230  {
4231  std::stringstream ss;
4232  ss << ryml::as_json(tree[3][2]); // works with any stream having .operator<<() and .write()
4233  s = ss.str();
4234  CHECK(ryml::to_csubstr(s) == R"("more": {
4235  "vinho verde": "Soalheiro",
4236  "vinho tinto": "Redoma 2017"
4237 }
4238 )");
4239  }
4240 }
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 4246 of file quickstart.cpp.

4247 {
4248  ryml::csubstr yml = R"(- a
4249 - b
4250 - x0: 1
4251  x1: 2
4252 - champagne: Dom Perignon
4253  coffee: Arabica
4254  more:
4255  vinho verde: Soalheiro
4256  vinho tinto: Redoma 2017
4257  beer:
4258  - Rochefort 10
4259  - Busch
4260  - Leffe Rituel
4261 - foo
4262 - bar
4263 - baz
4264 - bat
4265 )";
4266  const ryml::Tree tree = ryml::parse_in_arena(yml);
4267  // this is emitting to stdout, but of course you can pass in any
4268  // FILE* obtained from fopen()
4269  size_t len = ryml::emit_yaml(tree, tree.root_id(), stdout);
4270  // the return value is the number of characters that were written
4271  // to the file
4272  CHECK(len == yml.len);
4273 }

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

4280 {
4281  const ryml::Tree tree = ryml::parse_in_arena(R"(- a
4282 - b
4283 - x0: 1
4284  x1: 2
4285 - champagne: Dom Perignon
4286  coffee: Arabica
4287  more:
4288  vinho verde: Soalheiro
4289  vinho tinto: Redoma 2017
4290  beer:
4291  - Rochefort 10
4292  - Busch
4293  - Leffe Rituel
4294  - - and so
4295  - many other
4296  - wonderful beers
4297 - more
4298 - seq
4299 - members
4300 - here
4301 )");
4302  CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"]) == R"(beer:
4303  - Rochefort 10
4304  - Busch
4305  - Leffe Rituel
4306  - - and so
4307  - many other
4308  - wonderful beers
4309 )");
4310  CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][0]) == "Rochefort 10");
4311  CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][3]) == R"(- and so
4312 - many other
4313 - wonderful beers
4314 )");
4315 }

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

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

4502  :
4503  block key: block val
4504 block seq:
4505  - block val 1
4506  - block val 2
4507  - quoted
4508 'flow map, singleline':
4509  flow key: flow val
4510 'flow seq, singleline':
4511  - flow val
4512  - flow val
4513 'flow map, multiline':
4514  flow key: flow val
4515 'flow seq, multiline':
4516  - flow val
4517  - flow val
4518 )");
4519  // change all keys to single-quoted:
4520  tree.rootref().set_style_conditionally(/*type_mask*/ryml::KEY,
4521  /*remflags*/ryml::KEY_STYLE,
4522  /*addflags*/ryml::KEY_SQUO,
4523  /*recurse*/true);
4524  // change all vals to double-quoted
4525  tree.rootref().set_style_conditionally(/*type_mask*/ryml::VAL,
4526  /*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 4533 of file quickstart.cpp.

4541  :
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': ["flow val","flow val"]
4547 'flow map, multiline':
4548  'flow key': "flow val"
4549 'flow seq, multiline': ["flow val","flow val"]
4550 )");
4551  // you can also set a conditional style in a single node (or its branch if recurse is true):
4552  tree["flow seq, singleline"].set_style_conditionally(/*type_mask*/ryml::SEQ,
4553  /*remflags*/ryml::CONTAINER_STYLE,
4554  /*addflags*/ryml::BLOCK,
4555  /*recurse*/false);
4556  CHECK(tostr(tree) ==
4557  R"('block map':
4558  'block key': "block val"
4559 'block seq': ["block val 1","block val 2","quoted"]
4560 'flow map, singleline':
4561  'flow key': "flow val"
4562 'flow seq, singleline':
4563  - "flow val"
4564  - "flow val"
4565 'flow map, multiline':
4566  'flow key': "flow val"
4567 'flow seq, multiline': ["flow val","flow val"]
4568 )");
4569  // see also:
4570  // - ryml::scalar_style_choose()
4571  // - ryml::scalar_style_json_choose()
4572  // - ryml::scalar_style_query_squo()
4573  // - ryml::scalar_style_query_plain()
4574 }
4575 
4576 
4577 //-----------------------------------------------------------------------------
4578 
4579 /** [experimental] control the indentation of emitted FLOW_ML containers */
4580 void sample_style_flow_ml_indent()
4581 {
4582  // we will be using this helper throughout this function
4583  auto tostr = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) {
4584  return ryml::emitrs_yaml<std::string>(n, opts);
4585  };
4586  ryml::csubstr yaml = "{map: {seq: [0, 1, 2, 3, [40, 41]]}}";
4587  ryml::Tree tree = ryml::parse_in_arena(yaml);
4588  ryml::EmitOptions defaults = {};
4589  ryml::EmitOptions noindent = ryml::EmitOptions{}.indent_flow_ml(false);
4590  CHECK(tostr(tree, defaults) == "{map: {seq: [0,1,2,3,[40,41]]}}");
4591  // 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 4600 of file quickstart.cpp.

4600  : [
4601  0,
4602  1,
4603  2,
4604  3,
4605  [
4606  40,
4607  41
4608  ]
4609  ]
4610  }
4611 }
4612 )");
4613  // if we use the noindent options, then each value is put at the
4614  // beginning of the line
4615  CHECK(tostr(tree, noindent) ==
4616  R"({
4617 map: {
4618 seq: [
4619 0,
4620 1,
4621 2,
4622 3,
4623 [
4624 40,
4625 41
4626 ]
4627 ]
4628 }
4629 }
4630 )");
4631  // Note that the noindent option will safely respect any prior
4632  // indent level from enclosing block containers! For example:
4633  tree.rootref().set_container_style(ryml::BLOCK);
4634  CHECK(tostr(tree, noindent) == // notice it is indented at the map level
4635  R"(map: {
4636  seq: [
4637  0,
4638  1,
4639  2,
4640  3,
4641  [
4642  40,
4643  41
4644  ]
4645  ]
4646  }
4647 )");
4648  // Let's set it one BLOCK level further:
4649  tree["map"].set_container_style(ryml::BLOCK);
4650  CHECK(tostr(tree, noindent) == // notice it is indented one more level
4651  R"(map:
4652  seq: [
4653  0,
4654  1,
4655  2,
4656  3,
4657  [
4658  40,
4659  41
4660  ]
4661  ]
4662 )");
4663 }

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

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

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

◆ sample_tags()

void sample_tags ( )

Definition at line 4846 of file quickstart.cpp.

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

◆ sample_tag_directives()

void sample_tag_directives ( )

Definition at line 4986 of file quickstart.cpp.

4986  : {and: more}
4987 copy: {foo: bar,baz: bat,and: more}
4988 )");
4989  }
4990 }
4991 
4992 
4993 //-----------------------------------------------------------------------------
4994 
4995 void sample_tags()
4996 {
4997  const std::string yaml = R"(--- !!map
4998 a: 0
4999 b: 1
5000 --- !map
5001 a: b
5002 --- !!seq
5003 - a
5004 - b
5005 --- !!str a b
5006 --- !!str 'a: b'
5007 ---
5008 !!str a: b
5009 --- !!set
5010 ? a
5011 ? b
5012 --- !!set
5013 a:
5014 --- !!seq
5015 - !!int 0

◆ sample_docs()

void sample_docs ( )

Definition at line 5020 of file quickstart.cpp.

5022  : root.children())
5023  CHECK(doc.is_doc());
5024  // tags are kept verbatim from the source:
5025  CHECK(root[0].has_val_tag());
5026  CHECK(root[0].val_tag() == "!!map"); // valid only if the node has a val tag
5027  CHECK(root[1].val_tag() == "!map");
5028  CHECK(root[2].val_tag() == "!!seq");
5029  CHECK(root[3].val_tag() == "!!str");
5030  CHECK(root[4].val_tag() == "!!str");
5031  CHECK(root[5]["a"].has_key_tag());
5032  CHECK(root[5]["a"].key_tag() == "!!str"); // valid only if the node has a key tag
5033  CHECK(root[6].val_tag() == "!!set");
5034  CHECK(root[7].val_tag() == "!!set");
5035  CHECK(root[8].val_tag() == "!!seq");
5036  CHECK(root[8][0].val_tag() == "!!int");
5037  CHECK(root[8][1].val_tag() == "!!str");
5038  // ryml also provides a complete toolbox to deal with tags.
5039  // there is an enumeration for the standard YAML tags:
5040  CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
5041  CHECK(ryml::to_tag("!!map") == ryml::TAG_MAP);
5042  CHECK(ryml::to_tag("!!seq") == ryml::TAG_SEQ);
5043  CHECK(ryml::to_tag("!!str") == ryml::TAG_STR);
5044  CHECK(ryml::to_tag("!!int") == ryml::TAG_INT);
5045  CHECK(ryml::to_tag("!!set") == ryml::TAG_SET);
5046  // given a tag enum, you can fetch the short tag string:
5048  CHECK(ryml::from_tag(ryml::TAG_MAP) == "!!map");
5049  CHECK(ryml::from_tag(ryml::TAG_SEQ) == "!!seq");
5050  CHECK(ryml::from_tag(ryml::TAG_STR) == "!!str");
5051  CHECK(ryml::from_tag(ryml::TAG_INT) == "!!int");
5052  CHECK(ryml::from_tag(ryml::TAG_SET) == "!!set");
5053  // you can also fetch the long tag string:
5055  CHECK(ryml::from_tag_long(ryml::TAG_MAP) == "<tag:yaml.org,2002:map>");
5056  CHECK(ryml::from_tag_long(ryml::TAG_SEQ) == "<tag:yaml.org,2002:seq>");
5057  CHECK(ryml::from_tag_long(ryml::TAG_STR) == "<tag:yaml.org,2002:str>");
5058  CHECK(ryml::from_tag_long(ryml::TAG_INT) == "<tag:yaml.org,2002:int>");
5059  CHECK(ryml::from_tag_long(ryml::TAG_SET) == "<tag:yaml.org,2002:set>");
5060  // and likewise:
5061  CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
5062  CHECK(ryml::to_tag("<tag:yaml.org,2002:map>") == ryml::TAG_MAP);
5063  CHECK(ryml::to_tag("<tag:yaml.org,2002:seq>") == ryml::TAG_SEQ);
5064  CHECK(ryml::to_tag("<tag:yaml.org,2002:str>") == ryml::TAG_STR);
5065  CHECK(ryml::to_tag("<tag:yaml.org,2002:int>") == ryml::TAG_INT);
5066  CHECK(ryml::to_tag("<tag:yaml.org,2002:set>") == ryml::TAG_SET);
5067  // to normalize a tag as much as possible, use normalize_tag():
5068  CHECK(ryml::normalize_tag("!!map") == "!!map");
5069  CHECK(ryml::normalize_tag("!<tag:yaml.org,2002:map>") == "!!map");
5070  CHECK(ryml::normalize_tag("<tag:yaml.org,2002:map>") == "!!map");
5071  CHECK(ryml::normalize_tag("tag:yaml.org,2002:map") == "!!map");
5072  CHECK(ryml::normalize_tag("!<!!map>") == "<!!map>");
5073  CHECK(ryml::normalize_tag("!map") == "!map");
5074  CHECK(ryml::normalize_tag("!my!foo") == "!my!foo");
5075  // and also for the long form:
5076  CHECK(ryml::normalize_tag_long("!!map") == "<tag:yaml.org,2002:map>");
5077  CHECK(ryml::normalize_tag_long("!<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
5078  CHECK(ryml::normalize_tag_long("<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
5079  CHECK(ryml::normalize_tag_long("tag:yaml.org,2002:map") == "<tag:yaml.org,2002:map>");
5080  CHECK(ryml::normalize_tag_long("!<!!map>") == "<!!map>");
5081  CHECK(ryml::normalize_tag_long("!map") == "!map");
5082  // The tree provides the following methods applying to every node
5083  // with a key and/or val tag:
5084  ryml::Tree normalized_tree = tree;
5085  normalized_tree.normalize_tags(); // normalize all tags in short form
5086  CHECK(ryml::emitrs_yaml<std::string>(normalized_tree) == R"(--- !!map
5087 a: 0
5088 b: 1
5089 --- !map
5090 a: b
5091 --- !!seq
5092 - a
5093 - b
5094 --- !!str a b
5095 --- !!str 'a: b'
5096 ---
5097 !!str a: b
5098 --- !!set
5099 a:
5100 b:
5101 --- !!set
5102 a:
5103 --- !!seq
5104 - !!int 0
5105 - !!str 1
5106 )");
5107  ryml::Tree normalized_tree_long = tree;
5108  normalized_tree_long.normalize_tags_long(); // normalize all tags in short form
5109  CHECK(ryml::emitrs_yaml<std::string>(normalized_tree_long) == R"(--- !<tag:yaml.org,2002:map>
5110 a: 0
5111 b: 1
5112 --- !map
5113 a: b
5114 --- !<tag:yaml.org,2002:seq>
5115 - a
5116 - b
5117 --- !<tag:yaml.org,2002:str> a b
5118 --- !<tag:yaml.org,2002:str> 'a: b'
5119 ---
5120 !<tag:yaml.org,2002:str> a: b
5121 --- !<tag:yaml.org,2002:set>
5122 a:
5123 b:
5124 --- !<tag:yaml.org,2002:set>
5125 a:
5126 --- !<tag:yaml.org,2002:seq>
5127 - !<tag:yaml.org,2002:int> 0
5128 - !<tag:yaml.org,2002:str> 1
5129 )");
5130 }
5131 
5132 
5133 //-----------------------------------------------------------------------------
5134 
5135 void sample_tag_directives()
5136 {
5137  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 5160 of file quickstart.cpp.

5170 {
5171  std::string yml = R"(---
5172 a: 0
5173 b: 1
5174 ---
5175 c: 2
5176 d: 3

◆ sample_error_basic()

void sample_error_basic ( )

Definition at line 5181 of file quickstart.cpp.

5187  {
5188  // using the node API
5189  const ryml::ConstNodeRef stream = tree.rootref();
5190  CHECK(stream.is_root());
5191  CHECK(stream.is_stream());
5192  CHECK(!stream.is_doc());
5193  CHECK(stream.num_children() == 3);
5194  for(const ryml::ConstNodeRef doc : stream.children())
5195  CHECK(doc.is_doc());
5196  CHECK(tree.docref(0).id() == stream.child(0).id());
5197  CHECK(tree.docref(1).id() == stream.child(1).id());
5198  CHECK(tree.docref(2).id() == stream.child(2).id());
5199  // equivalent: using the lower level index API
5200  const ryml::id_type stream_id = tree.root_id();
5201  CHECK(tree.is_root(stream_id));
5202  CHECK(tree.is_stream(stream_id));
5203  CHECK(!tree.is_doc(stream_id));
5204  CHECK(tree.num_children(stream_id) == 3);
5205  for(ryml::id_type doc_id = tree.first_child(stream_id); doc_id != ryml::NONE; doc_id = tree.next_sibling(stream_id))
5206  CHECK(tree.is_doc(doc_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 5208 of file quickstart.cpp.

5259  {
5260  const std::string expected_json[] = {
5261  "{\n \"a\": 0,\n \"b\": 1\n}\n",
5262  "{\n \"c\": 2,\n \"d\": 3\n}\n",
5263  "[\n 4,\n 5,\n 6,\n 7\n]\n",
5264  };
5265  // using the node API
5266  {
5267  ryml::id_type count = 0;
5268  const ryml::ConstNodeRef stream = tree.rootref();
5269  CHECK(stream.num_children() == (ryml::id_type)C4_COUNTOF(expected_json));
5270  for(ryml::ConstNodeRef doc : stream.children())
5271  {
5272  CHECK(ryml::emitrs_json<std::string>(doc) == expected_json[count++]);
5273  }
5274  }
5275  // equivalent: using the index API
5276  {
5277  ryml::id_type count = 0;
5278  const ryml::id_type stream_id = tree.root_id();
5279  CHECK(tree.num_children(stream_id) == (ryml::id_type)C4_COUNTOF(expected_json));
5280  for(ryml::id_type doc_id = tree.first_child(stream_id); doc_id != ryml::NONE; doc_id = tree.next_sibling(doc_id))
5281  {
5282  CHECK(ryml::emitrs_json<std::string>(tree, doc_id) == expected_json[count++]);
5283  }
5284  }
5285  }
5286 }
5287 
5288 
5289 //-----------------------------------------------------------------------------
5290 
5291 // To avoid imposing a particular type of error handling, ryml uses
5292 // error handler callbacks. This enables users to use exceptions, or
5293 // setjmp()/longjmp(), or plain calls to abort(), as they see fit.
5294 //
5295 // However, it is important to note that the error callbacks must never
5296 // return to the caller! Otherwise, an infinite loop or program crash
5297 // will likely occur.
5298 //
5299 // For this reason, to recover from an error when exceptions are disabled,
5300 // then a non-local jump must be performed using setjmp()/longjmp().
5301 // The code below demonstrates both flows.
5302 //
5303 // ryml provides default error handlers, which call
5304 // std::abort(). You can use the cmake option and the macro
5305 // RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS to have the default error
5306 // handler throw an exception instead.
5307 
5308 /** demonstrates how to set a custom error handler for ryml */
5309 void sample_error_handler()
5310 {
5311  ErrorHandlerExample errh; // browse the implementation of this
5312  // class to understand more details
5313  errh.check_disabled();
5314  // set the global error handlers. Note the error callbacks must
5315  // never return: they must either throw an exception, use setjmp()
5316  // and longjmp(), or abort. Otherwise, the parser will enter into
5317  // an infinite loop, or the program may crash.
5319  errh.check_enabled();
5320  CHECK(errh.check_error_occurs([&]{
5321  ryml::Tree tree = ryml::parse_in_arena("errorhandler.yml", "[a: b\n}");
5322  }));
5323  ryml::set_callbacks(errh.original_callbacks); // restore defaults.
5324  errh.check_disabled();
5325 }
5326 
5327 
5328 //-----------------------------------------------------------------------------
5329 
5330 void sample_error_basic()
5331 {
5332  auto cause_basic_error = []{
5333  ryml::TagDirective tag = {};
5334  return tag.create_from_str("%%%TAG abc");
5335  };
5336  {
5337  ScopedErrorHandlerExample errh; // set the example callbacks (scoped)
5338  CHECK(errh.check_error_occurs(cause_basic_error));
5339  }
5340 #ifdef _RYML_WITH_EXCEPTIONS
5341  bool gotit = false;
5342  try
5343  {
5344  cause_basic_error();
5345  }
5346  catch(ryml::ExceptionBasic const& exc)
5347  {
5348  gotit = true;
5349  ryml::csubstr msg = ryml::to_csubstr(exc.what());
5350  CHECK(!exc.errdata_basic.location.name.empty());
5351  CHECK(!msg.empty());
5352  }
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:226
ryml::Callbacks original_callbacks
Definition: quickstart.cpp:228
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 5357 of file quickstart.cpp.

5358 {
5359  ryml::csubstr ymlsrc = R"({
5360  a: b
5361  [
5362 )";
5363  ryml::csubstr ymlfile = "file.yml";
5364  auto cause_parse_error = [&]{
5365  return ryml::parse_in_arena(ymlfile, ymlsrc);
5366  };
5367  // the YAML in ymlsrc must cause a parse error while it is being
5368  // parsed. We use our error handler to catch that error, and save
5369  // the error info:
5370  ErrorHandlerExample errh;
5371  {
5373  CHECK(errh.check_error_occurs(cause_parse_error));
5374  // the handler in errh saves the error info in itself. Let's
5375  // use that to see the messages we get.
5376  //
5377  // this message is the short message passed into the parse
5378  // error handler:
5379  CHECK(errh.saved_msg_short == "invalid character: '['");
5380  // this message was created inside the handler, by calling
5381  // ryml::err_parse_format():
5382  CHECK(ryml::to_csubstr(errh.saved_msg_full).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
5383  // If you keep the YAML source buffer around, you can also use
5384  // it to create/print a larger error message showing the
5385  // YAML source code context which causes the error:
5386  std::string msg_ctx = errh.saved_msg_full + "\n";
5387  ryml::location_format_with_context([&msg_ctx](ryml::csubstr s){
5388  msg_ctx.append(s.str, s.len);
5389  }, errh.saved_parse_loc, ymlsrc, "err");
5390  CHECK(ryml::to_csubstr(msg_ctx).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
5391  CHECK(ryml::to_csubstr(msg_ctx).ends_with(R"(file.yml:3: col=4 (12B): err:
5392 err:
5393 err: [
5394 err: |
5395 err: (here)
5396 err:
5397 err: see region:
5398 err:
5399 err: {
5400 err: a: b
5401 err: [
5402 err: |
5403 err: (here)
5404 )"));
5405  //
5406  // Let's now check the location (see the message above):
5407  CHECK(errh.saved_parse_loc.name == ymlfile);
5408  CHECK(errh.saved_parse_loc.line == 3);
5409  CHECK(errh.saved_parse_loc.col == 4);
5410  CHECK(errh.saved_parse_loc.offset == 12);
5411  CHECK(errh.saved_parse_loc.offset <= ymlsrc.len);
5412  // ... and this is the location in the ryml source code file where
5413  // this error was found:
5414  CHECK(!errh.saved_basic_loc.name.empty());
5415  CHECK(errh.saved_basic_loc.line > 0);
5416  CHECK(errh.saved_basic_loc.col > 0);
5417  CHECK(errh.saved_basic_loc.offset > 0);
5419  }
5420  // A parse error is also a basic error. If no parse error handler
5421  // is set, then ryml falls back to a basic error:
5422  {
5423  ryml::Callbacks cb = errh.callbacks();
5424  cb.m_error_parse = nullptr;
5425  ryml::set_callbacks(cb);
5426  CHECK(ryml::get_callbacks().m_error_parse == nullptr);
5427  CHECK(errh.check_error_occurs(cause_parse_error));
5428  // we got a basic error instead of a parse error:
5429  CHECK(errh.saved_msg_short == "invalid character: '['");
5430  // notice that the full message now displays this as a basic
5431  // error:
5432  CHECK(errh.saved_msg_full == "file.yml:3: col=4 (12B): ERROR: [basic] invalid character: '['");
5433  // the yml location is now in the location saved from the basic error
5434  CHECK(errh.saved_basic_loc.name == ymlfile);
5435  CHECK(errh.saved_basic_loc.line == 3);
5436  CHECK(errh.saved_basic_loc.col == 4);
5437  CHECK(errh.saved_basic_loc.offset == 12);
5438  CHECK(errh.saved_basic_loc.offset <= ymlsrc.len);
5439  CHECK(errh.saved_parse_loc.name == ryml::csubstr{});
5444  }
5445 #ifdef _RYML_WITH_EXCEPTIONS
5446  bool gotit = false;
5447  try
5448  {
5449  cause_parse_error();
5450  }
5451  catch(ryml::ExceptionParse const& exc)
5452  {
5453  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:253
std::string saved_msg_full
Definition: quickstart.cpp:251
std::string saved_msg_short
Definition: quickstart.cpp:250
ryml::Location saved_parse_loc
Definition: quickstart.cpp:254
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, 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, ErrorHandlerExample::original_callbacks, 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 5461 of file quickstart.cpp.

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

◆ sample_global_allocator()

void sample_global_allocator ( )

demonstrates how to set the global allocator for ryml

Definition at line 5633 of file quickstart.cpp.

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

◆ sample_per_tree_allocator()

void sample_per_tree_allocator ( )

Definition at line 5756 of file quickstart.cpp.

5756  {
5757  ((GlobalAllocatorExample*)this_)->free(mem, len);
5758  }
5759 
5760  // checking
5762  {
5763  check_and_reset();
5764  }
5765  void check_and_reset()
5766  {
5767  std::cout << "size: alloc=" << alloc_size << " dealloc=" << dealloc_size << std::endl;
5768  std::cout << "count: #allocs=" << num_allocs << " #deallocs=" << num_deallocs << std::endl;
5769  CHECK(num_allocs == num_deallocs);
5770  CHECK(alloc_size >= dealloc_size); // failure here means a double free
5771  CHECK(alloc_size == dealloc_size); // failure here means a leak
5772  num_allocs = 0;
5773  num_deallocs = 0;
5774  alloc_size = 0;
5775  dealloc_size = 0;
5776  }
5777 };
5778 /** @} */
5779 
5780 
5781 /** 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 5790 of file quickstart.cpp.

5801  {foo: bar})");
5802  mem.check_and_reset();
5803 
5804  // parse another tree and check
5805  (void)ryml::parse_in_arena(R"([a, b, c, d, {foo: bar, money: pennys}])");
5806  mem.check_and_reset();
5807 
5808  // 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 5816 of file quickstart.cpp.

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